From 0d385c1b8b0b1ac873fb57e6f0fca86837f18c10 Mon Sep 17 00:00:00 2001 From: Andi Date: Thu, 29 May 2025 10:34:54 +0200 Subject: [PATCH 01/42] module implemented and tested --- GeneralsMD/Code/GameEngine/CMakeLists.txt | 28 +-- .../Include/GameClient/RadiusDecal.h | 201 +++++++++--------- .../GameLogic/Module/RadiusDecalBehavior.h | 124 +++++++++++ .../Source/Common/System/MemoryInit.cpp | 1 + .../Source/Common/Thing/ModuleFactory.cpp | 2 + .../Source/GameClient/RadiusDecal.cpp | 4 +- .../Object/Update/RadiusDecalBehavior.cpp | 199 +++++++++++++++++ 7 files changed, 450 insertions(+), 109 deletions(-) create mode 100644 GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h create mode 100644 GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index 425eba687a6..db3fe32dc02 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -129,17 +129,17 @@ set(GAMEENGINE_SRC Include/Common/UserPreferences.h Include/Common/version.h Include/Common/WellKnownKeys.h -# Include/Common/Xfer.h -# Include/Common/XferCRC.h -# Include/Common/XferDeepCRC.h -# Include/Common/XferLoad.h -# Include/Common/XferSave.h +# Include/Common/Xfer.h +# Include/Common/XferCRC.h +# Include/Common/XferDeepCRC.h +# Include/Common/XferLoad.h +# Include/Common/XferSave.h Include/GameClient/Anim2D.h Include/GameClient/AnimateWindowManager.h Include/GameClient/CampaignManager.h Include/GameClient/CDCheck.h Include/GameClient/ChallengeGenerals.h - Include/GameClient/ClientInstance.h + Include/GameClient/ClientInstance.h Include/GameClient/ClientRandomValue.h Include/GameClient/Color.h Include/GameClient/CommandXlat.h @@ -399,6 +399,7 @@ set(GAMEENGINE_SRC Include/GameLogic/Module/RadarUpdate.h Include/GameLogic/Module/RadarUpgrade.h Include/GameLogic/Module/RadiusDecalUpdate.h + Include/GameLogic/Module/RadiusDecalBehavior.h Include/GameLogic/Module/RailedTransportAIUpdate.h Include/GameLogic/Module/RailedTransportContain.h Include/GameLogic/Module/RailedTransportDockUpdate.h @@ -668,10 +669,10 @@ set(GAMEENGINE_SRC Source/Common/System/Trig.cpp Source/Common/System/UnicodeString.cpp Source/Common/System/Upgrade.cpp -# Source/Common/System/Xfer.cpp -# Source/Common/System/XferCRC.cpp -# Source/Common/System/XferLoad.cpp -# Source/Common/System/XferSave.cpp +# Source/Common/System/Xfer.cpp +# Source/Common/System/XferCRC.cpp +# Source/Common/System/XferLoad.cpp +# Source/Common/System/XferSave.cpp Source/Common/TerrainTypes.cpp Source/Common/Thing/DrawModule.cpp Source/Common/Thing/Module.cpp @@ -681,7 +682,7 @@ set(GAMEENGINE_SRC Source/Common/Thing/ThingTemplate.cpp Source/Common/UserPreferences.cpp Source/Common/version.cpp - Source/GameClient/ClientInstance.cpp + Source/GameClient/ClientInstance.cpp Source/GameClient/Color.cpp Source/GameClient/Credits.cpp Source/GameClient/Display.cpp @@ -1035,6 +1036,7 @@ set(GAMEENGINE_SRC Source/GameLogic/Object/Update/ProneUpdate.cpp Source/GameLogic/Object/Update/RadarUpdate.cpp Source/GameLogic/Object/Update/RadiusDecalUpdate.cpp + Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp Source/GameLogic/Object/Update/ScatterShotUpdate.cpp Source/GameLogic/Object/Update/SlavedUpdate.cpp Source/GameLogic/Object/Update/SmartBombTargetHomingUpdate.cpp @@ -1167,12 +1169,12 @@ target_include_directories(z_gameengine PRIVATE ) target_link_libraries(z_gameengine PRIVATE - corei_gameengine_private + corei_gameengine_private zi_always ) target_link_libraries(z_gameengine PUBLIC - corei_gameengine_public + corei_gameengine_public z_wwvegas ) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/RadiusDecal.h b/GeneralsMD/Code/GameEngine/Include/GameClient/RadiusDecal.h index 5e720d3f545..bd213160c22 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/RadiusDecal.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/RadiusDecal.h @@ -1,95 +1,106 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: RadiusDecal.h /////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef _RadiusDecal_H_ -#define _RadiusDecal_H_ - -#include "Common/GameCommon.h" -#include "Common/GameType.h" -#include "GameClient/Color.h" - -enum ShadowType CPP_11(: Int); -class Player; -class Shadow; -class RadiusDecalTemplate; - -// ------------------------------------------------------------------------------------------------ -class RadiusDecal -{ - friend class RadiusDecalTemplate; -private: - const RadiusDecalTemplate* m_template; - Shadow* m_decal; - Bool m_empty; -public: - RadiusDecal(); - RadiusDecal(const RadiusDecal& that); - RadiusDecal& operator=(const RadiusDecal& that); - ~RadiusDecal(); - - void xferRadiusDecal( Xfer *xfer ); - - // please note: it is very important, for game/net sync reasons, to ensure that - // isEmpty() returns the same value, regardless of whether this decal will - // be visible to the local player or not. - Bool isEmpty() const { return m_empty; } - void clear(); - void update(); - void setPosition(const Coord3D& pos); - void setOpacity( const Real o ); -}; - -// ------------------------------------------------------------------------------------------------ -class RadiusDecalTemplate -{ - friend class RadiusDecal; -private: - AsciiString m_name; - ShadowType m_shadowType; - Real m_minOpacity; - Real m_maxOpacity; - UnsignedInt m_opacityThrobTime; - Color m_color; - Bool m_onlyVisibleToOwningPlayer; - -public: - RadiusDecalTemplate(); - - Bool valid() const { return m_name.isNotEmpty(); } - void xferRadiusDecalTemplate( Xfer *xfer ); - - // please note: it is very important, for game/net sync reasons, to ensure that - // a valid radiusdecal is created, even if will not be visible to the local player, - // since some logic makes decisions based on this. - void createRadiusDecal(const Coord3D& pos, Real radius, const Player* owningPlayer, RadiusDecal& result) const; - - static void parseRadiusDecalTemplate(INI* ini, void *instance, void * store, const void* /*userData*/); -}; - -#endif +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: RadiusDecal.h /////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef _RadiusDecal_H_ +#define _RadiusDecal_H_ + +#include "Common/GameCommon.h" +#include "Common/GameType.h" +#include "GameClient/Color.h" + +enum ShadowType CPP_11(: Int); +class Player; +class Shadow; +class RadiusDecalTemplate; + +// ------------------------------------------------------------------------------------------------ +class RadiusDecal +{ + friend class RadiusDecalTemplate; +private: + const RadiusDecalTemplate* m_template; + Shadow* m_decal; + Bool m_empty; +public: + RadiusDecal(); + RadiusDecal(const RadiusDecal& that); + RadiusDecal& operator=(const RadiusDecal& that); + ~RadiusDecal(); + + void xferRadiusDecal( Xfer *xfer ); + + // please note: it is very important, for game/net sync reasons, to ensure that + // isEmpty() returns the same value, regardless of whether this decal will + // be visible to the local player or not. + Bool isEmpty() const { return m_empty; } + void clear(); + void update(); + void setPosition(const Coord3D& pos); + void setOpacity( const Real o ); +}; + +// ------------------------------------------------------------------------------------------------ +class RadiusDecalTemplate +{ + friend class RadiusDecal; +private: + AsciiString m_name; + ShadowType m_shadowType; + Real m_minOpacity; + Real m_maxOpacity; + UnsignedInt m_opacityThrobTime; + Color m_color; + Bool m_onlyVisibleToOwningPlayer; + +public: + RadiusDecalTemplate(); + + Bool valid() const { return m_name.isNotEmpty(); } + void xferRadiusDecalTemplate( Xfer *xfer ); + + // please note: it is very important, for game/net sync reasons, to ensure that + // a valid radiusdecal is created, even if will not be visible to the local player, + // since some logic makes decisions based on this. + void createRadiusDecal(const Coord3D& pos, Real radius, const Player* owningPlayer, RadiusDecal& result) const; + + static void parseRadiusDecalTemplate(INI* ini, void *instance, void * store, const void* /*userData*/); + + // DEBUG: + /*void debugPrint() const { + DEBUG_LOG(("-- m_name = %s\n", m_name.str())); + DEBUG_LOG(("-- m_shadowType = %d\n", m_shadowType)); + DEBUG_LOG(("-- m_minOpacity = %f\n", m_minOpacity)); + DEBUG_LOG(("-- m_maxOpacity = %f\n", m_maxOpacity)); + DEBUG_LOG(("-- m_opacityThrobTime = %d\n", m_opacityThrobTime)); + DEBUG_LOG(("-- m_color = %d\n", m_color)); + DEBUG_LOG(("-- m_onlyVisibleToOwningPlayer = %d\n", m_onlyVisibleToOwningPlayer)); + };*/ +}; + +#endif diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h new file mode 100644 index 00000000000..9fba409cdc5 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h @@ -0,0 +1,124 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: RadiusDecalBehavior.h ///////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __RadiusDecalBehavior_H_ +#define __RadiusDecalBehavior_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "GameLogic/Module/BehaviorModule.h" +#include "GameLogic/Module/UpgradeModule.h" +#include "GameLogic/Module/UpdateModule.h" +#include "GameClient/RadiusDecal.h" + +//------------------------------------------------------------------------------------------------- +class RadiusDecalBehaviorModuleData : public UpdateModuleData +{ +public: + UpgradeMuxData m_upgradeMuxData; + Bool m_initiallyActive; + + RadiusDecalTemplate m_decalTemplate; + Real m_decalRadius; + + RadiusDecalBehaviorModuleData(); + + static void buildFieldParse(MultiIniFieldParse& p); +}; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class RadiusDecalBehavior : public UpdateModule, public UpgradeMux +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( RadiusDecalBehavior, "RadiusDecalBehavior" ) + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( RadiusDecalBehavior, RadiusDecalBehaviorModuleData ) + +public: + + RadiusDecalBehavior( Thing *thing, const ModuleData* moduleData ); + // virtual destructor prototype provided by memory pool declaration + + // module methids + static Int getInterfaceMask() { return UpdateModule::getInterfaceMask() | (MODULEINTERFACE_UPGRADE); } + + // BehaviorModule + virtual UpgradeModuleInterface* getUpgrade() { return this; } + + //void createRadiusDecal( const Coord3D& pos ); + // void createRadiusDecal( const RadiusDecalTemplate& tmpl, Real radius, const Coord3D& pos ); + + void createRadiusDecal( void ); + void killRadiusDecal( void ); + + // UpdateModuleInterface + virtual UpdateSleepTime update(); + +protected: + + + virtual void upgradeImplementation() + { + createRadiusDecal(); + setWakeFrame(getObject(), UPDATE_SLEEP_NONE); + } + + virtual void getUpgradeActivationMasks(UpgradeMaskType& activation, UpgradeMaskType& conflicting) const + { + getRadiusDecalBehaviorModuleData()->m_upgradeMuxData.getUpgradeActivationMasks(activation, conflicting); + } + + virtual void performUpgradeFX() + { + getRadiusDecalBehaviorModuleData()->m_upgradeMuxData.performUpgradeFX(getObject()); + } + + virtual void processUpgradeRemoval() + { + // I can't take it any more. Let the record show that I think the UpgradeMux multiple inheritence is CRAP. + getRadiusDecalBehaviorModuleData()->m_upgradeMuxData.muxDataProcessUpgradeRemoval(getObject()); + } + + virtual Bool requiresAllActivationUpgrades() const + { + return getRadiusDecalBehaviorModuleData()->m_upgradeMuxData.m_requiresAllTriggers; + } + + inline Bool isUpgradeActive() const { return isAlreadyUpgraded(); } + + virtual Bool isSubObjectsUpgrade() { return false; } + +private: + + RadiusDecal m_radiusDecal; + + void clearDecal( void ); +}; + +#endif // __RadiusDecalBehavior_H_ + diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp index 3e2cccaac38..89db704b520 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp @@ -248,6 +248,7 @@ static PoolSizeRec sizes[] = { "SpectreGunshipDeploymentUpdate", 8, 8 }, { "BaikonurLaunchPower", 4, 4 }, { "RadiusDecalUpdate", 16, 16 }, + { "RadiusDecalBehavior", 32, 32 }, { "BattlePlanUpdate", 32, 32 }, { "LifetimeUpdate", 32, 32 }, { "LocomotorSetUpgrade", 512, 128 }, diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index 83a6ff4b14b..c152ef74e4f 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -144,6 +144,7 @@ #include "GameLogic/Module/BattlePlanUpdate.h" #include "GameLogic/Module/LifetimeUpdate.h" #include "GameLogic/Module/RadiusDecalUpdate.h" +#include "GameLogic/Module/RadiusDecalBehavior.h" #include "GameLogic/Module/AutoDepositUpdate.h" #include "GameLogic/Module/MissileAIUpdate.h" #include "GameLogic/Module/NeutronMissileUpdate.h" @@ -406,6 +407,7 @@ void ModuleFactory::init( void ) addModule( EnemyNearUpdate ); addModule( LifetimeUpdate ); addModule( RadiusDecalUpdate ); + addModule( RadiusDecalBehavior ); addModule( EMPUpdate ); addModule( LeafletDropBehavior ); addModule( AutoDepositUpdate ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/RadiusDecal.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/RadiusDecal.cpp index c0ffe556a05..60687cf2f84 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/RadiusDecal.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/RadiusDecal.cpp @@ -123,7 +123,7 @@ void RadiusDecalTemplate::xferRadiusDecalTemplate( Xfer *xfer ) { "Style", INI::parseBitString32, TheShadowNames, offsetof( RadiusDecalTemplate, m_shadowType ) }, { "OpacityMin", INI::parsePercentToReal, NULL, offsetof( RadiusDecalTemplate, m_minOpacity ) }, { "OpacityMax", INI::parsePercentToReal, NULL, offsetof( RadiusDecalTemplate, m_maxOpacity) }, - { "OpacityThrobTime", INI::parseDurationUnsignedInt,NULL, offsetof( RadiusDecalTemplate, m_opacityThrobTime ) }, + { "OpacityThrobTime", INI::parseDurationUnsignedInt, NULL, offsetof( RadiusDecalTemplate, m_opacityThrobTime ) }, { "Color", INI::parseColorInt, NULL, offsetof( RadiusDecalTemplate, m_color ) }, { "OnlyVisibleToOwningPlayer", INI::parseBool, NULL, offsetof( RadiusDecalTemplate, m_onlyVisibleToOwningPlayer ) }, { 0, 0, 0, 0 } @@ -195,9 +195,11 @@ RadiusDecal::~RadiusDecal() // ------------------------------------------------------------------------------------------------ void RadiusDecal::update() { + DEBUG_LOG(("RadiusDecal::update() - (0)\n")); if (m_decal && m_template) { UnsignedInt now = TheGameLogic->getFrame(); + // m_template->debugPrint(); Real theta = (2*PI) * (Real)(now % m_template->m_opacityThrobTime) / (Real)m_template->m_opacityThrobTime; Real percent = 0.5f * (Sin(theta) + 1.0f); Int opac; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp new file mode 100644 index 00000000000..990eeae1400 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp @@ -0,0 +1,199 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: RadiusDecalBehavior.cpp /////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#include "Common/RandomValue.h" +#include "Common/Xfer.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/Module/RadiusDecalBehavior.h" +#include "GameLogic/Object.h" + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +RadiusDecalBehaviorModuleData::RadiusDecalBehaviorModuleData() +{ + m_initiallyActive = false; + m_decalRadius = 0.0f; +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +/*static*/ void RadiusDecalBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + UpdateModuleData::buildFieldParse(p); + static const FieldParse dataFieldParse[] = + { + { "StartsActive", INI::parseBool, NULL, offsetof(RadiusDecalBehaviorModuleData, m_initiallyActive) }, + { "RadiusDecal", RadiusDecalTemplate::parseRadiusDecalTemplate, NULL, offsetof( RadiusDecalBehaviorModuleData, m_decalTemplate) }, + { "Radius", INI::parseReal, NULL, offsetof( RadiusDecalBehaviorModuleData, m_decalRadius) }, + { 0, 0, 0, 0 } + }; + + BehaviorModuleData::buildFieldParse(p); + p.add(dataFieldParse); + p.add(UpgradeMuxData::getFieldParse(), offsetof(RadiusDecalBehaviorModuleData, m_upgradeMuxData)); +} + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +RadiusDecalBehavior::RadiusDecalBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData ) +{ + if (getRadiusDecalBehaviorModuleData()->m_initiallyActive) + { + giveSelfUpgrade(); + } + else { + clearDecal(); + setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +RadiusDecalBehavior::~RadiusDecalBehavior( void ) +{ + clearDecal(); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void RadiusDecalBehavior::createRadiusDecal( void ) +{ + const RadiusDecalBehaviorModuleData* data = getRadiusDecalBehaviorModuleData(); + const RadiusDecalTemplate& tmpl = data->m_decalTemplate; + m_radiusDecal.clear(); + if (tmpl.valid()) { + // DEBUG + DEBUG_LOG(("RadiusDecalBehavior::createRadiusDecal: \n")); + tmpl.debugPrint(); + + tmpl.createRadiusDecal(*(getObject()->getPosition()), data->m_decalRadius, getObject()->getControllingPlayer(), m_radiusDecal); + setWakeFrame(getObject(), m_radiusDecal.isEmpty() ? UPDATE_SLEEP_FOREVER : UPDATE_SLEEP_NONE); + } + else { + // We don't have a decal defined. Do we need this? + setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void RadiusDecalBehavior::killRadiusDecal() +{ + clearDecal(); + setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); +} + +// ----------------------------------------------------------------------------------------------- +// Actual cleanup of the decal. This handles the case if the decal is null +void RadiusDecalBehavior::clearDecal() +{ + //if (m_radiusDecal != NULL && !m_radiusDecal.isEmpty()) { + m_radiusDecal.clear(); + //} +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +UpdateSleepTime RadiusDecalBehavior::update( void ) +{ + // Upgrade has not been triggered, or it might have been removed. + if (!isUpgradeActive()) { + clearDecal(); + return UPDATE_SLEEP_FOREVER; + } + + // The object is dead + if (getObject()->isEffectivelyDead()) { + clearDecal(); + return UPDATE_SLEEP_FOREVER; + } + + // If we reach this point, the upgrade is active: Create the decal if it doesn't exist. + //if (m_radiusDecal == NULL || m_radiusDecal.isEmpty()) { + //if (m_radiusDecal.isEmpty()) { + // createRadiusDecal(); + //} + + // This should be our usual case + if (!m_radiusDecal.isEmpty()) { + m_radiusDecal.update(); + m_radiusDecal.setPosition(*(getObject()->getPosition())); + return UPDATE_SLEEP_NONE; + } + + // Something probably went wrong if we reach this point + return UPDATE_SLEEP_FOREVER; +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void RadiusDecalBehavior::crc( Xfer *xfer ) +{ + + // extend base class + UpdateModule::crc( xfer ); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void RadiusDecalBehavior::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // extend base class + UpdateModule::xfer( xfer ); + + // decal, if any + m_radiusDecal.xferRadiusDecal(xfer); + + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void RadiusDecalBehavior::loadPostProcess( void ) +{ + + // extend base class + UpdateModule::loadPostProcess(); + +} // end loadPostProcess From 6b930ca3659bb1ad7dcbf913b54dcb45c9e3322f Mon Sep 17 00:00:00 2001 From: Andi Date: Thu, 29 May 2025 20:20:48 +0200 Subject: [PATCH 02/42] Fixed shadow texture stacking with multiple draw modules --- .../GameEngine/Source/GameClient/Drawable.cpp | 1 + .../Source/GameClient/RadiusDecal.cpp | 18 ++++--- .../Object/Update/RadiusDecalBehavior.cpp | 17 ++---- .../GameClient/Module/W3DModelDraw.h | 1 + .../Drawable/Draw/W3DDefaultDraw.cpp | 3 ++ .../GameClient/Drawable/Draw/W3DModelDraw.cpp | 53 ++++++++++++------- 6 files changed, 52 insertions(+), 41 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index e91de9272f8..35a1e200c7a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -867,6 +867,7 @@ Bool Drawable::getCurrentWorldspaceClientBonePositions(const char* boneName, Mat //------------------------------------------------------------------------------------------------- void Drawable::setTerrainDecal(TerrainDecalType type) { + DEBUG_LOG(("Drawable::setTerrainDecal - type = %d\n", type)); if (m_terrainDecalType == type) return; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/RadiusDecal.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/RadiusDecal.cpp index 60687cf2f84..c40701076dc 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/RadiusDecal.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/RadiusDecal.cpp @@ -195,17 +195,21 @@ RadiusDecal::~RadiusDecal() // ------------------------------------------------------------------------------------------------ void RadiusDecal::update() { - DEBUG_LOG(("RadiusDecal::update() - (0)\n")); if (m_decal && m_template) { - UnsignedInt now = TheGameLogic->getFrame(); - // m_template->debugPrint(); - Real theta = (2*PI) * (Real)(now % m_template->m_opacityThrobTime) / (Real)m_template->m_opacityThrobTime; - Real percent = 0.5f * (Sin(theta) + 1.0f); Int opac; - if( TheGameLogic->getDrawIconUI() ) + if (TheGameLogic->getDrawIconUI()) { - opac = REAL_TO_INT((m_template->m_minOpacity + percent * (m_template->m_maxOpacity - m_template->m_minOpacity)) * 255.0f); + if (m_template->m_opacityThrobTime > 0) { + UnsignedInt now = TheGameLogic->getFrame(); + // m_template->debugPrint(); + Real theta = (2 * PI) * (Real)(now % m_template->m_opacityThrobTime) / (Real)m_template->m_opacityThrobTime; + Real percent = 0.5f * (Sin(theta) + 1.0f); + opac = REAL_TO_INT((m_template->m_minOpacity + percent * (m_template->m_maxOpacity - m_template->m_minOpacity)) * 255.0f); + } + else { + opac = REAL_TO_INT(m_template->m_maxOpacity * 255.0f); + } } else { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp index 990eeae1400..572613bf0e7 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp @@ -91,10 +91,8 @@ void RadiusDecalBehavior::createRadiusDecal( void ) const RadiusDecalTemplate& tmpl = data->m_decalTemplate; m_radiusDecal.clear(); if (tmpl.valid()) { - // DEBUG - DEBUG_LOG(("RadiusDecalBehavior::createRadiusDecal: \n")); - tmpl.debugPrint(); - + // DEBUG_LOG(("RadiusDecalBehavior::createRadiusDecal: \n")); + // tmpl.debugPrint(); tmpl.createRadiusDecal(*(getObject()->getPosition()), data->m_decalRadius, getObject()->getControllingPlayer(), m_radiusDecal); setWakeFrame(getObject(), m_radiusDecal.isEmpty() ? UPDATE_SLEEP_FOREVER : UPDATE_SLEEP_NONE); } @@ -113,12 +111,9 @@ void RadiusDecalBehavior::killRadiusDecal() } // ----------------------------------------------------------------------------------------------- -// Actual cleanup of the decal. This handles the case if the decal is null void RadiusDecalBehavior::clearDecal() { - //if (m_radiusDecal != NULL && !m_radiusDecal.isEmpty()) { - m_radiusDecal.clear(); - //} + m_radiusDecal.clear(); } //------------------------------------------------------------------------------------------------- @@ -137,12 +132,6 @@ UpdateSleepTime RadiusDecalBehavior::update( void ) return UPDATE_SLEEP_FOREVER; } - // If we reach this point, the upgrade is active: Create the decal if it doesn't exist. - //if (m_radiusDecal == NULL || m_radiusDecal.isEmpty()) { - //if (m_radiusDecal.isEmpty()) { - // createRadiusDecal(); - //} - // This should be our usual case if (!m_radiusDecal.isEmpty()) { m_radiusDecal.update(); diff --git a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h index 59fcef32c10..3ca51084a45 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h @@ -509,6 +509,7 @@ class W3DModelDraw : public DrawModule, public ObjectDrawInterface Bool m_hideHeadlights; Bool m_pauseAnimation; Int m_animationMode; + Bool m_isFirstDrawModule; void adjustAnimation(const ModelConditionInfo* prevState, Real prevAnimFraction); Real getCurrentAnimFraction() const; diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDefaultDraw.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDefaultDraw.cpp index 9b09cd32a3b..161e5cc5015 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDefaultDraw.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DDefaultDraw.cpp @@ -66,6 +66,9 @@ W3DDefaultDraw::W3DDefaultDraw(Thing *thing, const ModuleData* moduleData) : Dra shadowInfo.m_sizeY=0; shadowInfo.m_offsetX=0; shadowInfo.m_offsetY=0; + + DEBUG_LOG(("W3DDefaultDraw::W3DDefaultDraw - addShadow\n")); + m_shadow = TheW3DShadowManager->addShadow(m_renderObject, &shadowInfo); diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp index fead45dcb71..4aee7f97721 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp @@ -1734,6 +1734,7 @@ W3DModelDraw::W3DModelDraw(Thing *thing, const ModuleData* moduleData) : DrawMod m_shadowEnabled = TRUE; m_terrainDecal = NULL; m_trackRenderObject = NULL; + m_isFirstDrawModule = FALSE; m_whichAnimInCurState = -1; m_nextState = NULL; m_nextStateAnimLoopDuration = NO_NEXT_DURATION; @@ -1759,24 +1760,30 @@ W3DModelDraw::W3DModelDraw(Thing *thing, const ModuleData* moduleData) : DrawMod Drawable* draw = getDrawable(); - if ( draw ) - { - Object* obj = draw->getObject(); - if (obj) - { - if (TheGlobalData->m_timeOfDay == TIME_OF_DAY_NIGHT) - m_hexColor = obj->getNightIndicatorColor(); - else - m_hexColor = obj->getIndicatorColor(); - } - - // THE VAST MAJORITY OF THESE SHOULD BE TRUE - if ( ! getW3DModelDrawModuleData()->m_receivesDynamicLights) - { - draw->setReceivesDynamicLights( FALSE ); - DEBUG_LOG(("setReceivesDynamicLights = FALSE: %s\n", draw->getTemplate()->getName().str())); - } - } + if (draw) + { + Object* obj = draw->getObject(); + if (obj) + { + if (TheGlobalData->m_timeOfDay == TIME_OF_DAY_NIGHT) + m_hexColor = obj->getNightIndicatorColor(); + else + m_hexColor = obj->getIndicatorColor(); + } + + // THE VAST MAJORITY OF THESE SHOULD BE TRUE + if (!getW3DModelDrawModuleData()->m_receivesDynamicLights) + { + draw->setReceivesDynamicLights(FALSE); + DEBUG_LOG(("setReceivesDynamicLights = FALSE: %s\n", draw->getTemplate()->getName().str())); + } + + // Check existing draw modules. If they are null, we are the first! + DrawModule** drawModules = draw->getDrawModules(); + if ((*drawModules) == NULL) { + m_isFirstDrawModule = TRUE; + } + } setModelState(info); } @@ -1859,7 +1866,8 @@ void W3DModelDraw::allocateShadows(void) const ThingTemplate *tmplate=getDrawable()->getTemplate(); //Check if we don't already have a shadow but need one for this type of model. - if (m_shadow == NULL && m_renderObject && TheW3DShadowManager && tmplate->getShadowType() != SHADOW_NONE) + if (m_shadow == NULL && m_renderObject && TheW3DShadowManager && tmplate->getShadowType() != SHADOW_NONE + && m_isFirstDrawModule) { Shadow::ShadowTypeInfo shadowInfo; strcpy(shadowInfo.m_ShadowName, tmplate->getShadowTextureName().str()); @@ -2734,6 +2742,8 @@ Bool W3DModelDraw::updateBonesForClientParticleSystems() //------------------------------------------------------------------------------------------------- void W3DModelDraw::setTerrainDecal(TerrainDecalType type) { + // DEBUG_LOG(("W3DModelDraw::setTerrainDecal - type = %d. invalid = %d\n", type, type == TERRAIN_DECAL_NONE || type >= TERRAIN_DECAL_MAX)); + if (m_terrainDecal) m_terrainDecal->release(); @@ -3068,7 +3078,7 @@ void W3DModelDraw::setModelState(const ModelConditionInfo* newState) } // set up shadows - if (m_renderObject && TheW3DShadowManager && tmplate->getShadowType() != SHADOW_NONE) + if (m_renderObject && TheW3DShadowManager && tmplate->getShadowType() != SHADOW_NONE && m_isFirstDrawModule) { Shadow::ShadowTypeInfo shadowInfo; strcpy(shadowInfo.m_ShadowName, tmplate->getShadowTextureName().str()); @@ -4329,6 +4339,9 @@ void W3DModelDraw::xfer( Xfer *xfer ) if( xfer->getXferMode() == XFER_LOAD && m_subObjectVec.empty() == FALSE ) updateSubObjects(); + // New stuff: + xfer->xferBool( &m_isFirstDrawModule ); + } // end xfer // ------------------------------------------------------------------------------------------------ From 8c7bcebc3a725fbf3ce15df8a97b7356068fce08 Mon Sep 17 00:00:00 2001 From: Andi Date: Fri, 30 May 2025 10:55:58 +0200 Subject: [PATCH 03/42] add random path movement --- .../GameLogic/Module/MissileAIUpdate.h | 12 ++- .../Update/AIUpdate/MissileAIUpdate.cpp | 92 +++++++++++++++++-- 2 files changed, 93 insertions(+), 11 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h index 703ce7d1c70..8d04a4796b3 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h @@ -63,8 +63,12 @@ class MissileAIUpdateModuleData : public AIUpdateModuleData Real m_lockDistance; ///< If I get this close to my target, guaranteed hit. Bool m_detonateCallsKill; ///< if true, kill() will be called, instead of KILL_SELF state, which calls destroy. - Int m_killSelfDelay; ///< If I have detonated and entered the KILL-SELF state, how ling do I wait before I Kill/destroy self? - MissileAIUpdateModuleData(); + Int m_killSelfDelay; ///< If I have detonated and entered the KILL-SELF state, how ling do I wait before I Kill/destroy self? + + Real m_randomPathEndDistance; + Real m_randomPathOffset; + + MissileAIUpdateModuleData(); static void buildFieldParse(MultiIniFieldParse& p); @@ -89,6 +93,7 @@ class MissileAIUpdate : public AIUpdateInterface, public ProjectileUpdateInterfa DEAD = 5, KILL = 6, ///< Hit victim (cheat). KILL_SELF = 7, ///< Destroy self. + ATTACK_RANDOM_PATH = 8, ///< fly toward victim }; virtual ProjectileUpdateInterface* getProjectileUpdateInterface() { return this; } @@ -121,6 +126,7 @@ class MissileAIUpdate : public AIUpdateInterface, public ProjectileUpdateInterfa ObjectID m_victimID; ///< ID of object that I am rocketing towards (INVALID_ID if not yet launched) UnsignedInt m_fuelExpirationDate; ///< how long 'til we run out of fuel Real m_noTurnDistLeft; ///< when zero, ok to start turning + Real m_randomPathDistLeft; ///< when zero, leave random path Real m_maxAccel; Coord3D m_originalTargetPos; ///< When firing uphill, we aim high to clear the brow of the hill. jba. Coord3D m_prevPos; @@ -137,7 +143,7 @@ class MissileAIUpdate : public AIUpdateInterface, public ProjectileUpdateInterfa void doPrelaunchState(); void doLaunchState(); void doIgnitionState(); - void doAttackState(Bool turnOK); + void doAttackState(Bool turnOK, Bool randomPath = FALSE); void doKillState(); void doKillSelfState(); void doDeadState(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp index 5f2bd0ef9be..7b26e21c970 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp @@ -100,6 +100,8 @@ void MissileAIUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) { "UseWeaponSpeed", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_useWeaponSpeed ) }, { "DetonateOnNoFuel", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_detonateOnNoFuel ) }, { "DistanceScatterWhenJammed",INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_distanceScatterWhenJammed ) }, + { "DistanceToTravelOnRandomPath",INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_randomPathEndDistance ) }, + { "RandomPathOffset",INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_randomPathOffset ) }, { "GarrisonHitKillRequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillKindof ) }, { "GarrisonHitKillForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillKindofNot ) }, @@ -130,6 +132,7 @@ MissileAIUpdate::MissileAIUpdate( Thing *thing, const ModuleData* moduleData ) : m_isArmed = false; m_fuelExpirationDate = 0; m_noTurnDistLeft = d->m_initialDist; + m_randomPathDistLeft = 0; m_prevPos = *getObject()->getPosition(); m_maxAccel = BIGNUM; m_detonationWeaponTmpl = NULL; @@ -498,7 +501,7 @@ void MissileAIUpdate::doIgnitionState() } //------------------------------------------------------------------------------------------------- -void MissileAIUpdate::doAttackState(Bool turnOK) +void MissileAIUpdate::doAttackState(Bool turnOK, Bool randomPath) { Locomotor* curLoco = getCurLocomotor(); @@ -522,12 +525,43 @@ void MissileAIUpdate::doAttackState(Bool turnOK) { if (curLoco) { - curLoco->setMaxAcceleration(m_maxAccel); - curLoco->setMaxTurnRate(turnOK ? BIGNUM : 0); + if (randomPath) { + if (m_randomPathDistLeft <= 0) { + // Weare leaving randomPath state. Reestablish target. + + Object* victim = TheGameLogic->findObjectByID(m_victimID); + + if (victim && d->m_tryToFollowTarget) + { + getStateMachine()->setGoalPosition(victim->getPosition()); + aiMoveToObject(const_cast(victim), CMD_FROM_AI); + m_originalTargetPos = *victim->getPosition(); + m_isTrackingTarget = TRUE;// Remember that I was originally shot at a moving object, so if the + // target dies I can do something cool. + m_victimID = victim->getID(); + } + else + { + // Otherwise, we are just a Coord shot. + Coord3D initialPos = m_originalTargetPos; + if (d->m_lockDistance > 0.0f) { + initialPos.z += APPROACH_HEIGHT; + } + aiMoveToPosition(&initialPos, CMD_FROM_AI); + m_victimID = INVALID_ID; + } + setCurrentVictim(victim); + switchToState(ATTACK); + } + } + else { + curLoco->setMaxAcceleration(m_maxAccel); + curLoco->setMaxTurnRate(turnOK ? BIGNUM : 0); + } } } - if (d->m_lockDistance > 0) + if (!randomPath && d->m_lockDistance > 0) { Real lockDistanceSquared = d->m_lockDistance; Real distanceToTargetSquared; @@ -568,7 +602,38 @@ void MissileAIUpdate::doAttackState(Bool turnOK) if (m_noTurnDistLeft <= 0.0f) { - switchToState(ATTACK); + // We first reach random path state + if (d->m_randomPathEndDistance > 0.0 && !randomPath) { + m_randomPathDistLeft = d->m_randomPathEndDistance; + + // Pick a random position near the target as new goal, and forget tracking the target for now + Coord3D targetPosition; + if (m_isTrackingTarget && getGoalObject()) + targetPosition = *getGoalObject()->getPosition(); + else + targetPosition = *getGoalPosition(); + + //TODO, make it spherical or cylindrical? + Real scatter = d->m_randomPathOffset; + targetPosition.x += GameLogicRandomValue(-scatter, scatter); + targetPosition.y += GameLogicRandomValue(-scatter, scatter); + targetPosition.z += GameLogicRandomValue(-scatter*0.5, scatter*0.5); + getStateMachine()->setGoalObject(NULL); + aiMoveToPosition(&targetPosition, CMD_FROM_AI); + m_isTrackingTarget = FALSE; + + Locomotor* curLoco = getCurLocomotor(); + if (curLoco) + { + curLoco->setMaxAcceleration(m_maxAccel); + curLoco->setMaxTurnRate(50.0f); // TODO + } + + switchToState(ATTACK_RANDOM_PATH); + } + else { + switchToState(ATTACK); + } } // If I was fired at a flyer and have lost target (most likely they died), then I need to do something better @@ -652,10 +717,13 @@ void MissileAIUpdate::doDeadState() UpdateSleepTime MissileAIUpdate::update() { Coord3D newPos = *getObject()->getPosition(); - if (m_noTurnDistLeft > 0.0f && m_state >= IGNITION) + if ((m_noTurnDistLeft > 0.0f || m_randomPathDistLeft > 0.0f) && m_state >= IGNITION) { Real distThisTurn = sqrtf(sqr(newPos.x-m_prevPos.x) + sqr(newPos.y-m_prevPos.y) + sqr(newPos.z-m_prevPos.z)); - m_noTurnDistLeft -= distThisTurn; + if (m_noTurnDistLeft > 0.0f) + m_noTurnDistLeft -= distThisTurn; + if (m_randomPathDistLeft > 0.0f) + m_randomPathDistLeft -= distThisTurn; m_prevPos = newPos; } @@ -715,6 +783,10 @@ UpdateSleepTime MissileAIUpdate::update() doAttackState(false); break; + case ATTACK_RANDOM_PATH: + doAttackState(false, true); + break; + case ATTACK: doAttackState(true); break; @@ -860,7 +932,7 @@ void MissileAIUpdate::crc( Xfer *xfer ) void MissileAIUpdate::xfer( Xfer *xfer ) { // version - const XferVersion currentVersion = 6; + const XferVersion currentVersion = 7; XferVersion version = currentVersion; xfer->xferVersion( &version, currentVersion ); @@ -925,6 +997,10 @@ void MissileAIUpdate::xfer( Xfer *xfer ) if( version>= 6 ) xfer->xferBool( &m_isJammed ); + if( version >= 7 ) + { + xfer->xferReal( &m_randomPathDistLeft); + } } // end xfer // ------------------------------------------------------------------------------------------------ From df61f6e052dc8da330b14b7d96e3b5e2df5acb46 Mon Sep 17 00:00:00 2001 From: Andi Date: Fri, 30 May 2025 10:57:09 +0200 Subject: [PATCH 04/42] fix line endings --- .../Include/GameClient/RadiusDecal.h | 212 +++++++++--------- 1 file changed, 106 insertions(+), 106 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/RadiusDecal.h b/GeneralsMD/Code/GameEngine/Include/GameClient/RadiusDecal.h index bd213160c22..f7ea1e8cb01 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/RadiusDecal.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/RadiusDecal.h @@ -1,106 +1,106 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: RadiusDecal.h /////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef _RadiusDecal_H_ -#define _RadiusDecal_H_ - -#include "Common/GameCommon.h" -#include "Common/GameType.h" -#include "GameClient/Color.h" - -enum ShadowType CPP_11(: Int); -class Player; -class Shadow; -class RadiusDecalTemplate; - -// ------------------------------------------------------------------------------------------------ -class RadiusDecal -{ - friend class RadiusDecalTemplate; -private: - const RadiusDecalTemplate* m_template; - Shadow* m_decal; - Bool m_empty; -public: - RadiusDecal(); - RadiusDecal(const RadiusDecal& that); - RadiusDecal& operator=(const RadiusDecal& that); - ~RadiusDecal(); - - void xferRadiusDecal( Xfer *xfer ); - - // please note: it is very important, for game/net sync reasons, to ensure that - // isEmpty() returns the same value, regardless of whether this decal will - // be visible to the local player or not. - Bool isEmpty() const { return m_empty; } - void clear(); - void update(); - void setPosition(const Coord3D& pos); - void setOpacity( const Real o ); -}; - -// ------------------------------------------------------------------------------------------------ -class RadiusDecalTemplate -{ - friend class RadiusDecal; -private: - AsciiString m_name; - ShadowType m_shadowType; - Real m_minOpacity; - Real m_maxOpacity; - UnsignedInt m_opacityThrobTime; - Color m_color; - Bool m_onlyVisibleToOwningPlayer; - -public: - RadiusDecalTemplate(); - - Bool valid() const { return m_name.isNotEmpty(); } - void xferRadiusDecalTemplate( Xfer *xfer ); - - // please note: it is very important, for game/net sync reasons, to ensure that - // a valid radiusdecal is created, even if will not be visible to the local player, - // since some logic makes decisions based on this. - void createRadiusDecal(const Coord3D& pos, Real radius, const Player* owningPlayer, RadiusDecal& result) const; - - static void parseRadiusDecalTemplate(INI* ini, void *instance, void * store, const void* /*userData*/); - - // DEBUG: - /*void debugPrint() const { - DEBUG_LOG(("-- m_name = %s\n", m_name.str())); - DEBUG_LOG(("-- m_shadowType = %d\n", m_shadowType)); - DEBUG_LOG(("-- m_minOpacity = %f\n", m_minOpacity)); - DEBUG_LOG(("-- m_maxOpacity = %f\n", m_maxOpacity)); - DEBUG_LOG(("-- m_opacityThrobTime = %d\n", m_opacityThrobTime)); - DEBUG_LOG(("-- m_color = %d\n", m_color)); - DEBUG_LOG(("-- m_onlyVisibleToOwningPlayer = %d\n", m_onlyVisibleToOwningPlayer)); - };*/ -}; - -#endif +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: RadiusDecal.h /////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef _RadiusDecal_H_ +#define _RadiusDecal_H_ + +#include "Common/GameCommon.h" +#include "Common/GameType.h" +#include "GameClient/Color.h" + +enum ShadowType CPP_11(: Int); +class Player; +class Shadow; +class RadiusDecalTemplate; + +// ------------------------------------------------------------------------------------------------ +class RadiusDecal +{ + friend class RadiusDecalTemplate; +private: + const RadiusDecalTemplate* m_template; + Shadow* m_decal; + Bool m_empty; +public: + RadiusDecal(); + RadiusDecal(const RadiusDecal& that); + RadiusDecal& operator=(const RadiusDecal& that); + ~RadiusDecal(); + + void xferRadiusDecal( Xfer *xfer ); + + // please note: it is very important, for game/net sync reasons, to ensure that + // isEmpty() returns the same value, regardless of whether this decal will + // be visible to the local player or not. + Bool isEmpty() const { return m_empty; } + void clear(); + void update(); + void setPosition(const Coord3D& pos); + void setOpacity( const Real o ); +}; + +// ------------------------------------------------------------------------------------------------ +class RadiusDecalTemplate +{ + friend class RadiusDecal; +private: + AsciiString m_name; + ShadowType m_shadowType; + Real m_minOpacity; + Real m_maxOpacity; + UnsignedInt m_opacityThrobTime; + Color m_color; + Bool m_onlyVisibleToOwningPlayer; + +public: + RadiusDecalTemplate(); + + Bool valid() const { return m_name.isNotEmpty(); } + void xferRadiusDecalTemplate( Xfer *xfer ); + + // please note: it is very important, for game/net sync reasons, to ensure that + // a valid radiusdecal is created, even if will not be visible to the local player, + // since some logic makes decisions based on this. + void createRadiusDecal(const Coord3D& pos, Real radius, const Player* owningPlayer, RadiusDecal& result) const; + + static void parseRadiusDecalTemplate(INI* ini, void *instance, void * store, const void* /*userData*/); + + // DEBUG: + /*void debugPrint() const { + DEBUG_LOG(("-- m_name = %s\n", m_name.str())); + DEBUG_LOG(("-- m_shadowType = %d\n", m_shadowType)); + DEBUG_LOG(("-- m_minOpacity = %f\n", m_minOpacity)); + DEBUG_LOG(("-- m_maxOpacity = %f\n", m_maxOpacity)); + DEBUG_LOG(("-- m_opacityThrobTime = %d\n", m_opacityThrobTime)); + DEBUG_LOG(("-- m_color = %d\n", m_color)); + DEBUG_LOG(("-- m_onlyVisibleToOwningPlayer = %d\n", m_onlyVisibleToOwningPlayer)); + };*/ +}; + +#endif From 5a9d7eb6d38c6cb6af7aa5499d5daf8a5b6a0419 Mon Sep 17 00:00:00 2001 From: Andi Date: Fri, 30 May 2025 19:55:45 +0200 Subject: [PATCH 05/42] Random missile path offset (WIP) --- .../GameLogic/Module/MissileAIUpdate.h | 3 + .../Update/AIUpdate/MissileAIUpdate.cpp | 100 ++++++++++++++---- 2 files changed, 84 insertions(+), 19 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h index 8d04a4796b3..9d6776d661e 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h @@ -65,6 +65,9 @@ class MissileAIUpdateModuleData : public AIUpdateModuleData Bool m_detonateCallsKill; ///< if true, kill() will be called, instead of KILL_SELF state, which calls destroy. Int m_killSelfDelay; ///< If I have detonated and entered the KILL-SELF state, how ling do I wait before I Kill/destroy self? + Real m_turnRateAttacking; + Real m_turnRateInitial; + Real m_randomPathEndDistance; Real m_randomPathOffset; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp index 7b26e21c970..7b64f06fe03 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp @@ -58,6 +58,23 @@ const Real BIGNUM = 99999.0f; //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") #endif + +//------------------------------------------------------------------------------------------------- +static void adjustVector(Coord3D* vec, const Matrix3D* mtx) +{ + if (mtx) + { + Vector3 vectmp; + vectmp.X = vec->x; + vectmp.Y = vec->y; + vectmp.Z = vec->z; + vectmp = mtx->Rotate_Vector(vectmp); + vec->x = vectmp.X; + vec->y = vectmp.Y; + vec->z = vectmp.Z; + } +} + //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- @@ -80,6 +97,8 @@ MissileAIUpdateModuleData::MissileAIUpdateModuleData() m_distanceScatterWhenJammed = 75.0f; m_detonateCallsKill = FALSE; m_killSelfDelay = 3; // just long enough for the contrail to catch up to me + m_turnRateInitial = 0; + m_turnRateAttacking = BIGNUM; } //----------------------------------------------------------------------------- @@ -103,6 +122,9 @@ void MissileAIUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) { "DistanceToTravelOnRandomPath",INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_randomPathEndDistance ) }, { "RandomPathOffset",INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_randomPathOffset ) }, + { "InitialTurnRate", INI::parseAngularVelocityReal, NULL, offsetof(MissileAIUpdateModuleData, m_turnRateInitial) }, + { "AttackingTurnRate", INI::parseAngularVelocityReal, NULL, offsetof(MissileAIUpdateModuleData, m_turnRateAttacking) }, + { "GarrisonHitKillRequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillKindof ) }, { "GarrisonHitKillForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillKindofNot ) }, { "GarrisonHitKillCount", INI::parseUnsignedInt, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillCount ) }, @@ -180,6 +202,7 @@ void MissileAIUpdate::switchToState(MissileStateType s) { if (m_state != s) { + DEBUG_LOG((">>> MissileAI enter state %d. prev state = %d\n", s, m_state)); m_state = s; m_stateTimestamp = TheGameLogic->getFrame(); } @@ -525,15 +548,18 @@ void MissileAIUpdate::doAttackState(Bool turnOK, Bool randomPath) { if (curLoco) { - if (randomPath) { + if (randomPath) { //ATTACK_RANDOM_PATH state if (m_randomPathDistLeft <= 0) { - // Weare leaving randomPath state. Reestablish target. + // Weare leaving randomPath state. Re-establish target. Object* victim = TheGameLogic->findObjectByID(m_victimID); if (victim && d->m_tryToFollowTarget) { + DEBUG_LOG((">>> MissileAI - EndRandomPath: victim is not null.\n")); + getStateMachine()->setGoalPosition(victim->getPosition()); + getStateMachine()->setGoalObject(victim); aiMoveToObject(const_cast(victim), CMD_FROM_AI); m_originalTargetPos = *victim->getPosition(); m_isTrackingTarget = TRUE;// Remember that I was originally shot at a moving object, so if the @@ -542,6 +568,8 @@ void MissileAIUpdate::doAttackState(Bool turnOK, Bool randomPath) } else { + DEBUG_LOG((">>> MissileAI - EndRandomPath: victim is null.\n")); + // Otherwise, we are just a Coord shot. Coord3D initialPos = m_originalTargetPos; if (d->m_lockDistance > 0.0f) { @@ -556,7 +584,9 @@ void MissileAIUpdate::doAttackState(Bool turnOK, Bool randomPath) } else { curLoco->setMaxAcceleration(m_maxAccel); - curLoco->setMaxTurnRate(turnOK ? BIGNUM : 0); + // curLoco->setMaxTurnRate(turnOK ? BIGNUM : 0); + DEBUG_LOG((">>> MissileAI setMaxTurnRate = %f\n", turnOK ? d->m_turnRateAttacking : d->m_turnRateInitial)); + curLoco->setMaxTurnRate(turnOK ? d->m_turnRateAttacking : d->m_turnRateInitial); } } } @@ -581,6 +611,7 @@ void MissileAIUpdate::doAttackState(Bool turnOK, Bool randomPath) // Ground pos. Change to original goal. aiMoveToPosition(&m_originalTargetPos, CMD_FROM_AI ); } + // DEBUG_LOG((">>> MissileAI enter KILL state. prev state = %d\n", m_state)); switchToState(KILL); return; } @@ -600,36 +631,65 @@ void MissileAIUpdate::doAttackState(Bool turnOK, Bool randomPath) } - if (m_noTurnDistLeft <= 0.0f) + // Have we finished NOTURN? + if (m_noTurnDistLeft <= 0.0f && m_state == ATTACK_NOTURN) { - // We first reach random path state - if (d->m_randomPathEndDistance > 0.0 && !randomPath) { - m_randomPathDistLeft = d->m_randomPathEndDistance; + if (d->m_randomPathEndDistance > 0.0) { //!randomPath + // ----------------------------------- + // We first reach random path state + // ----------------------------------- + //m_randomPathDistLeft = d->m_randomPathEndDistance; // Pick a random position near the target as new goal, and forget tracking the target for now - Coord3D targetPosition; + Coord3D targetPos; if (m_isTrackingTarget && getGoalObject()) - targetPosition = *getGoalObject()->getPosition(); + targetPos = *getGoalObject()->getPosition(); else - targetPosition = *getGoalPosition(); + targetPos = *getGoalPosition(); + + // get halfway position + targetPos.add(getObject()->getPosition()); + targetPos.scale(0.5); + + // TODO: add flag or check for Z scattering + + // TODO: get polar offset (orient to object?) + Vector3 objPos(getObject()->getPosition()->x, getObject()->getPosition()->y, getObject()->getPosition()->z); + Vector3 curDir(targetPos.x - objPos.X, targetPos.y - objPos.Y, targetPos.y - objPos.Y); + m_randomPathDistLeft = curDir.Length() * 0.5; + + DEBUG_LOG((">>> MissileAI - StartRandomPath: m_randomPathDistLeft = %f\n", m_randomPathDistLeft)); + + curDir.Normalize(); // buildTransformMatrix wants it this way + Matrix3D mtx; + mtx.buildTransformMatrix(objPos, curDir); + + // Real scatter = d->m_randomPathOffset; + Coord3D offset = { + // 0, 0, 100.0f + GameLogicRandomValue(-scatter, scatter), + GameLogicRandomValue(-scatter, scatter), + GameLogicRandomValue(0, scatter * 0.5) + }; + adjustVector(&offset, &mtx); + + targetPos.add(&offset); - //TODO, make it spherical or cylindrical? - Real scatter = d->m_randomPathOffset; - targetPosition.x += GameLogicRandomValue(-scatter, scatter); - targetPosition.y += GameLogicRandomValue(-scatter, scatter); - targetPosition.z += GameLogicRandomValue(-scatter*0.5, scatter*0.5); + getStateMachine()->setGoalPosition(&targetPos); getStateMachine()->setGoalObject(NULL); - aiMoveToPosition(&targetPosition, CMD_FROM_AI); + aiMoveToPosition(&targetPos, CMD_FROM_AI); m_isTrackingTarget = FALSE; Locomotor* curLoco = getCurLocomotor(); if (curLoco) { curLoco->setMaxAcceleration(m_maxAccel); - curLoco->setMaxTurnRate(50.0f); // TODO + curLoco->setMaxTurnRate(d->m_turnRateAttacking); // TODO: Extra turnrate value? + DEBUG_LOG((">>> MissileAI setMaxTurnRate = %f\n", d->m_turnRateAttacking)); } switchToState(ATTACK_RANDOM_PATH); + // ----------------------------------- } else { switchToState(ATTACK); @@ -668,8 +728,10 @@ void MissileAIUpdate::doKillState(void) if (curLoco) { + const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData(); curLoco->setMaxAcceleration(m_maxAccel); - curLoco->setMaxTurnRate(BIGNUM); + curLoco->setMaxTurnRate(d->m_turnRateAttacking * 2.0f); + DEBUG_LOG((">>> MissileAI (killState) setMaxTurnRate = %f\n", d->m_turnRateAttacking * 2.0f)); } if (isIdle()) { // we finished the move @@ -784,7 +846,7 @@ UpdateSleepTime MissileAIUpdate::update() break; case ATTACK_RANDOM_PATH: - doAttackState(false, true); + doAttackState(true, true); break; case ATTACK: From 8d101caa86bb4e5b5445f905e8f1bb1353ab8983 Mon Sep 17 00:00:00 2001 From: Andi Date: Sat, 31 May 2025 17:01:54 +0200 Subject: [PATCH 06/42] cleanup --- .../GameLogic/Module/MissileAIUpdate.h | 9 +- .../Source/GameLogic/Object/Locomotor.cpp | 22 ++++ .../Update/AIUpdate/MissileAIUpdate.cpp | 100 +++++++++++------- 3 files changed, 87 insertions(+), 44 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h index 9d6776d661e..f2dae4d19d7 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h @@ -65,11 +65,12 @@ class MissileAIUpdateModuleData : public AIUpdateModuleData Bool m_detonateCallsKill; ///< if true, kill() will be called, instead of KILL_SELF state, which calls destroy. Int m_killSelfDelay; ///< If I have detonated and entered the KILL-SELF state, how ling do I wait before I Kill/destroy self? - Real m_turnRateAttacking; - Real m_turnRateInitial; + // Real m_turnRateAttacking; ///< Turn rate of the missile after ignition and no-turn stage + // Real m_turnRateInitial; ///< Turn rate of the missile during no-turn stage - Real m_randomPathEndDistance; - Real m_randomPathOffset; + Real m_zDirFactor; ///< Z correction factor for AA weapons with no pitch + + Real m_randomPathOffset; ///< Max distance to scatter for random path offset MissileAIUpdateModuleData(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp index 984973e1b11..c7db2121230 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp @@ -1136,6 +1136,9 @@ void Locomotor::locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalP dx *= dist; dy *= dist; dz *= dist; + + // DEBUG_LOG((">>> Locomotor Braking - d(xyz) = %f / %f / %f\n", dx * vel, dy * vel, dz * vel)); + pos.x += dx * vel; pos.y += dy * vel; pos.z += dz * vel; @@ -1981,6 +1984,25 @@ void Locomotor::moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, Bool adjust = true; if( obj->getStatusBits().test( OBJECT_STATUS_BRAKING ) ) { + //Real closeInDist = 150.0f; // TODO: get/set this from missileAI? + //Real af = 1.0f - __min((onPathDistToGoal / closeInDist), 1.0); + + //if (af > 0.0f) { + + // vel.Set( + // vel.X * (1.0f - af) + (goalPos.x - pos.x) * af, + // vel.Y * (1.0f - af) + (goalPos.y - pos.y) * af, + // vel.Z * (1.0f - af) + (goalPos.z - pos.z) * af + // ); + // if (isNearlyZero(sqr(vel.X) + sqr(vel.Y) + sqr(vel.Z))) { + // // we are at target. + // adjust = false; + // } + // maxTurnRate = (1.0f + (af * 2.0f) ) * maxTurnRate; + //} + + // DEBUG_LOG((">>> moveTowardsPositionThrust - Braking - maxTurnRate = %f\n", maxTurnRate)); + // align to target, cause that's where we're going anyway. vel.Set(goalPos.x - pos.x, goalPos.y-pos.y, goalPos.z-pos.z); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp index 7b64f06fe03..16bacd904be 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp @@ -97,8 +97,9 @@ MissileAIUpdateModuleData::MissileAIUpdateModuleData() m_distanceScatterWhenJammed = 75.0f; m_detonateCallsKill = FALSE; m_killSelfDelay = 3; // just long enough for the contrail to catch up to me - m_turnRateInitial = 0; - m_turnRateAttacking = BIGNUM; + // m_turnRateInitial = 0; + // m_turnRateAttacking = BIGNUM; + m_zDirFactor = 2.0f; } //----------------------------------------------------------------------------- @@ -119,19 +120,22 @@ void MissileAIUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) { "UseWeaponSpeed", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_useWeaponSpeed ) }, { "DetonateOnNoFuel", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_detonateOnNoFuel ) }, { "DistanceScatterWhenJammed",INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_distanceScatterWhenJammed ) }, - { "DistanceToTravelOnRandomPath",INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_randomPathEndDistance ) }, + { "RandomPathOffset",INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_randomPathOffset ) }, - { "InitialTurnRate", INI::parseAngularVelocityReal, NULL, offsetof(MissileAIUpdateModuleData, m_turnRateInitial) }, - { "AttackingTurnRate", INI::parseAngularVelocityReal, NULL, offsetof(MissileAIUpdateModuleData, m_turnRateAttacking) }, + // Note (AW): these values don't really do much, MaxThrustAngle in locomotor handles the movement. + // { "InitialTurnRate", INI::parseAngularVelocityReal, NULL, offsetof(MissileAIUpdateModuleData, m_turnRateInitial) }, + // { "AttackingTurnRate", INI::parseAngularVelocityReal, NULL, offsetof(MissileAIUpdateModuleData, m_turnRateAttacking) }, { "GarrisonHitKillRequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillKindof ) }, { "GarrisonHitKillForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillKindofNot ) }, { "GarrisonHitKillCount", INI::parseUnsignedInt, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillCount ) }, { "GarrisonHitKillFX", INI::parseFXList, NULL, offsetof( MissileAIUpdateModuleData, m_garrisonHitKillFX ) }, - { "DetonateCallsKill", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_detonateCallsKill ) }, - { "KillSelfDelay", INI::parseDurationUnsignedInt, NULL, offsetof( MissileAIUpdateModuleData, m_killSelfDelay ) }, - { 0, 0, 0, 0 } + { "DetonateCallsKill", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_detonateCallsKill ) }, + { "KillSelfDelay", INI::parseDurationUnsignedInt, NULL, offsetof( MissileAIUpdateModuleData, m_killSelfDelay ) }, + { "ZCorrectionFactor", INI::parseReal, NULL, offsetof(MissileAIUpdateModuleData, m_zDirFactor) }, + + { 0, 0, 0, 0 } }; p.add(dataFieldParse); @@ -202,7 +206,7 @@ void MissileAIUpdate::switchToState(MissileStateType s) { if (m_state != s) { - DEBUG_LOG((">>> MissileAI enter state %d. prev state = %d\n", s, m_state)); + // DEBUG_LOG((">>> MissileAI enter state %d. prev state = %d\n", s, m_state)); m_state = s; m_stateTimestamp = TheGameLogic->getFrame(); } @@ -260,21 +264,30 @@ void MissileAIUpdate::projectileFireAtObjectOrPosition( const Object *victim, co } } - Real deltaZ = victimPos->z - obj->getPosition()->z; - Real dx = victimPos->x - obj->getPosition()->x; - Real dy = victimPos->y - obj->getPosition()->y; - Real xyDist = sqrt(sqr(dx)+sqr(dy)); - if (xyDist<1) xyDist = 1; - Real zFactor = 0; - if (deltaZ>0) { - zFactor = deltaZ/xyDist; + Vector3 dir; + + if (d->m_zDirFactor > 0) { + Real deltaZ = victimPos->z - obj->getPosition()->z; + Real dx = victimPos->x - obj->getPosition()->x; + Real dy = victimPos->y - obj->getPosition()->y; + Real xyDist = sqrt(sqr(dx) + sqr(dy)); + if (xyDist < 1) xyDist = 1; + Real zFactor = 0; + if (deltaZ > 0) { + zFactor = deltaZ / xyDist; + } + dir = getObject()->getTransformMatrix()->Get_X_Vector(); + dir.Normalize(); + dir.Z += d->m_zDirFactor * zFactor; + dir.Normalize(); + } + else { + dir = getObject()->getTransformMatrix()->Get_X_Vector(); + dir.Normalize(); } - - Vector3 dir = getObject()->getTransformMatrix()->Get_X_Vector(); - dir.Normalize(); - dir.Z += 2*zFactor; - dir.Normalize(); + DEBUG_LOG((">>> MissileAI FIREPROJ - dir = (%f/%f/%f)\n", dir.X, dir.Y, dir.Z)); + PhysicsBehavior* physics = getObject()->getPhysics(); if (physics && initialVelToUse > 0) { @@ -286,6 +299,8 @@ void MissileAIUpdate::projectileFireAtObjectOrPosition( const Object *victim, co force.z = forceMag * dir.Z; physics->applyMotiveForce( &force ); + + DEBUG_LOG((">>> MissileAI FIREPROJ - force = (%f/%f/%f)\n", force.x, force.y, force.z)); } Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z); @@ -320,7 +335,7 @@ void MissileAIUpdate::projectileFireAtObjectOrPosition( const Object *victim, co m_victimID = INVALID_ID; } - setCurrentVictim( victim );/// extending access to the victim via the parent class + setCurrentVictim( victim );/// extending access to the victim via the parent class m_prevPos = *getObject()->getPosition(); } @@ -584,9 +599,9 @@ void MissileAIUpdate::doAttackState(Bool turnOK, Bool randomPath) } else { curLoco->setMaxAcceleration(m_maxAccel); - // curLoco->setMaxTurnRate(turnOK ? BIGNUM : 0); - DEBUG_LOG((">>> MissileAI setMaxTurnRate = %f\n", turnOK ? d->m_turnRateAttacking : d->m_turnRateInitial)); - curLoco->setMaxTurnRate(turnOK ? d->m_turnRateAttacking : d->m_turnRateInitial); + curLoco->setMaxTurnRate(turnOK ? BIGNUM : 0); + // DEBUG_LOG((">>> MissileAI setMaxTurnRate = %f\n", turnOK ? d->m_turnRateAttacking : d->m_turnRateInitial)); + // curLoco->setMaxTurnRate(turnOK ? d->m_turnRateAttacking : d->m_turnRateInitial); } } } @@ -624,9 +639,12 @@ void MissileAIUpdate::doAttackState(Bool turnOK, Bool randomPath) Real distanceToTargetSquared = ThePartitionManager->getDistanceSquared( getObject(), getGoalPosition(), FROM_CENTER_2D ); Real diveDistanceSquared = d->m_diveDistance; if (curLoco && curLoco->getPreferredHeight()) { - diveDistanceSquared *= diveDistanceSquared; - if( distanceToTargetSquared < diveDistanceSquared ) - curLoco->setUsePreciseZPos( true ); + diveDistanceSquared *= diveDistanceSquared; + if (distanceToTargetSquared < diveDistanceSquared) { + curLoco->setUsePreciseZPos(true); + DEBUG_LOG((">>> MissileAI - AttackState - DIVE - distanceToTarget = %f\n", sqrt(distanceToTargetSquared))); + } + } } @@ -634,12 +652,10 @@ void MissileAIUpdate::doAttackState(Bool turnOK, Bool randomPath) // Have we finished NOTURN? if (m_noTurnDistLeft <= 0.0f && m_state == ATTACK_NOTURN) { - if (d->m_randomPathEndDistance > 0.0) { //!randomPath + if (d->m_randomPathOffset > 0.0) { // ----------------------------------- // We first reach random path state // ----------------------------------- - //m_randomPathDistLeft = d->m_randomPathEndDistance; - // Pick a random position near the target as new goal, and forget tracking the target for now Coord3D targetPos; if (m_isTrackingTarget && getGoalObject()) @@ -664,9 +680,8 @@ void MissileAIUpdate::doAttackState(Bool turnOK, Bool randomPath) Matrix3D mtx; mtx.buildTransformMatrix(objPos, curDir); - // Real scatter = d->m_randomPathOffset; + Real scatter = d->m_randomPathOffset; Coord3D offset = { - // 0, 0, 100.0f GameLogicRandomValue(-scatter, scatter), GameLogicRandomValue(-scatter, scatter), GameLogicRandomValue(0, scatter * 0.5) @@ -675,6 +690,11 @@ void MissileAIUpdate::doAttackState(Bool turnOK, Bool randomPath) targetPos.add(&offset); + // Make sure Z is above ground + PathfindLayerEnum layer = TheTerrainLogic->getHighestLayerForDestination(&targetPos); + Real minHeight = TheTerrainLogic->getLayerHeight(targetPos.x, targetPos.y, layer) + APPROACH_HEIGHT; + targetPos.z = __max(targetPos.z, minHeight); + getStateMachine()->setGoalPosition(&targetPos); getStateMachine()->setGoalObject(NULL); aiMoveToPosition(&targetPos, CMD_FROM_AI); @@ -684,8 +704,8 @@ void MissileAIUpdate::doAttackState(Bool turnOK, Bool randomPath) if (curLoco) { curLoco->setMaxAcceleration(m_maxAccel); - curLoco->setMaxTurnRate(d->m_turnRateAttacking); // TODO: Extra turnrate value? - DEBUG_LOG((">>> MissileAI setMaxTurnRate = %f\n", d->m_turnRateAttacking)); + // curLoco->setMaxTurnRate(d->m_turnRateAttacking); + curLoco->setMaxTurnRate(BIGNUM); } switchToState(ATTACK_RANDOM_PATH); @@ -728,10 +748,10 @@ void MissileAIUpdate::doKillState(void) if (curLoco) { - const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData(); + // const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData(); curLoco->setMaxAcceleration(m_maxAccel); - curLoco->setMaxTurnRate(d->m_turnRateAttacking * 2.0f); - DEBUG_LOG((">>> MissileAI (killState) setMaxTurnRate = %f\n", d->m_turnRateAttacking * 2.0f)); + // curLoco->setMaxTurnRate(__min(d->m_turnRateAttacking * 2.0f, BIGNUM)); + curLoco->setMaxTurnRate(BIGNUM); } if (isIdle()) { // we finished the move @@ -743,7 +763,7 @@ void MissileAIUpdate::doKillState(void) closeEnough = curLoco->getMaxSpeedForCondition(BODY_PRISTINE); } Real distanceToTargetSq = ThePartitionManager->getDistanceSquared( getObject(), getGoalObject(), FROM_BOUNDINGSPHERE_3D); - //DEBUG_LOG(("Distance to target %f, closeEnough %f\n", sqrt(distanceToTargetSq), closeEnough)); + // DEBUG_LOG((">>> MissileAI KILL (Idle) - Distance to target %f, closeEnough %f\n", sqrt(distanceToTargetSq), closeEnough)); if (distanceToTargetSq < closeEnough*closeEnough) { Coord3D pos = *getGoalObject()->getPosition(); getObject()->setPosition(&pos); From cdcfc764a9b9028e84af7ce41b95be1b1adf53a5 Mon Sep 17 00:00:00 2001 From: Andi Date: Sat, 31 May 2025 17:41:09 +0200 Subject: [PATCH 07/42] shellmap music (WIP) --- .../Source/GameClient/GUI/Shell/Shell.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp index 47039e76a59..def99ca4212 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp @@ -30,6 +30,8 @@ // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine +#include "Common/AudioEventRTS.h" +#include "Common/AudioHandleSpecialValues.h" #include "Common/RandomValue.h" #include "GameClient/Shell.h" #include "GameClient/WindowLayout.h" @@ -508,6 +510,18 @@ void Shell::showShellMap(Bool useShellMap ) top()->bringForward(); m_shellMapOn = FALSE; m_clearBackground = FALSE; + + // MUSIC + // TODO + //AsciiString musicName = "Shell"; + //if (!musicName.isEmpty()) + //{ + // TheAudio->removeAudioEvent(AHSV_StopTheMusicFade); + // AudioEventRTS event(musicName); + // event.setShouldFade(TRUE); + // TheAudio->addAudioEvent(&event); + // TheAudio->update();//Since GameEngine::update() is suspended until after I am gone... + //} } } From ba813eb8281803736c6673c823305b40c67bb556 Mon Sep 17 00:00:00 2001 From: andreasw Date: Tue, 3 Jun 2025 13:27:16 +0200 Subject: [PATCH 08/42] Fixed Shadows not appearing --- .../W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp index 4aee7f97721..3cb2a0cb79d 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp @@ -1866,8 +1866,9 @@ void W3DModelDraw::allocateShadows(void) const ThingTemplate *tmplate=getDrawable()->getTemplate(); //Check if we don't already have a shadow but need one for this type of model. - if (m_shadow == NULL && m_renderObject && TheW3DShadowManager && tmplate->getShadowType() != SHADOW_NONE - && m_isFirstDrawModule) + ShadowType type = tmplate->getShadowType(); + if (m_shadow == NULL && m_renderObject && TheW3DShadowManager && type != SHADOW_NONE + && (m_isFirstDrawModule || !(type == SHADOW_DECAL || type == SHADOW_ALPHA_DECAL || type == SHADOW_ADDITIVE_DECAL))) { Shadow::ShadowTypeInfo shadowInfo; strcpy(shadowInfo.m_ShadowName, tmplate->getShadowTextureName().str()); @@ -3078,7 +3079,9 @@ void W3DModelDraw::setModelState(const ModelConditionInfo* newState) } // set up shadows - if (m_renderObject && TheW3DShadowManager && tmplate->getShadowType() != SHADOW_NONE && m_isFirstDrawModule) + ShadowType type = tmplate->getShadowType(); + if (m_renderObject && TheW3DShadowManager && type != SHADOW_NONE && + (m_isFirstDrawModule || !(type == SHADOW_DECAL || type == SHADOW_ALPHA_DECAL || type == SHADOW_ADDITIVE_DECAL))) { Shadow::ShadowTypeInfo shadowInfo; strcpy(shadowInfo.m_ShadowName, tmplate->getShadowTextureName().str()); From 59dab2e937c53043e9f08971b09160e6d7404faa Mon Sep 17 00:00:00 2001 From: andreasw Date: Tue, 3 Jun 2025 19:41:49 +0200 Subject: [PATCH 09/42] projectile weaponbonus fixes --- .../Include/GameLogic/Module/DumbProjectileBehavior.h | 2 +- .../Include/GameLogic/Module/MissileAIUpdate.h | 2 ++ .../Include/GameLogic/Module/ParkingPlaceBehavior.h | 5 ++++- .../GameLogic/Object/Behavior/DumbProjectileBehavior.cpp | 9 ++++++++- .../GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp | 7 ++++++- 5 files changed, 21 insertions(+), 4 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DumbProjectileBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DumbProjectileBehavior.h index 88998889e7a..1adc2ca9954 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DumbProjectileBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DumbProjectileBehavior.h @@ -65,7 +65,7 @@ class DumbProjectileBehaviorModuleData : public UpdateModuleData KindOfMaskType m_garrisonHitKillKindofNot; ///< the kind(s) of units that CANNOT be collided with const FXList* m_garrisonHitKillFX; Real m_flightPathAdjustDistPerFrame; - + Bool m_applyLauncherBonus; DumbProjectileBehaviorModuleData(); diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h index f2dae4d19d7..43fb44e628c 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h @@ -72,6 +72,8 @@ class MissileAIUpdateModuleData : public AIUpdateModuleData Real m_randomPathOffset; ///< Max distance to scatter for random path offset + Bool m_applyLauncherBonus; ///< Apply the launcher's weapon bonus flags (for any non-detonate triggered weapon) + MissileAIUpdateModuleData(); static void buildFieldParse(MultiIniFieldParse& p); diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h index 8def59d5eb7..5fdbe922322 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h @@ -50,6 +50,7 @@ class ParkingPlaceBehaviorModuleData : public UpdateModuleData Real m_landingDeckHeightOffset; Bool m_hasRunways; // if true, each col has a runway in front of it Bool m_parkInHangars; // if true, park at the hangar production spot, not the "real" parking place + // Real m_damageScalar; // Damage reduction for parked aircraft ParkingPlaceBehaviorModuleData() { @@ -62,6 +63,7 @@ class ParkingPlaceBehaviorModuleData : public UpdateModuleData m_landingDeckHeightOffset = 0.0f; m_hasRunways = false; m_parkInHangars = false; + //m_damageScalar = 1.0f; } static void buildFieldParse(MultiIniFieldParse& p) @@ -78,7 +80,8 @@ class ParkingPlaceBehaviorModuleData : public UpdateModuleData { "ParkInHangars", INI::parseBool, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_parkInHangars ) }, { "HealAmountPerSecond", INI::parseReal, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_healAmount ) }, // { "ExtraHealAmount4Helicopters", INI::parseReal, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_extraHealAmount4Helicopters ) }, - + // { "ParkedUnitsDamageScalar", INI::parseReal, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalar) }, + // { "ParkedUnitsDamageScalarUpgraded", INI::parseReal, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalarUpgraded) }, //{ "TimeForFullHeal", INI::parseDurationUnsignedInt, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_framesForFullHeal ) }, diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp index 91de76b64dc..e18a38bbc61 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp @@ -71,7 +71,8 @@ DumbProjectileBehaviorModuleData::DumbProjectileBehaviorModuleData() : m_secondPercentIndent(0.0f), m_garrisonHitKillCount(0), m_garrisonHitKillFX(NULL), - m_flightPathAdjustDistPerFrame(0.0f) + m_flightPathAdjustDistPerFrame(0.0f), + m_applyLauncherBonus(FALSE) { } @@ -99,6 +100,7 @@ void DumbProjectileBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p) { "FlightPathAdjustDistPerSecond", INI::parseVelocityReal, NULL, offsetof( DumbProjectileBehaviorModuleData, m_flightPathAdjustDistPerFrame ) }, + { "ApplyLauncherBonus", INI::parseBool, NULL, offsetof(DumbProjectileBehaviorModuleData, m_applyLauncherBonus) }, { 0, 0, 0, 0 } }; @@ -341,6 +343,11 @@ void DumbProjectileBehavior::projectileLaunchAtObjectOrPosition( m_launcherID = launcher ? launcher->getID() : INVALID_ID; m_extraBonusFlags = launcher ? launcher->getWeaponBonusCondition() : 0; + + if (d->m_applyLauncherBonus && m_extraBonusFlags != 0) { + getObject()->setWeaponBonusConditionFlags(m_extraBonusFlags); + } + m_victimID = victim ? victim->getID() : INVALID_ID; m_detonationWeaponTmpl = detWeap; m_lifespanFrame = TheGameLogic->getFrame() + d->m_maxLifespan; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp index 16bacd904be..4c1a2959d1e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/MissileAIUpdate.cpp @@ -100,6 +100,7 @@ MissileAIUpdateModuleData::MissileAIUpdateModuleData() // m_turnRateInitial = 0; // m_turnRateAttacking = BIGNUM; m_zDirFactor = 2.0f; + m_applyLauncherBonus = FALSE; } //----------------------------------------------------------------------------- @@ -134,7 +135,7 @@ void MissileAIUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) { "DetonateCallsKill", INI::parseBool, NULL, offsetof( MissileAIUpdateModuleData, m_detonateCallsKill ) }, { "KillSelfDelay", INI::parseDurationUnsignedInt, NULL, offsetof( MissileAIUpdateModuleData, m_killSelfDelay ) }, { "ZCorrectionFactor", INI::parseReal, NULL, offsetof(MissileAIUpdateModuleData, m_zDirFactor) }, - + { "ApplyLauncherBonus", INI::parseBool, NULL, offsetof(MissileAIUpdateModuleData, m_applyLauncherBonus) }, { 0, 0, 0, 0 } }; @@ -231,6 +232,10 @@ void MissileAIUpdate::projectileLaunchAtObjectOrPosition( m_detonationWeaponTmpl = detWeap; m_extraBonusFlags = launcher ? launcher->getWeaponBonusCondition() : 0; + if (getMissileAIUpdateModuleData()->m_applyLauncherBonus && m_extraBonusFlags != 0) { + getObject()->setWeaponBonusConditionFlags(m_extraBonusFlags); + } + Weapon::positionProjectileForLaunch(getObject(), launcher, wslot, specificBarrelToUse); projectileFireAtObjectOrPosition( victim, victimPos, detWeap, exhaustSysOverride ); From b0b94c6c724b311ca4a49d1c0647e78668d9bec2 Mon Sep 17 00:00:00 2001 From: andreasw Date: Wed, 4 Jun 2025 15:23:42 +0200 Subject: [PATCH 10/42] Allow setting None flags for weaponsetupgrade --- GeneralsMD/Code/GameEngine/Include/Common/INI.h | 1 + .../GameEngine/Include/GameLogic/WeaponSetType.h | 1 + .../Code/GameEngine/Source/Common/INI/INI.cpp | 16 ++++++++++++++++ .../Object/Upgrade/WeaponSetUpgrade.cpp | 6 ++++-- 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/INI.h b/GeneralsMD/Code/GameEngine/Include/Common/INI.h index 8d5c22b4bf0..b23662d275b 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/INI.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/INI.h @@ -299,6 +299,7 @@ class INI static void parseBitString32( INI *ini, void *instance, void *store, const void* userData ); static void parseByteSizedIndexList( INI *ini, void *instance, void *store, const void* userData ); static void parseIndexList( INI *ini, void *instance, void *store, const void* userData ); + static void parseIndexListOrNone( INI *ini, void *instance, void *store, const void* userData ); static void parseLookupList( INI *ini, void *instance, void *store, const void* userData ); static void parseThingTemplate( INI *ini, void *instance, void *store, const void* userData ); static void parseArmorTemplate( INI *ini, void *instance, void *store, const void* userData ); diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/WeaponSetType.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/WeaponSetType.h index 34fa3f18615..706853f3204 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/WeaponSetType.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/WeaponSetType.h @@ -40,6 +40,7 @@ // enum WeaponSetType CPP_11(: Int) { + WEAPONSET_NONE = -1, // The access and use of this enum has the bit shifting built in, so this is a 0,1,2,3,4,5 enum WEAPONSET_VETERAN = 0, WEAPONSET_ELITE, diff --git a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp index 10d236cb77b..f83ba505995 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp @@ -1470,6 +1470,22 @@ void INI::parseIndexList( INI* ini, void * /*instance*/, void *store, const void *(Int *)store = scanIndexList(ini->getNextToken(), nameList); } +//------------------------------------------------------------------------------------------------- +/** returns -1 if "None", otherwise like parseIndexList **/ +//------------------------------------------------------------------------------------------------- +void INI::parseIndexListOrNone(INI* ini, void* /*instance*/, void* store, const void* userData) +{ + const char* token = ini->getNextToken(); + if (stricmp(token, "None") == 0) { + *(Int*)store = -1; + } + else { + //like parseIndexList + ConstCharPtrArray nameList = (ConstCharPtrArray)userData; + *(Int*)store = scanIndexList(token, nameList); + } +} + //------------------------------------------------------------------------------------------------- /** Parse a single string token, check for that token in the index list * of names provided and store the index into that list. diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/WeaponSetUpgrade.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/WeaponSetUpgrade.cpp index a2a68a5ca6b..f8ca1c54253 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/WeaponSetUpgrade.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/WeaponSetUpgrade.cpp @@ -54,7 +54,7 @@ void WeaponSetUpgradeModuleData::buildFieldParse(MultiIniFieldParse& p) static const FieldParse dataFieldParse[] = { - { "WeaponSetFlag", INI::parseIndexList, WeaponSetFlags::getBitNames(),offsetof(WeaponSetUpgradeModuleData, m_weaponSetFlag) }, + { "WeaponSetFlag", INI::parseIndexListOrNone, WeaponSetFlags::getBitNames(),offsetof(WeaponSetUpgradeModuleData, m_weaponSetFlag) }, { "WeaponSetFlagsToClear", WeaponSetFlags::parseFromINI, NULL, offsetof(WeaponSetUpgradeModuleData, m_weaponSetFlagsToClear) }, { "NeedsParkedAircraft", INI::parseBool, NULL, offsetof(WeaponSetUpgradeModuleData, m_needsParkedAircraft) }, { 0, 0, 0, 0 } @@ -112,7 +112,9 @@ void WeaponSetUpgrade::upgradeImplementation( ) const WeaponSetUpgradeModuleData* data = getWeaponSetUpgradeModuleData(); Object *obj = getObject(); - obj->setWeaponSetFlag(data->m_weaponSetFlag); + if (data->m_weaponSetFlag > WEAPONSET_NONE) { + obj->setWeaponSetFlag(data->m_weaponSetFlag); + } /*DEBUG_LOG((">>> WSU: m_weaponSetFlagsToClear = %d\n", data->m_weaponSetFlag));*/ From 65e3621b41a0a6802587884017aacdea3eddb72c Mon Sep 17 00:00:00 2001 From: andreasw Date: Wed, 4 Jun 2025 20:11:03 +0200 Subject: [PATCH 11/42] Add Damage Scalar and Kindofs to ParkingPlaceBehavior --- .../GameLogic/Module/ParkingPlaceBehavior.h | 26 +++- .../Behavior/GenerateMinefieldBehavior.cpp | 5 +- .../Object/Behavior/ParkingPlaceBehavior.cpp | 114 +++++++++++++++++- 3 files changed, 138 insertions(+), 7 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h index 5fdbe922322..9130b626aba 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h @@ -50,10 +50,16 @@ class ParkingPlaceBehaviorModuleData : public UpdateModuleData Real m_landingDeckHeightOffset; Bool m_hasRunways; // if true, each col has a runway in front of it Bool m_parkInHangars; // if true, park at the hangar production spot, not the "real" parking place - // Real m_damageScalar; // Damage reduction for parked aircraft + Real m_damageScalar; // Damage reduction for parked aircraft + Real m_damageScalarUpgraded; // Damage reduction for parked aircraft + AsciiString m_damageScalarUpgradeTrigger; // Upgrade template for damageScalar upgrade + + KindOfMaskType m_kindof; ///< the kind(s) of units that can land here + KindOfMaskType m_kindofnot; ///< the kind(s) of units that must not land here ParkingPlaceBehaviorModuleData() { + m_damageScalarUpgradeTrigger.clear(); //m_framesForFullHeal = 0; m_healAmount = 0; // m_extraHealAmount4Helicopters = 0; @@ -63,7 +69,8 @@ class ParkingPlaceBehaviorModuleData : public UpdateModuleData m_landingDeckHeightOffset = 0.0f; m_hasRunways = false; m_parkInHangars = false; - //m_damageScalar = 1.0f; + m_damageScalar = 1.0f; + m_damageScalarUpgraded = 1.0f; } static void buildFieldParse(MultiIniFieldParse& p) @@ -80,9 +87,13 @@ class ParkingPlaceBehaviorModuleData : public UpdateModuleData { "ParkInHangars", INI::parseBool, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_parkInHangars ) }, { "HealAmountPerSecond", INI::parseReal, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_healAmount ) }, // { "ExtraHealAmount4Helicopters", INI::parseReal, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_extraHealAmount4Helicopters ) }, - // { "ParkedUnitsDamageScalar", INI::parseReal, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalar) }, - // { "ParkedUnitsDamageScalarUpgraded", INI::parseReal, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalarUpgraded) }, + { "ParkedUnitsDamageScalar", INI::parseReal, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalar) }, + { "ParkedUnitsDamageScalarUpgraded", INI::parseReal, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalarUpgraded) }, + { "DamageScalarUpgradedTriggeredBy", INI::parseAsciiString, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalarUpgradeTrigger) }, + { "DamageScalarUpgradedTriggeredBy", INI::parseAsciiString, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalarUpgradeTrigger) }, + { "RequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_kindof) }, + { "ForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_kindofnot) }, //{ "TimeForFullHeal", INI::parseDurationUnsignedInt, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_framesForFullHeal ) }, { 0, 0, 0, 0 } @@ -213,8 +224,15 @@ class ParkingPlaceBehavior : public UpdateModule, ParkingPlaceInfo* findPPI(ObjectID id); ParkingPlaceInfo* findEmptyPPI(); + void applyDamageScalar(Object* obj, Real scalarNew, Real scalarOld = 1.0f); + void removeDamageScalar(Object* obj, Real scalar); + Real getDamageScalar(); + void updateDamageScalars(); + Coord3D m_heliRallyPoint; Bool m_heliRallyPointExists; ///< Only move to the rally point if this is true + + Bool m_damageScalarUpgradeApplied; }; #endif // __ParkingPlaceBehavior_H_ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp index e3477ea5dc2..3e2afdbd59b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp @@ -465,8 +465,11 @@ UpdateSleepTime GenerateMinefieldBehavior::update() { if (m_generated) { + const GenerateMinefieldBehaviorModuleData* d = getGenerateMinefieldBehaviorModuleData(); // Upgraded minefield to next level for China Player - const UpgradeTemplate *upgradeTemplate = TheUpgradeCenter->findUpgrade( "Upgrade_ChinaEMPMines" ); + // + // const UpgradeTemplate *upgradeTemplate = TheUpgradeCenter->findUpgrade( "Upgrade_ChinaEMPMines" ); + const UpgradeTemplate *upgradeTemplate = TheUpgradeCenter->findUpgrade( d->m_mineUpgradeTrigger ); if (upgradeTemplate) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/ParkingPlaceBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/ParkingPlaceBehavior.cpp index e2a0990b2d3..8f1f09627d2 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/ParkingPlaceBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/ParkingPlaceBehavior.cpp @@ -32,6 +32,8 @@ #include "Common/CRCDebug.h" #include "Common/Xfer.h" #include "Common/ThingTemplate.h" +#include "Common/Player.h" +#include "Common/KindOf.h" #include "GameClient/Drawable.h" #include "GameLogic/AI.h" #include "GameLogic/AIPathfind.h" @@ -302,10 +304,14 @@ Bool ParkingPlaceBehavior::hasAvailableSpaceFor(const ThingTemplate* thing) cons { if (!m_gotInfo) // degenerate case, shouldn't happen, but just in case... return false; - + if (thing->isKindOf(KINDOF_PRODUCED_AT_HELIPAD)) return true; + const ParkingPlaceBehaviorModuleData* d = getParkingPlaceBehaviorModuleData(); + if (d && !thing->isKindOfMulti(d->m_kindof, d->m_kindofnot)) + return FALSE; + for (std::vector::const_iterator it = m_spaces.begin(); it != m_spaces.end(); ++it) { ObjectID id = it->m_objectInSpace; @@ -337,6 +343,11 @@ Bool ParkingPlaceBehavior::reserveSpace(ObjectID id, Real parkingOffset, Parking const ParkingPlaceBehaviorModuleData* d = getParkingPlaceBehaviorModuleData(); + // Check Valid Kindof + Object* obj = TheGameLogic->findObjectByID(id); + if (d && !obj->getTemplate()->isKindOfMulti(d->m_kindof, d->m_kindofnot)) + return FALSE; + ParkingPlaceInfo* ppi = findPPI(id); if (ppi == NULL) { @@ -562,6 +573,70 @@ void ParkingPlaceBehavior::resetWakeFrame() } } +//------------------------------------------------------------------------------------------------- +void ParkingPlaceBehavior::applyDamageScalar(Object* obj, Real scalarNew, Real scalarOld) +{ + BodyModuleInterface* body = obj->getBodyModule(); + + //If we have a scalar already, remove it + if (scalarOld != 1.0) { + body->applyDamageScalar(1.0f / __max(scalarOld, 0.01f)); + } + + // DEBUG_LOG((">>>ParkingPlaceBehavior: removeOldScalar '%f' from obj '%s' - new scalar = '%f' \n", + // scalarOld, obj->getTemplate()->getName().str(), body->getDamageScalar())); + + //apply new scalar + body->applyDamageScalar(__max(scalarNew, 0.01f)); + + // DEBUG_LOG((">>>ParkingPlaceBehavior: applyDamageScalar '%f' to obj '%s' - new scalar = '%f' \n", + // scalarNew, obj->getTemplate()->getName().str(), body->getDamageScalar())); +} + +//------------------------------------------------------------------------------------------------- +void ParkingPlaceBehavior::removeDamageScalar(Object* obj, Real scalar) +{ + BodyModuleInterface* body = obj->getBodyModule(); + body->applyDamageScalar(1.0f / __max(scalar, 0.01f)); + + // DEBUG_LOG((">>>ParkingPlaceBehavior: removeDamageScalar '%f' from obj '%s' - new scalar = '%f' \n", + // scalar, obj->getTemplate()->getName().str(), body->getDamageScalar())); +} + +//------------------------------------------------------------------------------------------------- +Real ParkingPlaceBehavior::getDamageScalar() +{ + const ParkingPlaceBehaviorModuleData * d = getParkingPlaceBehaviorModuleData(); + if (m_damageScalarUpgradeApplied) { + return d->m_damageScalarUpgraded; + } + else { + return d->m_damageScalar; + } +} + +//------------------------------------------------------------------------------------------------- +void ParkingPlaceBehavior::updateDamageScalars() { + const ParkingPlaceBehaviorModuleData * d = getParkingPlaceBehaviorModuleData(); + + Real scalarNew = d->m_damageScalarUpgraded; + Real scalarOld = d->m_damageScalar; + + for (std::list::iterator it = m_healing.begin(); it != m_healing.end(); ++it) + { + if (it->m_gettingHealedID != INVALID_ID) + { + Object* objToHeal = TheGameLogic->findObjectByID(it->m_gettingHealedID); + if (objToHeal != NULL && !objToHeal->isEffectivelyDead()) + { + applyDamageScalar(objToHeal, scalarNew, scalarOld); + } + } + } +} + + + //------------------------------------------------------------------------------------------------- void ParkingPlaceBehavior::setHealee(Object* healee, Bool add) { @@ -572,10 +647,17 @@ void ParkingPlaceBehavior::setHealee(Object* healee, Bool add) if (it->m_gettingHealedID == healee->getID()) return; } + HealingInfo info; info.m_gettingHealedID = healee->getID(); info.m_healStartFrame = TheGameLogic->getFrame(); m_healing.push_back(info); + + Real damageScalar = getDamageScalar(); + if (damageScalar != 1.0) { + applyDamageScalar(healee, damageScalar); + } + resetWakeFrame(); } else @@ -585,6 +667,11 @@ void ParkingPlaceBehavior::setHealee(Object* healee, Bool add) if (it->m_gettingHealedID == healee->getID()) { it = m_healing.erase(it); + + Real damageScalar = getDamageScalar(); + if (damageScalar != 1.0) { + removeDamageScalar(healee, damageScalar); + } resetWakeFrame(); } else @@ -679,11 +766,32 @@ UpdateSleepTime ParkingPlaceBehavior::update() buildInfo(); purgeDead(); + const ParkingPlaceBehaviorModuleData* d = getParkingPlaceBehaviorModuleData(); + + // Check if Damage Scalar is upgraded: + + if (!m_damageScalarUpgradeApplied) { + Player* player = getObject()->getControllingPlayer(); + const UpgradeTemplate* upgradeTemplate = TheUpgradeCenter->findUpgrade(d->m_damageScalarUpgradeTrigger); + + if (upgradeTemplate && player) + { + UpgradeMaskType upgradeMask = upgradeTemplate->getUpgradeMask(); + UpgradeMaskType objMask = getObject()->getObjectCompletedUpgradeMask(); + if (objMask.testForAny(upgradeMask) || player->hasUpgradeComplete(upgradeTemplate)) + { + DEBUG_LOG(("ParkingPlaceBehavior::update() - Apply Damage Scalar Upgrade!\n")); + m_damageScalarUpgradeApplied = TRUE; + updateDamageScalars(); + } + } + } + + UnsignedInt now = TheGameLogic->getFrame(); if (now >= m_nextHealFrame) { m_nextHealFrame = now + HEAL_RATE_FRAMES; - const ParkingPlaceBehaviorModuleData* d = getParkingPlaceBehaviorModuleData(); for (std::list::iterator it = m_healing.begin(); it != m_healing.end(); /*++it*/) { if (it->m_gettingHealedID != INVALID_ID) @@ -1083,6 +1191,8 @@ void ParkingPlaceBehavior::xfer( Xfer *xfer ) } } + xfer->xferBool(&m_damageScalarUpgradeApplied); + } // end xfer // ------------------------------------------------------------------------------------------------ From e96c18aefd98f54d2d8bfaa13b0173cb2e075f14 Mon Sep 17 00:00:00 2001 From: andreasw Date: Thu, 5 Jun 2025 14:21:07 +0200 Subject: [PATCH 12/42] fix duplicated line in parkingplacebehavior --- .../GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h | 1 - 1 file changed, 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h index 9130b626aba..37d6a164bbe 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h @@ -90,7 +90,6 @@ class ParkingPlaceBehaviorModuleData : public UpdateModuleData { "ParkedUnitsDamageScalar", INI::parseReal, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalar) }, { "ParkedUnitsDamageScalarUpgraded", INI::parseReal, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalarUpgraded) }, { "DamageScalarUpgradedTriggeredBy", INI::parseAsciiString, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalarUpgradeTrigger) }, - { "DamageScalarUpgradedTriggeredBy", INI::parseAsciiString, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalarUpgradeTrigger) }, { "RequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_kindof) }, { "ForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_kindofnot) }, From 2f0b3f744770ab0eb2123ad12fb9492d3a6b1141 Mon Sep 17 00:00:00 2001 From: andreasw Date: Thu, 5 Jun 2025 18:08:36 +0200 Subject: [PATCH 13/42] Improve CostModifierUpgrade --- .../Code/GameEngine/Include/Common/Player.h | 15 ++++-- .../GameLogic/Module/CostModifierUpgrade.h | 16 ++++++ .../GameEngine/Source/Common/RTS/Player.cpp | 51 ++++++++++++++----- .../Object/Upgrade/CostModifierUpgrade.cpp | 46 ++++++++++++++--- 4 files changed, 104 insertions(+), 24 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Player.h b/GeneralsMD/Code/GameEngine/Include/Common/Player.h index 05179cd2a37..881d0a906ee 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Player.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Player.h @@ -124,8 +124,11 @@ class KindOfPercentProductionChange : public MemoryPoolObject MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(KindOfPercentProductionChange, "KindOfPercentProductionChange") public: KindOfMaskType m_kindOf; - Real m_percent; - UnsignedInt m_ref; + Real m_percent; + UnsignedInt m_ref; // Counter + Bool m_stackWithAny; // this entry can stack with any of same values + UnsignedInt m_templateID; // Bonus Source thingtemplate + }; EMPTY_DTOR(KindOfPercentProductionChange) @@ -380,9 +383,13 @@ class Player : public Snapshot void friend_applyDifficultyBonusesForObject(Object* obj, Bool apply) const; /// Decrement the ref counter on the typeof production list node - void removeKindOfProductionCostChange(KindOfMaskType kindOf, Real percent); + void removeKindOfProductionCostChange(KindOfMaskType kindOf, Real percent, + UnsignedInt sourceTemplateID = INVALID_ID, + Bool stackUniqueType = FALSE, Bool stackWithAny = FALSE); /// add type of production cost change (Used for upgrades) - void addKindOfProductionCostChange( KindOfMaskType kindOf, Real percent); + void addKindOfProductionCostChange( KindOfMaskType kindOf, Real percent, + UnsignedInt sourceTemplateID = INVALID_ID, + Bool stackUniqueType = FALSE, Bool stackWithAny = FALSE); /// Returns production cost change based on typeof (Used for upgrades) Real getProductionCostChangeBasedOnKindOf( KindOfMaskType kindOf ) const; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/CostModifierUpgrade.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/CostModifierUpgrade.h index 5b4971ecd19..8a87c79b9ed 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/CostModifierUpgrade.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/CostModifierUpgrade.h @@ -77,6 +77,20 @@ class Player; // FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// +enum BonusStackingType CPP_11(: Int) +{ + NO_STACKING = 0, // Default behaviour: Values of different percentage stack + OTHER_TYPE = 1, // Values from the different source object types stack. + SAME_TYPE = 2 // Values from the same type of source object stack. +}; +static const char* TheBonusStackingTypeNames[] = +{ + "DIFFERENT_VALUE", + "OTHER_TYPE", + "SAME_TYPE", + NULL +}; + //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- class CostModifierUpgradeModuleData : public UpgradeModuleData @@ -90,6 +104,8 @@ class CostModifierUpgradeModuleData : public UpgradeModuleData Real m_percentage; KindOfMaskType m_kindOf; + Bool m_isOneShot; + BonusStackingType m_stackingType; }; //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp index 1861869fa14..abe8f5e2e85 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -3888,25 +3888,40 @@ void Player::addAIGroupToCurrentSelection(AIGroup *group) { //------------------------------------------------------------------------------------------------- /** addTypeOfProductionCostChange adds a production change to the typeof list */ //------------------------------------------------------------------------------------------------- -void Player::addKindOfProductionCostChange( KindOfMaskType kindOf, Real percent ) -{ - KindOfPercentProductionChangeListIt it = m_kindOfPercentProductionChangeList.begin(); - while(it != m_kindOfPercentProductionChangeList.end()) - { - - KindOfPercentProductionChange *tof = *it; - if( tof->m_percent == percent && tof->m_kindOf == kindOf) +void Player::addKindOfProductionCostChange( KindOfMaskType kindOf, Real percent, + UnsignedInt sourceTemplateID /*= INVALID_ID*/, + Bool stackUniqueType /*= FALSE*/, Bool stackWithAny /*= FALSE*/) +{ + // Possible cases: + // 1. Default behavior: No stacking of bonus with SAME perecentage + // 2. Stack with bonus from OTHER templates but SAME percentage + // - Keep separate entries for each templateID + // 3. Stack with bonus from SAME template and SAME percentage + // - Keep separate entry for each Object (need to track ObjectID) + // - Don't track Object, just track that we can stack, then just remove first matching entry that can stack + + if (!stackWithAny) { // We always stack, no need to check + + KindOfPercentProductionChangeListIt it = m_kindOfPercentProductionChangeList.begin(); + while (it != m_kindOfPercentProductionChangeList.end()) { - tof->m_ref++; - return; + KindOfPercentProductionChange* tof = *it; + if (tof->m_percent == percent && tof->m_kindOf == kindOf && + (!stackUniqueType || (tof->m_templateID == sourceTemplateID && tof->m_templateID != INVALID_ID))) + { + tof->m_ref++; + return; + } + ++it; } - ++it; - } + } KindOfPercentProductionChange *newTof = newInstance( KindOfPercentProductionChange ); newTof->m_kindOf = kindOf; newTof->m_percent = percent; newTof->m_ref = 1; + newTof->m_stackWithAny = stackWithAny; + newTof->m_templateID = sourceTemplateID; m_kindOfPercentProductionChangeList.push_back(newTof); } @@ -3914,14 +3929,19 @@ void Player::addKindOfProductionCostChange( KindOfMaskType kindOf, Real percent //------------------------------------------------------------------------------------------------- /** addTypeOfProductionCostChange adds a production change to the typeof list */ //------------------------------------------------------------------------------------------------- -void Player::removeKindOfProductionCostChange( KindOfMaskType kindOf, Real percent ) +void Player::removeKindOfProductionCostChange( KindOfMaskType kindOf, Real percent, + UnsignedInt sourceTemplateID /*= INVALID_ID*/, + Bool stackUniqueType /*= FALSE*/, Bool stackWithAny /*= FALSE*/) { KindOfPercentProductionChangeListIt it = m_kindOfPercentProductionChangeList.begin(); while(it != m_kindOfPercentProductionChangeList.end()) { KindOfPercentProductionChange* tof = *it; - if( tof->m_percent == percent && tof->m_kindOf == kindOf) + if( tof->m_percent == percent && tof->m_kindOf == kindOf && + (!stackWithAny || tof->m_stackWithAny) && + (!stackUniqueType || tof->m_templateID == sourceTemplateID) + ) { tof->m_ref--; if(tof->m_ref == 0) @@ -3930,6 +3950,9 @@ void Player::removeKindOfProductionCostChange( KindOfMaskType kindOf, Real perce if(tof) tof->deleteInstance(); } + else if (stackWithAny) { + DEBUG_CRASH(("KindOfProductionCost: StackWithAny should never have count > 1.\n")); + } return; } ++it; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/CostModifierUpgrade.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/CostModifierUpgrade.cpp index 438ba178d88..438238279a3 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/CostModifierUpgrade.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/CostModifierUpgrade.cpp @@ -53,6 +53,7 @@ #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "Common/Player.h" +#include "Common/ThingTemplate.h" #include "Common/Xfer.h" #include "GameLogic/Module/CostModifierUpgrade.h" #include "GameLogic/Object.h" @@ -77,6 +78,8 @@ CostModifierUpgradeModuleData::CostModifierUpgradeModuleData( void ) m_kindOf = KINDOFMASK_NONE; m_percentage = 0; + m_isOneShot = FALSE; + m_stackingType = NO_STACKING; } // end CostModifierUpgradeModuleData @@ -90,6 +93,9 @@ CostModifierUpgradeModuleData::CostModifierUpgradeModuleData( void ) { { "EffectKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( CostModifierUpgradeModuleData, m_kindOf ) }, { "Percentage", INI::parsePercentToReal, NULL, offsetof( CostModifierUpgradeModuleData, m_percentage ) }, + { "IsOneShotUpgrade", INI::parseBool, NULL, offsetof( CostModifierUpgradeModuleData, m_isOneShot) }, + { "BonusStacksWith", INI::parseIndexList, TheBonusStackingTypeNames, offsetof( CostModifierUpgradeModuleData, m_stackingType) }, + { 0, 0, 0, 0 } }; p.add(dataFieldParse); @@ -119,15 +125,25 @@ CostModifierUpgrade::~CostModifierUpgrade( void ) //------------------------------------------------------------------------------------------------- void CostModifierUpgrade::onDelete( void ) { + // This is a global one time upgrade. Don't remove it. + if (getCostModifierUpgradeModuleData()->m_isOneShot) + return; // if we haven't been upgraded there is nothing to clean up if( isAlreadyUpgraded() == FALSE ) return; + const CostModifierUpgradeModuleData* d = getCostModifierUpgradeModuleData(); + + Bool stackWithAny = d->m_stackingType == SAME_TYPE; + Bool stackUniqueType = d->m_stackingType == OTHER_TYPE; + // remove the radar from the player Player *player = getObject()->getControllingPlayer(); - if( player ) - player->removeKindOfProductionCostChange(getCostModifierUpgradeModuleData()->m_kindOf,getCostModifierUpgradeModuleData()->m_percentage ); + if (player) { + player->removeKindOfProductionCostChange(d->m_kindOf, d->m_percentage, + getObject()->getTemplate()->getTemplateID(), stackUniqueType, stackWithAny); + } // this upgrade module is now "not upgraded" setUpgradeExecuted(FALSE); @@ -138,23 +154,34 @@ void CostModifierUpgrade::onDelete( void ) //------------------------------------------------------------------------------------------------- void CostModifierUpgrade::onCapture( Player *oldOwner, Player *newOwner ) { + const CostModifierUpgradeModuleData* d = getCostModifierUpgradeModuleData(); + + // This is a global one time upgrade. Don't remove or transfer it. + if (d->m_isOneShot) + return; // do nothing if we haven't upgraded yet if( isAlreadyUpgraded() == FALSE ) return; - // remove radar from old player and add to new player + // remove bonus from old player and add to new player + Bool stackUniqueType = d->m_stackingType == OTHER_TYPE; + Bool stackWithAny = d->m_stackingType == SAME_TYPE; + + if( oldOwner ) { + oldOwner->removeKindOfProductionCostChange(d->m_kindOf, d->m_percentage, + getObject()->getTemplate()->getTemplateID(), stackUniqueType, stackWithAny); - oldOwner->removeKindOfProductionCostChange(getCostModifierUpgradeModuleData()->m_kindOf,getCostModifierUpgradeModuleData()->m_percentage ); setUpgradeExecuted(FALSE); } // end if if( newOwner ) { + newOwner->addKindOfProductionCostChange(d->m_kindOf, d->m_percentage, + getObject()->getTemplate()->getTemplateID(), stackUniqueType, stackWithAny); - newOwner->addKindOfProductionCostChange(getCostModifierUpgradeModuleData()->m_kindOf,getCostModifierUpgradeModuleData()->m_percentage ); setUpgradeExecuted(TRUE); } // end if @@ -165,10 +192,17 @@ void CostModifierUpgrade::onCapture( Player *oldOwner, Player *newOwner ) //------------------------------------------------------------------------------------------------- void CostModifierUpgrade::upgradeImplementation( void ) { + const CostModifierUpgradeModuleData * d = getCostModifierUpgradeModuleData(); + Player *player = getObject()->getControllingPlayer(); // update the player with another TypeOfProductionCostChange - player->addKindOfProductionCostChange(getCostModifierUpgradeModuleData()->m_kindOf,getCostModifierUpgradeModuleData()->m_percentage ); + + Bool stackWithAny = d->m_stackingType == SAME_TYPE; + Bool stackUniqueType = d->m_stackingType == OTHER_TYPE; + + player->addKindOfProductionCostChange(d->m_kindOf, d->m_percentage, + getObject()->getTemplate()->getTemplateID(), stackUniqueType, stackWithAny); } // end upgradeImplementation From 30916e5e8fe8ea0c47f5ec17fd0d94d99981bead Mon Sep 17 00:00:00 2001 From: andreasw Date: Thu, 5 Jun 2025 18:29:33 +0200 Subject: [PATCH 14/42] Improved ProductionTimeModifierUpgrade --- .../Code/GameEngine/Include/Common/Player.h | 8 +++- .../Module/ProductionTimeModifierUpgrade.h | 3 ++ .../GameEngine/Source/Common/RTS/Player.cpp | 37 +++++++++++++------ .../Object/Upgrade/CostModifierUpgrade.cpp | 8 ++-- .../Upgrade/ProductionTimeModifierUpgrade.cpp | 22 +++++++++-- 5 files changed, 58 insertions(+), 20 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Player.h b/GeneralsMD/Code/GameEngine/Include/Common/Player.h index 881d0a906ee..7ddea400332 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Player.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Player.h @@ -394,9 +394,13 @@ class Player : public Snapshot Real getProductionCostChangeBasedOnKindOf( KindOfMaskType kindOf ) const; /// Decrement the ref counter on the typeof production list node - void removeKindOfProductionTimeChange(KindOfMaskType kindOf, Real percent); + void removeKindOfProductionTimeChange(KindOfMaskType kindOf, Real percent, + UnsignedInt sourceTemplateID = INVALID_ID, + Bool stackUniqueType = FALSE, Bool stackWithAny = FALSE); /// add type of production cost change (Used for upgrades) - void addKindOfProductionTimeChange(KindOfMaskType kindOf, Real percent); + void addKindOfProductionTimeChange(KindOfMaskType kindOf, Real percent, + UnsignedInt sourceTemplateID = INVALID_ID, + Bool stackUniqueType = FALSE, Bool stackWithAny = FALSE); /// Returns production cost change based on typeof (Used for upgrades) Real getProductionTimeChangeBasedOnKindOf(KindOfMaskType kindOf) const; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ProductionTimeModifierUpgrade.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ProductionTimeModifierUpgrade.h index 594487fe406..69f2eddbbea 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ProductionTimeModifierUpgrade.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ProductionTimeModifierUpgrade.h @@ -76,6 +76,7 @@ class Player; //----------------------------------------------------------------------------- // FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// +enum BonusStackingType CPP_11(: Int); //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- @@ -90,6 +91,8 @@ class ProductionTimeModifierUpgradeModuleData : public UpgradeModuleData Real m_percentage; KindOfMaskType m_kindOf; + Bool m_isOneShot; + BonusStackingType m_stackingType; }; //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp index abe8f5e2e85..fde54298862 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -3983,25 +3983,32 @@ Real Player::getProductionCostChangeBasedOnKindOf( KindOfMaskType kindOf ) const //------------------------------------------------------------------------------------------------- /** addKindOfProductionTimeChange adds a production change to the typeof list */ //------------------------------------------------------------------------------------------------- -void Player::addKindOfProductionTimeChange(KindOfMaskType kindOf, Real percent) +void Player::addKindOfProductionTimeChange(KindOfMaskType kindOf, Real percent, + UnsignedInt sourceTemplateID /*= INVALID_ID*/, + Bool stackUniqueType /*= FALSE*/, Bool stackWithAny /*= FALSE*/) { - KindOfPercentProductionChangeListIt it = m_kindOfPercentProductionTimeChangeList.begin(); - while (it != m_kindOfPercentProductionTimeChangeList.end()) - { + if (!stackWithAny) { // We always stack, no need to check - KindOfPercentProductionChange* tof = *it; - if (tof->m_percent == percent && tof->m_kindOf == kindOf) + KindOfPercentProductionChangeListIt it = m_kindOfPercentProductionTimeChangeList.begin(); + while (it != m_kindOfPercentProductionTimeChangeList.end()) { - tof->m_ref++; - return; + KindOfPercentProductionChange* tof = *it; + if (tof->m_percent == percent && tof->m_kindOf == kindOf && + (!stackUniqueType || (tof->m_templateID == sourceTemplateID && tof->m_templateID != INVALID_ID))) + { + tof->m_ref++; + return; + } + ++it; } - ++it; } KindOfPercentProductionChange* newTof = newInstance(KindOfPercentProductionChange); newTof->m_kindOf = kindOf; newTof->m_percent = percent; newTof->m_ref = 1; + newTof->m_stackWithAny = stackWithAny; + newTof->m_templateID = sourceTemplateID; m_kindOfPercentProductionTimeChangeList.push_back(newTof); } @@ -4009,14 +4016,19 @@ void Player::addKindOfProductionTimeChange(KindOfMaskType kindOf, Real percent) //------------------------------------------------------------------------------------------------- /** removeKindOfProductionTimeChange adds a production change to the typeof list */ //------------------------------------------------------------------------------------------------- -void Player::removeKindOfProductionTimeChange(KindOfMaskType kindOf, Real percent) +void Player::removeKindOfProductionTimeChange(KindOfMaskType kindOf, Real percent, + UnsignedInt sourceTemplateID /*= INVALID_ID*/, + Bool stackUniqueType /*= FALSE*/, Bool stackWithAny /*= FALSE*/) { KindOfPercentProductionChangeListIt it = m_kindOfPercentProductionTimeChangeList.begin(); while (it != m_kindOfPercentProductionTimeChangeList.end()) { KindOfPercentProductionChange* tof = *it; - if (tof->m_percent == percent && tof->m_kindOf == kindOf) + if (tof->m_percent == percent && tof->m_kindOf == kindOf && + (!stackWithAny || tof->m_stackWithAny) && + (!stackUniqueType || tof->m_templateID == sourceTemplateID) + ) { tof->m_ref--; if (tof->m_ref == 0) @@ -4025,6 +4037,9 @@ void Player::removeKindOfProductionTimeChange(KindOfMaskType kindOf, Real percen if (tof) tof->deleteInstance(); } + else if (stackWithAny) { + DEBUG_CRASH(("KindOfProductionTime: StackWithAny should never have count > 1.\n")); + } return; } ++it; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/CostModifierUpgrade.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/CostModifierUpgrade.cpp index 438238279a3..6c85ce1178d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/CostModifierUpgrade.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/CostModifierUpgrade.cpp @@ -125,20 +125,20 @@ CostModifierUpgrade::~CostModifierUpgrade( void ) //------------------------------------------------------------------------------------------------- void CostModifierUpgrade::onDelete( void ) { + const CostModifierUpgradeModuleData* d = getCostModifierUpgradeModuleData(); + // This is a global one time upgrade. Don't remove it. - if (getCostModifierUpgradeModuleData()->m_isOneShot) + if (d->m_isOneShot) return; // if we haven't been upgraded there is nothing to clean up if( isAlreadyUpgraded() == FALSE ) return; - const CostModifierUpgradeModuleData* d = getCostModifierUpgradeModuleData(); - Bool stackWithAny = d->m_stackingType == SAME_TYPE; Bool stackUniqueType = d->m_stackingType == OTHER_TYPE; - // remove the radar from the player + // remove the bonus from the player Player *player = getObject()->getControllingPlayer(); if (player) { player->removeKindOfProductionCostChange(d->m_kindOf, d->m_percentage, diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/ProductionTimeModifierUpgrade.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/ProductionTimeModifierUpgrade.cpp index ba30a45c20d..1a7f4d6cd66 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/ProductionTimeModifierUpgrade.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/ProductionTimeModifierUpgrade.cpp @@ -53,8 +53,10 @@ #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine #include "Common/player.h" +#include "Common/ThingTemplate.h" #include "Common/Xfer.h" #include "GameLogic/Module/ProductionTimeModifierUpgrade.h" +#include "GameLogic/Module/CostModifierUpgrade.h" #include "GameLogic/Object.h" #include "Common/BitFlagsIO.h" //----------------------------------------------------------------------------- @@ -77,6 +79,8 @@ ProductionTimeModifierUpgradeModuleData::ProductionTimeModifierUpgradeModuleData m_kindOf = KINDOFMASK_NONE; m_percentage = 0; + m_isOneShot = FALSE; + m_stackingType = NO_STACKING; } // end ProductionTimeModifierUpgradeModuleData @@ -90,6 +94,8 @@ ProductionTimeModifierUpgradeModuleData::ProductionTimeModifierUpgradeModuleData { { "EffectKindOf", KindOfMaskType::parseFromINI, NULL, offsetof(ProductionTimeModifierUpgradeModuleData, m_kindOf ) }, { "Percentage", INI::parsePercentToReal, NULL, offsetof(ProductionTimeModifierUpgradeModuleData, m_percentage ) }, + { "IsOneShotUpgrade", INI::parseBool, NULL, offsetof(ProductionTimeModifierUpgradeModuleData, m_isOneShot) }, + { "BonusStacksWith", INI::parseIndexList, TheBonusStackingTypeNames, offsetof(ProductionTimeModifierUpgradeModuleData, m_stackingType) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); @@ -119,15 +125,25 @@ ProductionTimeModifierUpgrade::~ProductionTimeModifierUpgrade( void ) //------------------------------------------------------------------------------------------------- void ProductionTimeModifierUpgrade::onDelete( void ) { + const ProductionTimeModifierUpgradeModuleData* d = getProductionTimeModifierUpgradeModuleData(); + + // This is a global one time upgrade. Don't remove it. + if (d->m_isOneShot) + return; // if we haven't been upgraded there is nothing to clean up if( isAlreadyUpgraded() == FALSE ) return; + Bool stackWithAny = d->m_stackingType == SAME_TYPE; + Bool stackUniqueType = d->m_stackingType == OTHER_TYPE; + // remove the radar from the player - Player *player = getObject()->getControllingPlayer(); - if( player ) - player->removeKindOfProductionTimeChange(getProductionTimeModifierUpgradeModuleData()->m_kindOf, getProductionTimeModifierUpgradeModuleData()->m_percentage ); + Player* player = getObject()->getControllingPlayer(); + if (player) { + player->removeKindOfProductionTimeChange(d->m_kindOf, d->m_percentage, + getObject()->getTemplate()->getTemplateID(), stackUniqueType, stackWithAny); + } // this upgrade module is now "not upgraded" setUpgradeExecuted(FALSE); From 8b4bd773942bea4799bea471bd521d9bc4e9d820 Mon Sep 17 00:00:00 2001 From: andreasw Date: Thu, 5 Jun 2025 21:16:59 +0200 Subject: [PATCH 15/42] Add UnitProductionBonusUpgrade --- GeneralsMD/Code/GameEngine/CMakeLists.txt | 2 + .../Code/GameEngine/Include/Common/Player.h | 6 + .../Module/UnitProductionBonusUpgrade.h | 100 ++++++++ .../GameEngine/Source/Common/RTS/Player.cpp | 102 ++++++++ .../Source/Common/System/MemoryInit.cpp | 1 + .../Source/Common/Thing/ModuleFactory.cpp | 2 + .../Upgrade/UnitProductionBonusUpgrade.cpp | 222 ++++++++++++++++++ 7 files changed, 435 insertions(+) create mode 100644 GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UnitProductionBonusUpgrade.h create mode 100644 GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/UnitProductionBonusUpgrade.cpp diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index db3fe32dc02..f906269fc5b 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -288,6 +288,7 @@ set(GAMEENGINE_SRC Include/GameLogic/Module/ConvertToHijackedVehicleCrateCollide.h Include/GameLogic/Module/CostModifierUpgrade.h Include/GameLogic/Module/ProductionTimeModifierUpgrade.h + Include/GameLogic/Module/UnitProductionBonusUpgrade.h Include/GameLogic/Module/CountermeasuresBehavior.h Include/GameLogic/Module/CrateCollide.h Include/GameLogic/Module/CreateCrateDie.h @@ -1061,6 +1062,7 @@ set(GAMEENGINE_SRC Source/GameLogic/Object/Upgrade/CommandSetUpgrade.cpp Source/GameLogic/Object/Upgrade/CostModifierUpgrade.cpp Source/GameLogic/Object/Upgrade/ProductionTimeModifierUpgrade.cpp + Source/GameLogic/Object/Upgrade/UnitProductionBonusUpgrade.cpp Source/GameLogic/Object/Upgrade/ExperienceScalarUpgrade.cpp Source/GameLogic/Object/Upgrade/GrantScienceUpgrade.cpp Source/GameLogic/Object/Upgrade/LocomotorSetUpgrade.cpp diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Player.h b/GeneralsMD/Code/GameEngine/Include/Common/Player.h index 7ddea400332..e578e19c4a6 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/Player.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/Player.h @@ -417,6 +417,12 @@ class Player : public Snapshot */ VeterancyLevel getProductionVeterancyLevel( AsciiString buildTemplateName ) const; + + // These values can now be set via module + void addProductionCostChangePercent(AsciiString buildTemplateName, Real percent); + void addProductionTimeChangePercent(AsciiString buildTemplateName, Real percent); + + // Friend function for the script engine's usage. void friend_setSkillset(Int skillSet); diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UnitProductionBonusUpgrade.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UnitProductionBonusUpgrade.h new file mode 100644 index 00000000000..f68b6051eb2 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UnitProductionBonusUpgrade.h @@ -0,0 +1,100 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: CostModifierUpgrade.h ///////////////////////////////////////////////// +//----------------------------------------------------------------------------- +// +// Electronic Arts Pacific. +// +// Confidential Information +// Copyright (C) 2002 - All Rights Reserved +// +//----------------------------------------------------------------------------- +// +// created: June 2025 +// +// Filename: UnitProductionBonusUpgrade.h +// +// author: Andi W +// +// purpose: +// +//----------------------------------------------------------------------------- +/////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __UNIT_PRODUCTION_BONUS_UPGRADE_H_ +#define __UNIT_PRODUCTION_BONUS_UPGRADE_H_ + +//----------------------------------------------------------------------------- +#include "GameLogic/Module/UpgradeModule.h" + +//----------------------------------------------------------------------------- +class Thing; +class Player; + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class UnitProductionBonusUpgradeModuleData : public UpgradeModuleData +{ + +public: + + UnitProductionBonusUpgradeModuleData(void); + + static void buildFieldParse(MultiIniFieldParse& p); + + std::vector m_templateNames; + Real m_costPercentage; + Real m_timePercentage; + // Bool m_isOneShot; +}; + +//------------------------------------------------------------------------------------------------- +/** The OCL upgrade module */ +//------------------------------------------------------------------------------------------------- +class UnitProductionBonusUpgrade : public UpgradeModule +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(UnitProductionBonusUpgrade, "UnitProductionBonusUpgrade") + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA(UnitProductionBonusUpgrade, UnitProductionBonusUpgradeModuleData); + +public: + + UnitProductionBonusUpgrade(Thing* thing, const ModuleData* moduleData); + // virtual destructor prototype defined by MemoryPoolObject + + // virtual void onDelete(void); ///< we have some work to do when this module goes away + // virtual void onCapture(Player* oldOwner, Player* newOwner); + +protected: + + virtual void upgradeImplementation(void); ///< Here's the actual work of Upgrading + virtual Bool isSubObjectsUpgrade() { return false; } + +}; + +#endif // __UNIT_PRODUCTION_BONUS_UPGRADE_H_ diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp index fde54298862..9ae1346d2bd 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp @@ -2044,6 +2044,36 @@ Real Player::getProductionTimeChangePercent( AsciiString buildTemplateName ) con return 0.0f; } +//============================================================================= +void Player::addProductionCostChangePercent(AsciiString buildTemplateName, Real percent) +{ + // First check if the entry exists + ProductionChangeMap::iterator it = m_productionCostChanges.find(NAMEKEY(buildTemplateName)); + if (it != m_productionCostChanges.end()) + { + (*it).second += percent; // Additive stacking + return; + } + // If we haven't found it, add it + m_productionCostChanges[NAMEKEY(buildTemplateName)] = percent; + //TODO: remove the entry if we end up at 0? +} + +//============================================================================= +void Player::addProductionTimeChangePercent(AsciiString buildTemplateName, Real percent) +{ + // First check if the entry exists + ProductionChangeMap::iterator it = m_productionTimeChanges.find(NAMEKEY(buildTemplateName)); + if (it != m_productionTimeChanges.end()) + { + (*it).second += percent; // Additive stacking + return; + } + // If we haven't found it, add it + m_productionTimeChanges[NAMEKEY(buildTemplateName)] = percent; + //TODO: remove the entry if we end up at 0? +} + //============================================================================= VeterancyLevel Player::getProductionVeterancyLevel( AsciiString buildTemplateName ) const { @@ -4782,6 +4812,78 @@ void Player::xfer( Xfer *xfer ) else m_unitsShouldHunt = FALSE; + + // ------------------------- + // Xfer ProductionCostChangeMap + // ------------------------- + { + UnsignedShort entriesCount = m_productionCostChanges.size(); + xfer->xferUnsignedShort(&entriesCount); + ProductionChangeMap::iterator it; + if (xfer->getXferMode() == XFER_SAVE) + { + // iterate each prototype and xfer if it needs to be in the save file + for (it = m_productionCostChanges.begin(); it != m_productionCostChanges.end(); ++it) + { + AsciiString templateName = KEYNAME((*it).first); + xfer->xferAsciiString(&templateName); + xfer->xferReal(&((*it).second)); + } //end for, it + + } // end if, saving + else + { + for (UnsignedShort i = 0; i < entriesCount; ++i) + { + AsciiString templateName; + Real bonusPercent; + + xfer->xferAsciiString(&templateName); + xfer->xferReal(&bonusPercent); + + m_productionCostChanges[NAMEKEY(templateName)] = bonusPercent; + + } // end for, i + + } // end else, loading + } + //------------------------ + // Xfer ProductionTimeChangeMap + // ------------------------- + { + UnsignedShort entriesCount = m_productionTimeChanges.size(); + xfer->xferUnsignedShort(&entriesCount); + ProductionChangeMap::iterator it; + if (xfer->getXferMode() == XFER_SAVE) + { + // iterate each prototype and xfer if it needs to be in the save file + for (it = m_productionTimeChanges.begin(); it != m_productionTimeChanges.end(); ++it) + { + AsciiString templateName = KEYNAME((*it).first); + xfer->xferAsciiString(&templateName); + xfer->xferReal(&((*it).second)); + } //end for, it + + } // end if, saving + else + { + for (UnsignedShort i = 0; i < entriesCount; ++i) + { + AsciiString templateName; + Real bonusPercent; + + xfer->xferAsciiString(&templateName); + xfer->xferReal(&bonusPercent); + + m_productionTimeChanges[NAMEKEY(templateName)] = bonusPercent; + + } // end for, i + + } // end else, loading + } + //------------------------ + + } // end xfer // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp index 89db704b520..32d4a411fc9 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp @@ -275,6 +275,7 @@ static PoolSizeRec sizes[] = { "SupplyWarehouseCripplingBehavior", 16, 16 }, { "CostModifierUpgrade", 32, 32 }, { "ProductionTimeModifierUpgrade", 32, 32 }, + { "UnitProductionBonusUpgrade", 64, 32 }, { "CashBountyPower", 32, 32 }, { "CleanupAreaPower", 32, 32 }, { "ObjectCreationUpgrade", 196, 32 }, diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index c152ef74e4f..9bb0f16d20c 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -215,6 +215,7 @@ #include "GameLogic/Module/WeaponBonusUpgrade.h" #include "GameLogic/Module/CostModifierUpgrade.h" #include "GameLogic/Module/ProductionTimeModifierUpgrade.h" +#include "GameLogic/Module/UnitProductionBonusUpgrade.h" #include "GameLogic/Module/ExperienceScalarUpgrade.h" #include "GameLogic/Module/MaxHealthUpgrade.h" @@ -484,6 +485,7 @@ void ModuleFactory::init( void ) // upgrade modules addModule( CostModifierUpgrade ); addModule( ProductionTimeModifierUpgrade ); + addModule( UnitProductionBonusUpgrade ); addModule( ActiveShroudUpgrade ); addModule( ArmorUpgrade ); addModule( CommandSetUpgrade ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/UnitProductionBonusUpgrade.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/UnitProductionBonusUpgrade.cpp new file mode 100644 index 00000000000..de3754d257f --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/UnitProductionBonusUpgrade.cpp @@ -0,0 +1,222 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: UnitProductionBonusUpgrade.cpp ///////////////////////////////////////////////// +//----------------------------------------------------------------------------- +// +// Electronic Arts Pacific. +// +// Confidential Information +// Copyright (C) 2002 - All Rights Reserved +// +//----------------------------------------------------------------------------- +// +// created: Aug 2002 +// +// Filename: UnitProductionBonusUpgrade.cpp +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: UnitProductionBonusUpgrade.cpp ///////////////////////////////////////////////// +//----------------------------------------------------------------------------- +// +// Electronic Arts Pacific. +// +// Confidential Information +// Copyright (C) 2002 - All Rights Reserved +// +//----------------------------------------------------------------------------- +// +// created: June 2025 +// +// Filename: UnitProductionBonusUpgrade.h +// +// author: Andi W +// +// purpose: Upgrade that modifies the cost or build time of a list of units +// +//----------------------------------------------------------------------------- +/////////////////////////////////////////////////////////////////////////////// + +//----------------------------------------------------------------------------- +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#include "Common/Player.h" +#include "Common/ThingTemplate.h" +#include "Common/ThingFactory.h" +#include "Common/Xfer.h" +#include "GameLogic/Module/UnitProductionBonusUpgrade.h" +#include "GameLogic/Object.h" +#include "Common/BitFlagsIO.h" + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +UnitProductionBonusUpgradeModuleData::UnitProductionBonusUpgradeModuleData( void ) +{ + m_templateNames.clear(); + m_costPercentage = 0.0f; + m_timePercentage = 0.0f; + // m_isOneShot = FALSE; + +} // end UnitProductionBonusUpgradeModuleData + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +/* static */ void UnitProductionBonusUpgradeModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + UpgradeModuleData::buildFieldParse( p ); + + static const FieldParse dataFieldParse[] = + { + { "CostModifierPercentage", INI::parsePercentToReal, NULL, offsetof( UnitProductionBonusUpgradeModuleData, m_costPercentage ) }, + { "BuildTimeModifierPercentage", INI::parsePercentToReal, NULL, offsetof( UnitProductionBonusUpgradeModuleData, m_timePercentage ) }, + // { "IsOneShotUpgrade", INI::parseBool, NULL, offsetof( UnitProductionBonusUpgradeModuleData, m_isOneShot) }, + { "UnitTemplateName", INI::parseAsciiStringVectorAppend, NULL, offsetof(UnitProductionBonusUpgradeModuleData, m_templateNames) }, + + { 0, 0, 0, 0 } + }; + p.add(dataFieldParse); + +} // end buildFieldParse + +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +UnitProductionBonusUpgrade::UnitProductionBonusUpgrade( Thing *thing, const ModuleData* moduleData ) : + UpgradeModule( thing, moduleData ) +{ + +} // end UnitProductionBonusUpgrade + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +UnitProductionBonusUpgrade::~UnitProductionBonusUpgrade( void ) +{ + +} // end ~UnitProductionBonusUpgrade + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//void UnitProductionBonusUpgrade::onDelete( void ) +//{ +// +// // this upgrade module is now "not upgraded" +// setUpgradeExecuted(FALSE); +// +//} // end onDelete + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//void UnitProductionBonusUpgrade::onCapture( Player *oldOwner, Player *newOwner ) +//{ +// +// +//} // end onCapture + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void UnitProductionBonusUpgrade::upgradeImplementation( void ) +{ + const UnitProductionBonusUpgradeModuleData * d = getUnitProductionBonusUpgradeModuleData(); + + Player *player = getObject()->getControllingPlayer(); + + for (std::vector::const_iterator tempName = d->m_templateNames.begin(); + tempName != d->m_templateNames.end(); ++tempName) + { + if (d->m_costPercentage != 0.0f) { + player->addProductionCostChangePercent(*tempName, d->m_costPercentage); + } + + if (d->m_timePercentage != 0.0f) { + player->addProductionTimeChangePercent(*tempName, d->m_timePercentage); + } + } + +} // end upgradeImplementation + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void UnitProductionBonusUpgrade::crc( Xfer *xfer ) +{ + + // extend base class + UpgradeModule::crc( xfer ); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void UnitProductionBonusUpgrade::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // extend base class + UpgradeModule::xfer( xfer ); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void UnitProductionBonusUpgrade::loadPostProcess( void ) +{ + + // extend base class + UpgradeModule::loadPostProcess(); + +} // end loadPostProcess From 3677af12ee295b821009fef32b73c646706d034e Mon Sep 17 00:00:00 2001 From: Andi Date: Sat, 7 Jun 2025 13:26:14 +0200 Subject: [PATCH 16/42] Create freefall projectile module --- GeneralsMD/Code/GameEngine/CMakeLists.txt | 2 ++ GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp | 1 + .../Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp | 2 ++ 3 files changed, 5 insertions(+) diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index db3fe32dc02..a845501516b 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -309,6 +309,7 @@ set(GAMEENGINE_SRC Include/GameLogic/Module/DockUpdate.h Include/GameLogic/Module/DozerAIUpdate.h Include/GameLogic/Module/DumbProjectileBehavior.h + Include/GameLogic/Module/FreeFallProjectileBehavior.h Include/GameLogic/Module/DynamicGeometryInfoUpdate.h Include/GameLogic/Module/DynamicShroudClearingRangeUpdate.h Include/GameLogic/Module/EjectPilotDie.h @@ -853,6 +854,7 @@ set(GAMEENGINE_SRC Source/GameLogic/Object/Behavior/BunkerBusterBehavior.cpp Source/GameLogic/Object/Behavior/CountermeasuresBehavior.cpp Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp + Source/GameLogic/Object/Behavior/FreeFallProjectileBehavior.cpp Source/GameLogic/Object/Behavior/FireWeaponWhenDamagedBehavior.cpp Source/GameLogic/Object/Behavior/FireWeaponWhenDeadBehavior.cpp Source/GameLogic/Object/Behavior/FlightDeckBehavior.cpp diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp index 89db704b520..b9d48c84dee 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp @@ -194,6 +194,7 @@ static PoolSizeRec sizes[] = { "HackInternetAIUpdate", 32, 32 }, { "MissileAIUpdate", 512, 32 }, { "DumbProjectileBehavior", 64, 32 }, + { "FreeFallProjectileBehavior", 32, 32 }, { "DestroyDie", 1024, 32 }, { "UpgradeDie", 128, 32 }, { "KeepObjectDie", 128, 32 }, diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index c152ef74e4f..e5f7cc0e9c0 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -51,6 +51,7 @@ #include "GameLogic/Module/BridgeTowerBehavior.h" #include "GameLogic/Module/CountermeasuresBehavior.h" #include "GameLogic/Module/DumbProjectileBehavior.h" +#include "GameLogic/Module/FreeFallProjectileBehavior.h" #include "GameLogic/Module/InstantDeathBehavior.h" #include "GameLogic/Module/SlowDeathBehavior.h" #include "GameLogic/Module/HelicopterSlowDeathUpdate.h" @@ -337,6 +338,7 @@ void ModuleFactory::init( void ) addModule( BridgeTowerBehavior ); addModule( CountermeasuresBehavior ); addModule( DumbProjectileBehavior ); + addModule( FreeFallProjectileBehavior ); addModule( PhysicsBehavior ); addModule( InstantDeathBehavior ); addModule( SlowDeathBehavior ); From 210c58d335eba8098e8233575723cd3138fdb914 Mon Sep 17 00:00:00 2001 From: Andi Date: Sat, 7 Jun 2025 16:46:36 +0200 Subject: [PATCH 17/42] Finish up freefall module add bounceFactor to freefall module --- .../GameEngine/Include/Common/ThingTemplate.h | 1612 ++++++++--------- .../Module/FreeFallProjectileBehavior.h | 125 ++ .../Include/GameLogic/Module/PhysicsUpdate.h | 1 + .../Behavior/FreeFallProjectileBehavior.cpp | 482 +++++ .../GameLogic/Object/Update/PhysicsUpdate.cpp | 10 +- 5 files changed, 1422 insertions(+), 808 deletions(-) create mode 100644 GeneralsMD/Code/GameEngine/Include/GameLogic/Module/FreeFallProjectileBehavior.h create mode 100644 GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/FreeFallProjectileBehavior.cpp diff --git a/GeneralsMD/Code/GameEngine/Include/Common/ThingTemplate.h b/GeneralsMD/Code/GameEngine/Include/Common/ThingTemplate.h index b918059d7f6..f11f82a79e5 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/ThingTemplate.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/ThingTemplate.h @@ -1,810 +1,810 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: ThingTemplate.h ////////////////////////////////////////////////////////////////////////// -// Author: Colin Day, April 2001 -// Desc: Thing templates are a 'roadmap' to creating things -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __THINGTEMPLATE_H_ -#define __THINGTEMPLATE_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "Lib/BaseType.h" - -#include "Common/AudioEventRTS.h" -#include "Common/FileSystem.h" -#include "Common/GameCommon.h" -#include "Common/Geometry.h" -#include "Common/KindOf.h" -#include "Common/ModuleFactory.h" -#include "Common/Overridable.h" -#include "Common/ProductionPrerequisite.h" -#include "Common/Science.h" -#include "Common/UnicodeString.h" - -#include "GameLogic/ArmorSet.h" -#include "GameLogic/WeaponSet.h" -#include "Common/STLTypedefs.h" -#include "GameClient/Color.h" - -// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// -class AIUpdateModuleData; -class Image; -class Object; -class Drawable; -class ProductionPrerequisite; -struct FieldParse; -class Player; -class INI; -enum RadarPriorityType CPP_11(: Int); -enum ScienceType CPP_11(: Int); -enum EditorSortingType CPP_11(: Int); -enum ShadowType CPP_11(: Int); -class WeaponTemplateSet; -class ArmorTemplateSet; -class FXList; - -// TYPEDEFS FOR FILE ////////////////////////////////////////////////////////////////////////////// -typedef std::map PerUnitSoundMap; -typedef std::map PerUnitFXMap; - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//Code renderer handles these states now. -//enum InventoryImageType -//{ -// INV_IMAGE_ENABLED = 0, -// INV_IMAGE_DISABLED, -// INV_IMAGE_HILITE, -// INV_IMAGE_PUSHED, -// -// INV_IMAGE_NUM_IMAGES // keep this last -// -//}; -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -enum -{ - MAX_UPGRADE_CAMEO_UPGRADES = 5 -}; - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -enum ThingTemplateAudioType CPP_11(: Int) -{ - TTAUDIO_voiceSelect, ///< Response when unit is selected - TTAUDIO_voiceGroupSelect, ///< Response when a group of this unit is selected - TTAUDIO_voiceSelectElite, ///< Response when unit is selected and elite - TTAUDIO_voiceMove, ///< Response when unit moves - TTAUDIO_voiceAttack, ///< Response when unit is told to attack - TTAUDIO_voiceEnter, ///< Response when unit is told to enter a building - TTAUDIO_voiceFear, ///< Response when unit is under attack - TTAUDIO_voiceCreated, ///< Response when unit is created - TTAUDIO_voiceNearEnemy, ///< Unit is near an enemy - TTAUDIO_voiceTaskUnable, ///< Unit is told to do something impossible - TTAUDIO_voiceTaskComplete, ///< Unit completes a move, or other task indicated - TTAUDIO_voiceMeetEnemy, ///< Unit meets an enemy unit - TTAUDIO_soundMoveStart, ///< Sound when unit starts moving - TTAUDIO_soundMoveStartDamaged, ///< Sound when unit starts moving and is damaged - TTAUDIO_soundMoveLoop, ///< Sound when unit is moving - TTAUDIO_soundMoveLoopDamaged, ///< Sound when unit is moving and is damaged - TTAUDIO_soundAmbient, ///< Ambient sound for unit during normal status. Also the default sound - TTAUDIO_soundAmbientDamaged, ///< Ambient sound for unit if damaged. Corresponds to body info damage - TTAUDIO_soundAmbientReallyDamaged,///< Ambient sound for unit if badly damaged. - TTAUDIO_soundAmbientRubble, ///< Ambient sound for unit if it is currently rubble. (Dam, for instance) - TTAUDIO_soundStealthOn, ///< Sound when unit stealths - TTAUDIO_soundStealthOff, ///< Sound when unit destealths - TTAUDIO_soundCreated, ///< Sound when unit is created - TTAUDIO_soundOnDamaged, ///< Sound when unit enters damaged state - TTAUDIO_soundOnReallyDamaged, ///< Sound when unit enters reallyd damaged state - TTAUDIO_soundEnter, ///< Sound when another unit enters me. - TTAUDIO_soundExit, ///< Sound when another unit exits me. - TTAUDIO_soundPromotedVeteran, ///< Sound when unit gets promoted to Veteran level - TTAUDIO_soundPromotedElite, ///< Sound when unit gets promoted to Elite level - TTAUDIO_soundPromotedHero, ///< Sound when unit gets promoted to Hero level - TTAUDIO_voiceGarrison, ///< Unit is ordered to enter a garrisonable building - TTAUDIO_soundFalling, ///< This sound is actually called on a unit when it is exiting another. - ///< However, there is a soundExit which refers to the container, and this is only used for bombs falling from planes. -#ifdef ALLOW_SURRENDER - TTAUDIO_voiceSurrender, ///< Unit surrenders -#endif - TTAUDIO_voiceDefect, ///< Unit is forced to defect - TTAUDIO_voiceAttackSpecial, ///< Unit is ordered to use a special attack - TTAUDIO_voiceAttackAir, ///< Unit is ordered to attack an airborne unit - TTAUDIO_voiceGuard, ///< Unit is ordered to guard an area - - TTAUDIO_COUNT // keep last! -}; - -class AudioArray -{ -public: - DynamicAudioEventRTS* m_audio[TTAUDIO_COUNT]; - - AudioArray() - { - for (Int i = 0; i < TTAUDIO_COUNT; ++i) - m_audio[i] = NULL; - } - - ~AudioArray() - { - for (Int i = 0; i < TTAUDIO_COUNT; ++i) - if (m_audio[i]) - m_audio[i]->deleteInstance(); - } - - AudioArray(const AudioArray& that) - { - for (Int i = 0; i < TTAUDIO_COUNT; ++i) - { - if (that.m_audio[i]) - m_audio[i] = newInstance(DynamicAudioEventRTS)(*that.m_audio[i]); - else - m_audio[i] = NULL; - } - } - - AudioArray& operator=(const AudioArray& that) - { - if (this != &that) - { - for (Int i = 0; i < TTAUDIO_COUNT; ++i) - { - if (that.m_audio[i]) - { - if (m_audio[i]) - *m_audio[i] = *that.m_audio[i]; - else - m_audio[i] = newInstance(DynamicAudioEventRTS)(*that.m_audio[i]); - } - else - { - m_audio[i] = NULL; - } - } - } - return *this; - } -}; - -//------------------------------------------------------------------------------------------------- -/** Object class type enumeration */ -//------------------------------------------------------------------------------------------------- -enum BuildCompletionType CPP_11(: Int) -{ - BC_INVALID = 0, - BC_APPEARS_AT_RALLY_POINT, ///< unit appears at rally point of its #1 prereq - BC_PLACED_BY_PLAYER, ///< unit must be manually placed by player - - BC_NUM_TYPES // leave this last -}; -#ifdef DEFINE_BUILD_COMPLETION_NAMES -static const char *BuildCompletionNames[] = -{ - "INVALID", - "APPEARS_AT_RALLY_POINT", - "PLACED_BY_PLAYER", - - NULL -}; -#endif // end DEFINE_BUILD_COMPLETION_NAMES - -enum BuildableStatus CPP_11(: Int) -{ - // saved into savegames... do not change or remove values! - BSTATUS_YES = 0, - BSTATUS_IGNORE_PREREQUISITES, - BSTATUS_NO, - BSTATUS_ONLY_BY_AI, - - BSTATUS_NUM_TYPES // leave this last -}; - -#ifdef DEFINE_BUILDABLE_STATUS_NAMES -static const char *BuildableStatusNames[] = -{ - "Yes", - "Ignore_Prerequisites", - "No", - "Only_By_AI", - NULL -}; -#endif // end DEFINE_BUILDABLE_STATUS_NAMES - -enum AmmoPipsStyle CPP_11(: Int) -{ - AMMO_PIPS_DEFAULT = 0, ///< Default style, showing each shot in clip - AMMO_PIPS_BAR, ///< Show percentage bar - AMMO_PIPS_SINGLE, ///< like default, but show a single pip only (full or empty) - - AMMO_PIPS_NUM_TYPES // leave this last -}; -#ifdef DEFINE_AMMO_PIPS_STYLE_NAMES -static const char* AmmoPipsStyleNames[] = -{ - "DEFAULT", - "PERCENTAGE_BAR", - "SINGLE", - - NULL -}; -#endif // end DEFINE_AMMO_PIPS_STYLE_NAMES - -//------------------------------------------------------------------------------------------------- -enum ModuleParseMode CPP_11(: Int) -{ - MODULEPARSE_NORMAL, - MODULEPARSE_ADD_REMOVE_REPLACE, - MODULEPARSE_INHERITABLE, - MODULEPARSE_OVERRIDEABLE_BY_LIKE_KIND, - -}; - -//------------------------------------------------------------------------------------------------- -class ModuleInfo -{ -private: - struct Nugget - { - AsciiString first; - AsciiString m_moduleTag; - const ModuleData* second; - Int interfaceMask; - Bool copiedFromDefault; - Bool inheritable; - Bool overrideableByLikeKind; - - Nugget(const AsciiString& n, const AsciiString& moduleTag, const ModuleData* d, Int i, Bool inh, Bool oblk) - : first(n), - m_moduleTag(moduleTag), - second(d), - interfaceMask(i), - copiedFromDefault(false), - inheritable(inh), - overrideableByLikeKind(oblk) - { - } - - }; - std::vector m_info; - -public: - - ModuleInfo() { } - - void addModuleInfo( ThingTemplate *thingTemplate, const AsciiString& name, const AsciiString& moduleTag, const ModuleData* data, Int interfaceMask, Bool inheritable, Bool overrideableByLikeKind = FALSE ); - const ModuleInfo::Nugget *getNuggetWithTag( const AsciiString& tag ) const; - - Int getCount() const - { - return m_info.size(); - } - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: ThingTemplate.h ////////////////////////////////////////////////////////////////////////// +// Author: Colin Day, April 2001 +// Desc: Thing templates are a 'roadmap' to creating things +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __THINGTEMPLATE_H_ +#define __THINGTEMPLATE_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Lib/BaseType.h" + +#include "Common/AudioEventRTS.h" +#include "Common/FileSystem.h" +#include "Common/GameCommon.h" +#include "Common/Geometry.h" +#include "Common/KindOf.h" +#include "Common/ModuleFactory.h" +#include "Common/Overridable.h" +#include "Common/ProductionPrerequisite.h" +#include "Common/Science.h" +#include "Common/UnicodeString.h" + +#include "GameLogic/ArmorSet.h" +#include "GameLogic/WeaponSet.h" +#include "Common/STLTypedefs.h" +#include "GameClient/Color.h" + +// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// +class AIUpdateModuleData; +class Image; +class Object; +class Drawable; +class ProductionPrerequisite; +struct FieldParse; +class Player; +class INI; +enum RadarPriorityType CPP_11(: Int); +enum ScienceType CPP_11(: Int); +enum EditorSortingType CPP_11(: Int); +enum ShadowType CPP_11(: Int); +class WeaponTemplateSet; +class ArmorTemplateSet; +class FXList; + +// TYPEDEFS FOR FILE ////////////////////////////////////////////////////////////////////////////// +typedef std::map PerUnitSoundMap; +typedef std::map PerUnitFXMap; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//Code renderer handles these states now. +//enum InventoryImageType +//{ +// INV_IMAGE_ENABLED = 0, +// INV_IMAGE_DISABLED, +// INV_IMAGE_HILITE, +// INV_IMAGE_PUSHED, +// +// INV_IMAGE_NUM_IMAGES // keep this last +// +//}; +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +enum +{ + MAX_UPGRADE_CAMEO_UPGRADES = 5 +}; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +enum ThingTemplateAudioType CPP_11(: Int) +{ + TTAUDIO_voiceSelect, ///< Response when unit is selected + TTAUDIO_voiceGroupSelect, ///< Response when a group of this unit is selected + TTAUDIO_voiceSelectElite, ///< Response when unit is selected and elite + TTAUDIO_voiceMove, ///< Response when unit moves + TTAUDIO_voiceAttack, ///< Response when unit is told to attack + TTAUDIO_voiceEnter, ///< Response when unit is told to enter a building + TTAUDIO_voiceFear, ///< Response when unit is under attack + TTAUDIO_voiceCreated, ///< Response when unit is created + TTAUDIO_voiceNearEnemy, ///< Unit is near an enemy + TTAUDIO_voiceTaskUnable, ///< Unit is told to do something impossible + TTAUDIO_voiceTaskComplete, ///< Unit completes a move, or other task indicated + TTAUDIO_voiceMeetEnemy, ///< Unit meets an enemy unit + TTAUDIO_soundMoveStart, ///< Sound when unit starts moving + TTAUDIO_soundMoveStartDamaged, ///< Sound when unit starts moving and is damaged + TTAUDIO_soundMoveLoop, ///< Sound when unit is moving + TTAUDIO_soundMoveLoopDamaged, ///< Sound when unit is moving and is damaged + TTAUDIO_soundAmbient, ///< Ambient sound for unit during normal status. Also the default sound + TTAUDIO_soundAmbientDamaged, ///< Ambient sound for unit if damaged. Corresponds to body info damage + TTAUDIO_soundAmbientReallyDamaged,///< Ambient sound for unit if badly damaged. + TTAUDIO_soundAmbientRubble, ///< Ambient sound for unit if it is currently rubble. (Dam, for instance) + TTAUDIO_soundStealthOn, ///< Sound when unit stealths + TTAUDIO_soundStealthOff, ///< Sound when unit destealths + TTAUDIO_soundCreated, ///< Sound when unit is created + TTAUDIO_soundOnDamaged, ///< Sound when unit enters damaged state + TTAUDIO_soundOnReallyDamaged, ///< Sound when unit enters reallyd damaged state + TTAUDIO_soundEnter, ///< Sound when another unit enters me. + TTAUDIO_soundExit, ///< Sound when another unit exits me. + TTAUDIO_soundPromotedVeteran, ///< Sound when unit gets promoted to Veteran level + TTAUDIO_soundPromotedElite, ///< Sound when unit gets promoted to Elite level + TTAUDIO_soundPromotedHero, ///< Sound when unit gets promoted to Hero level + TTAUDIO_voiceGarrison, ///< Unit is ordered to enter a garrisonable building + TTAUDIO_soundFalling, ///< This sound is actually called on a unit when it is exiting another. + ///< However, there is a soundExit which refers to the container, and this is only used for bombs falling from planes. +#ifdef ALLOW_SURRENDER + TTAUDIO_voiceSurrender, ///< Unit surrenders +#endif + TTAUDIO_voiceDefect, ///< Unit is forced to defect + TTAUDIO_voiceAttackSpecial, ///< Unit is ordered to use a special attack + TTAUDIO_voiceAttackAir, ///< Unit is ordered to attack an airborne unit + TTAUDIO_voiceGuard, ///< Unit is ordered to guard an area + + TTAUDIO_COUNT // keep last! +}; + +class AudioArray +{ +public: + DynamicAudioEventRTS* m_audio[TTAUDIO_COUNT]; + + AudioArray() + { + for (Int i = 0; i < TTAUDIO_COUNT; ++i) + m_audio[i] = NULL; + } + + ~AudioArray() + { + for (Int i = 0; i < TTAUDIO_COUNT; ++i) + if (m_audio[i]) + m_audio[i]->deleteInstance(); + } + + AudioArray(const AudioArray& that) + { + for (Int i = 0; i < TTAUDIO_COUNT; ++i) + { + if (that.m_audio[i]) + m_audio[i] = newInstance(DynamicAudioEventRTS)(*that.m_audio[i]); + else + m_audio[i] = NULL; + } + } + + AudioArray& operator=(const AudioArray& that) + { + if (this != &that) + { + for (Int i = 0; i < TTAUDIO_COUNT; ++i) + { + if (that.m_audio[i]) + { + if (m_audio[i]) + *m_audio[i] = *that.m_audio[i]; + else + m_audio[i] = newInstance(DynamicAudioEventRTS)(*that.m_audio[i]); + } + else + { + m_audio[i] = NULL; + } + } + } + return *this; + } +}; + +//------------------------------------------------------------------------------------------------- +/** Object class type enumeration */ +//------------------------------------------------------------------------------------------------- +enum BuildCompletionType CPP_11(: Int) +{ + BC_INVALID = 0, + BC_APPEARS_AT_RALLY_POINT, ///< unit appears at rally point of its #1 prereq + BC_PLACED_BY_PLAYER, ///< unit must be manually placed by player + + BC_NUM_TYPES // leave this last +}; +#ifdef DEFINE_BUILD_COMPLETION_NAMES +static const char *BuildCompletionNames[] = +{ + "INVALID", + "APPEARS_AT_RALLY_POINT", + "PLACED_BY_PLAYER", + + NULL +}; +#endif // end DEFINE_BUILD_COMPLETION_NAMES + +enum BuildableStatus CPP_11(: Int) +{ + // saved into savegames... do not change or remove values! + BSTATUS_YES = 0, + BSTATUS_IGNORE_PREREQUISITES, + BSTATUS_NO, + BSTATUS_ONLY_BY_AI, + + BSTATUS_NUM_TYPES // leave this last +}; + +#ifdef DEFINE_BUILDABLE_STATUS_NAMES +static const char *BuildableStatusNames[] = +{ + "Yes", + "Ignore_Prerequisites", + "No", + "Only_By_AI", + NULL +}; +#endif // end DEFINE_BUILDABLE_STATUS_NAMES + +enum AmmoPipsStyle CPP_11(: Int) +{ + AMMO_PIPS_DEFAULT = 0, ///< Default style, showing each shot in clip + AMMO_PIPS_BAR, ///< Show percentage bar + AMMO_PIPS_SINGLE, ///< like default, but show a single pip only (full or empty) + + AMMO_PIPS_NUM_TYPES // leave this last +}; +#ifdef DEFINE_AMMO_PIPS_STYLE_NAMES +static const char* AmmoPipsStyleNames[] = +{ + "DEFAULT", + "PERCENTAGE_BAR", + "SINGLE", + + NULL +}; +#endif // end DEFINE_AMMO_PIPS_STYLE_NAMES + +//------------------------------------------------------------------------------------------------- +enum ModuleParseMode CPP_11(: Int) +{ + MODULEPARSE_NORMAL, + MODULEPARSE_ADD_REMOVE_REPLACE, + MODULEPARSE_INHERITABLE, + MODULEPARSE_OVERRIDEABLE_BY_LIKE_KIND, + +}; + +//------------------------------------------------------------------------------------------------- +class ModuleInfo +{ +private: + struct Nugget + { + AsciiString first; + AsciiString m_moduleTag; + const ModuleData* second; + Int interfaceMask; + Bool copiedFromDefault; + Bool inheritable; + Bool overrideableByLikeKind; + + Nugget(const AsciiString& n, const AsciiString& moduleTag, const ModuleData* d, Int i, Bool inh, Bool oblk) + : first(n), + m_moduleTag(moduleTag), + second(d), + interfaceMask(i), + copiedFromDefault(false), + inheritable(inh), + overrideableByLikeKind(oblk) + { + } + + }; + std::vector m_info; + +public: + + ModuleInfo() { } + + void addModuleInfo( ThingTemplate *thingTemplate, const AsciiString& name, const AsciiString& moduleTag, const ModuleData* data, Int interfaceMask, Bool inheritable, Bool overrideableByLikeKind = FALSE ); + const ModuleInfo::Nugget *getNuggetWithTag( const AsciiString& tag ) const; + + Int getCount() const + { + return m_info.size(); + } + #if defined(RTS_DEBUG) || defined(RTS_INTERNAL) - Bool containsPartialName(const char* n) const - { - for (size_t i = 0; i < m_info.size(); i++) - if (strstr(m_info[i].first.str(), n) != NULL) - return true; - return false; - } -#endif - - AsciiString getNthName(size_t i) const - { - if (i >= 0 && i < m_info.size()) - { - return m_info[i].first; - } - return AsciiString::TheEmptyString; - } - - AsciiString getNthTag(size_t i) const - { - if (i >= 0 && i < m_info.size()) - { - return m_info[i].m_moduleTag; - } - return AsciiString::TheEmptyString; - } - - const ModuleData* getNthData(size_t i) const - { - if (i >= 0 && i < m_info.size()) - { - return m_info[i].second; - } - return NULL; - } - - // for use only by ThingTemplate::friend_getAIModuleInfo - ModuleData* friend_getNthData(Int i); - - void clear() - { - m_info.clear(); - } - - void setCopiedFromDefault(Bool v) - { - for (size_t i = 0; i < m_info.size(); i++) - m_info[i].copiedFromDefault = v; - } - - Bool clearModuleDataWithTag(const AsciiString& tagToClear, AsciiString& clearedModuleNameOut); - Bool clearCopiedFromDefaultEntries(Int interfaceMask, const AsciiString &name, const ThingTemplate *fullTemplate ); - Bool clearAiModuleInfo(); -}; - -//------------------------------------------------------------------------------------------------- -/** Definition of a thing template to read from our game data framework */ -//------------------------------------------------------------------------------------------------- -class ThingTemplate : public Overridable -{ - - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(ThingTemplate, "ThingTemplatePool" ) - -private: - -#if defined(_MSC_VER) && _MSC_VER < 1300 - ThingTemplate(const ThingTemplate& that) : m_geometryInfo(that.m_geometryInfo) - { - DEBUG_CRASH(("This should never be called\n")); - } -#else - ThingTemplate(const ThingTemplate& that) = delete; -#endif - -public: - - - ThingTemplate(); - - // copy the guts of that into this, but preserve this' name, id, and list-links. - void copyFrom(const ThingTemplate* that); - - /// called by ThingFactory after all templates have been loaded. - void resolveNames(); - -#ifdef LOAD_TEST_ASSETS - void initForLTA(const AsciiString& name); - inline AsciiString getLTAName() const { return m_LTAName; } -#endif - - /** - return a unique identifier suitable for identifying this ThingTemplate on machines playing - across the net. this should be considered a Magic Cookie and used only for net traffic or - similar sorts of things. To convert an id back to a ThingTemplate, use ThingFactory::findByID(). - Note that 0 is always an invalid id. NOTE that we are not referencing m_override here - because even though we actually have multiple templates here representing overrides, - we still only conceptually have one template and want to always use one single - pointer for comparisons of templates. However, even if we did reference m_override - the IDs would be the same for each one since every override first *COPIES* data - from the current/parent template data. - */ - UnsignedShort getTemplateID() const { return m_templateID; } - - // note that m_override is not used here, see getTemplateID(), for it is the same reasons - const AsciiString& getName() const { return m_nameString; } ///< return the name of this template - - /// get the display color (used for the editor) - Color getDisplayColor() const { return m_displayColor; } - - /// get the editor sorting - EditorSortingType getEditorSorting() const { return (EditorSortingType)m_editorSorting; } - - /// return true iff the template has the specified kindOf flag set. - inline Bool isKindOf(KindOfType t) const - { - return TEST_KINDOFMASK(m_kindof, t); - } - - /// convenience for doing multiple kindof testing at once. - inline Bool isKindOfMulti(const KindOfMaskType& mustBeSet, const KindOfMaskType& mustBeClear) const - { - return TEST_KINDOFMASK_MULTI(m_kindof, mustBeSet, mustBeClear); - } - - inline Bool isAnyKindOf( const KindOfMaskType& anyKindOf ) const - { - return TEST_KINDOFMASK_ANY(m_kindof, anyKindOf); - } - - /// set the display name - const UnicodeString& getDisplayName() const { return m_displayName; } ///< return display name - - RadarPriorityType getDefaultRadarPriority() const { return (RadarPriorityType)m_radarPriority; } ///< return radar priority from INI - - AmmoPipsStyle getAmmoPipsStyle() const { return (AmmoPipsStyle)m_ammoPipsStyle; } ///< return ammo pips style from ini - - - // note, you should not call this directly; rather, call Object::getTransportSlotCount(). - Int getRawTransportSlotCount() const { return m_transportSlotCount; } - - Real getFenceWidth() const { return m_fenceWidth; } // return fence width - - Real getFenceXOffset() const { return m_fenceXOffset; } // return fence offset - - Bool isBridge() const { return m_isBridge; } // return fence offset - - // Only Object can ask this. Everyone else should ask the Object. In fact, you really should ask the Object everything. - Real friend_calcVisionRange() const { return m_visionRange; } ///< get vision range - Real friend_calcShroudClearingRange() const { return m_shroudClearingRange; } ///< get vision range for Shroud ONLY (Design requested split) - - //This one is okay to check directly... because it doesn't get effected by bonuses. - Real getShroudRevealToAllRange() const { return m_shroudRevealToAllRange; } - - // This function is only for use by the AIUpdateModuleData::parseLocomotorSet function. - AIUpdateModuleData *friend_getAIModuleInfo(void); - - ShadowType getShadowType() const { return (ShadowType)m_shadowType; } - Real getShadowSizeX() const { return m_shadowSizeX; } - Real getShadowSizeY() const { return m_shadowSizeY; } - Real getShadowOffsetX() const { return m_shadowOffsetX; } - Real getShadowOffsetY() const { return m_shadowOffsetY; } - - const AsciiString& getShadowTextureName( void ) const { return m_shadowTextureName; } - UnsignedInt getOcclusionDelay(void) const { return m_occlusionDelay;} - - const ModuleInfo& getBehaviorModuleInfo() const { return m_behaviorModuleInfo; } - const ModuleInfo& getDrawModuleInfo() const { return m_drawModuleInfo; } - const ModuleInfo& getClientUpdateModuleInfo() const { return m_clientUpdateModuleInfo; } - - const Image *getSelectedPortraitImage( void ) const { return m_selectedPortraitImage; } - const Image *getButtonImage( void ) const { return m_buttonImage; } - - //Code renderer handles these states now. - //const AsciiString& getInventoryImageName( InventoryImageType type ) const { return m_inventoryImage[ type ]; } - - Int getSkillPointValue(Int level) const; - - Int getExperienceValue(Int level) const { return m_experienceValues[level]; } - Int getExperienceRequired(Int level) const {return m_experienceRequired[level]; } - Bool isTrainable() const{return m_isTrainable; } - Bool isEnterGuard() const{return m_enterGuard; } - Bool isHijackGuard() const{return m_hijackGuard; } - - const AudioEventRTS *getVoiceSelect() const { return getAudio(TTAUDIO_voiceSelect); } - const AudioEventRTS *getVoiceGroupSelect() const { return getAudio(TTAUDIO_voiceGroupSelect); } - const AudioEventRTS *getVoiceMove() const { return getAudio(TTAUDIO_voiceMove); } - const AudioEventRTS *getVoiceAttack() const { return getAudio(TTAUDIO_voiceAttack); } - const AudioEventRTS *getVoiceEnter() const { return getAudio(TTAUDIO_voiceEnter); } - const AudioEventRTS *getVoiceFear() const { return getAudio(TTAUDIO_voiceFear); } - const AudioEventRTS *getVoiceSelectElite() const { return getAudio(TTAUDIO_voiceSelectElite); } - const AudioEventRTS *getVoiceCreated() const { return getAudio(TTAUDIO_voiceCreated); } - const AudioEventRTS *getVoiceNearEnemy() const { return getAudio(TTAUDIO_voiceNearEnemy); } - const AudioEventRTS *getVoiceTaskUnable() const { return getAudio(TTAUDIO_voiceTaskUnable); } - const AudioEventRTS *getVoiceTaskComplete() const { return getAudio(TTAUDIO_voiceTaskComplete); } - const AudioEventRTS *getVoiceMeetEnemy() const { return getAudio(TTAUDIO_voiceMeetEnemy); } - const AudioEventRTS *getVoiceGarrison() const { return getAudio(TTAUDIO_voiceGarrison); } -#ifdef ALLOW_SURRENDER - const AudioEventRTS *getVoiceSurrender() const { return getAudio(TTAUDIO_voiceSurrender); } -#endif - const AudioEventRTS *getVoiceDefect() const { return getAudio(TTAUDIO_voiceDefect); } - const AudioEventRTS *getVoiceAttackSpecial() const { return getAudio(TTAUDIO_voiceAttackSpecial); } - const AudioEventRTS *getVoiceAttackAir() const { return getAudio(TTAUDIO_voiceAttackAir); } - const AudioEventRTS *getVoiceGuard() const { return getAudio(TTAUDIO_voiceGuard); } - const AudioEventRTS *getSoundMoveStart() const { return getAudio(TTAUDIO_soundMoveStart); } - const AudioEventRTS *getSoundMoveStartDamaged() const { return getAudio(TTAUDIO_soundMoveStartDamaged); } - const AudioEventRTS *getSoundMoveLoop() const { return getAudio(TTAUDIO_soundMoveLoop); } - const AudioEventRTS *getSoundMoveLoopDamaged() const { return getAudio(TTAUDIO_soundMoveLoopDamaged); } - const AudioEventRTS *getSoundAmbient() const { return getAudio(TTAUDIO_soundAmbient); } - const AudioEventRTS *getSoundAmbientDamaged() const { return getAudio(TTAUDIO_soundAmbientDamaged); } - const AudioEventRTS *getSoundAmbientReallyDamaged() const { return getAudio(TTAUDIO_soundAmbientReallyDamaged); } - const AudioEventRTS *getSoundAmbientRubble() const { return getAudio(TTAUDIO_soundAmbientRubble); } - const AudioEventRTS *getSoundStealthOn() const { return getAudio(TTAUDIO_soundStealthOn); } - const AudioEventRTS *getSoundStealthOff() const { return getAudio(TTAUDIO_soundStealthOff); } - const AudioEventRTS *getSoundCreated() const { return getAudio(TTAUDIO_soundCreated); } - const AudioEventRTS *getSoundOnDamaged() const { return getAudio(TTAUDIO_soundOnDamaged); } - const AudioEventRTS *getSoundOnReallyDamaged() const { return getAudio(TTAUDIO_soundOnReallyDamaged); } - const AudioEventRTS *getSoundEnter() const { return getAudio(TTAUDIO_soundEnter); } - const AudioEventRTS *getSoundExit() const { return getAudio(TTAUDIO_soundExit); } - const AudioEventRTS *getSoundPromotedVeteran() const { return getAudio(TTAUDIO_soundPromotedVeteran); } - const AudioEventRTS *getSoundPromotedElite() const { return getAudio(TTAUDIO_soundPromotedElite); } - const AudioEventRTS *getSoundPromotedHero() const { return getAudio(TTAUDIO_soundPromotedHero); } - const AudioEventRTS *getSoundFalling() const { return getAudio(TTAUDIO_soundFalling); } - - Bool hasSoundAmbient() const { return hasAudio(TTAUDIO_soundAmbient); } - - const AudioEventRTS *getPerUnitSound(const AsciiString& soundName) const; - const FXList* getPerUnitFX(const AsciiString& fxName) const; - - UnsignedInt getThreatValue() const { return m_threatValue; } - - //------------------------------------------------------------------------------------------------- - /** If this is not NAMEKEY_INVALID, it indicates that all the templates which return the same name key - * should be counted as the same "type" when looking at getMaxSimultaneousOfType(). For instance, - * a Scud Storm and a Scud Storm rebuild hole will return the same value, so that the player - * can't build another Scud Storm while waiting for the rebuild hole to start rebuilding */ - //------------------------------------------------------------------------------------------------- - NameKeyType getMaxSimultaneousLinkKey() const { return m_maxSimultaneousLinkKey; } - UnsignedInt getMaxSimultaneousOfType() const; - - void validate(); - -// The version that does not take an Object argument is labeled friend for use by WorldBuilder. All game requests -// for CommandSet must use Object::getCommandSetString, as we have two different sources for dynamic answers. - const AsciiString& friend_getCommandSetString() const { return m_commandSetString; } - - const std::vector& getBuildVariations() const { return m_buildVariations; } - - Real getAssetScale() const { return m_assetScale; } ///< return uniform scaling - Real getInstanceScaleFuzziness() const { return m_instanceScaleFuzziness; } ///< return uniform scaling - Real getStructureRubbleHeight() const { return (Real)m_structureRubbleHeight; } ///< return uniform scaling - - /* - NOTE: if you have a Thing, don't call this function; call Thing::getGeometryInfo instead, since - geometry can now vary on a per-object basis. Only call this when you have no Thing around, - and want to get info for the "prototype" (eg, for building new Things)... - */ - const GeometryInfo& getTemplateGeometryInfo() const { return m_geometryInfo; } - - // - // these are intended ONLY for the private use of ThingFactory and do not use - // the m_override pointer, it deals only with templates at the "top" level - // - inline void friend_setTemplateName( const AsciiString& name ) { m_nameString = name; } - inline ThingTemplate *friend_getNextTemplate() const { return m_nextThingTemplate; } - inline void friend_setNextTemplate(ThingTemplate *tmplate) { m_nextThingTemplate = tmplate; } - inline void friend_setTemplateID(UnsignedShort id) { m_templateID = id; } - - Int getEnergyProduction() const { return m_energyProduction; } - Int getEnergyBonus() const { return m_energyBonus; } - - // these are NOT publicly available; you should call calcCostToBuild() or calcTimeToBuild() - // instead, because they will take player handicaps into account. - // Int getBuildCost() const { return m_buildCost; } - - Int getRefundValue() const { return m_refundValue; } - - BuildCompletionType getBuildCompletion() const { return (BuildCompletionType)m_buildCompletion; } - - BuildableStatus getBuildable() const; - - Int getPrereqCount() const { return m_prereqInfo.size(); } - const ProductionPrerequisite *getNthPrereq(Int i) const { return &m_prereqInfo[i]; } - - /** - return the BuildFacilityTemplate, if any. - - if this template needs no build facility, null is returned. - - if the template needs a build facility but the given player doesn't have any in existence, - null will be returned. - - if you pass null for player, we'll return the 'natural' build facility. - */ - const ThingTemplate *getBuildFacilityTemplate( const Player *player ) const; - - Bool isBuildableItem(void) const; - - /// calculate how long (in logic frames) it will take the given player to build this unit - Int calcTimeToBuild( const Player* player) const; - - /// calculate how much money it will take the given player to build this unit - Int calcCostToBuild( const Player* player) const; - - /// Used only by Skirmish AI. Everyone else should call calcCostToBuild. - Int friend_getBuildCost() const { return m_buildCost; } - - const AsciiString& getDefaultOwningSide() const { return m_defaultOwningSide; } - - /// get us the table to parse the fields for thing templates - const FieldParse* getFieldParse() const { return s_objectFieldParseTable; } - const FieldParse* getReskinFieldParse() const { return s_objectReskinFieldParseTable; } - - Bool isBuildFacility() const { return m_isBuildFacility; } - Real getPlacementViewAngle( void ) const { return m_placementViewAngle; } - - Real getFactoryExitWidth() const { return m_factoryExitWidth; } - Real getFactoryExtraBibWidth() const { return m_factoryExtraBibWidth; } - - void setCopiedFromDefault(); - + Bool containsPartialName(const char* n) const + { + for (size_t i = 0; i < m_info.size(); i++) + if (strstr(m_info[i].first.str(), n) != NULL) + return true; + return false; + } +#endif + + AsciiString getNthName(size_t i) const + { + if (i >= 0 && i < m_info.size()) + { + return m_info[i].first; + } + return AsciiString::TheEmptyString; + } + + AsciiString getNthTag(size_t i) const + { + if (i >= 0 && i < m_info.size()) + { + return m_info[i].m_moduleTag; + } + return AsciiString::TheEmptyString; + } + + const ModuleData* getNthData(size_t i) const + { + if (i >= 0 && i < m_info.size()) + { + return m_info[i].second; + } + return NULL; + } + + // for use only by ThingTemplate::friend_getAIModuleInfo + ModuleData* friend_getNthData(Int i); + + void clear() + { + m_info.clear(); + } + + void setCopiedFromDefault(Bool v) + { + for (size_t i = 0; i < m_info.size(); i++) + m_info[i].copiedFromDefault = v; + } + + Bool clearModuleDataWithTag(const AsciiString& tagToClear, AsciiString& clearedModuleNameOut); + Bool clearCopiedFromDefaultEntries(Int interfaceMask, const AsciiString &name, const ThingTemplate *fullTemplate ); + Bool clearAiModuleInfo(); +}; + +//------------------------------------------------------------------------------------------------- +/** Definition of a thing template to read from our game data framework */ +//------------------------------------------------------------------------------------------------- +class ThingTemplate : public Overridable +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(ThingTemplate, "ThingTemplatePool" ) + +private: + +#if defined(_MSC_VER) && _MSC_VER < 1300 + ThingTemplate(const ThingTemplate& that) : m_geometryInfo(that.m_geometryInfo) + { + DEBUG_CRASH(("This should never be called\n")); + } +#else + ThingTemplate(const ThingTemplate& that) = delete; +#endif + +public: + + + ThingTemplate(); + + // copy the guts of that into this, but preserve this' name, id, and list-links. + void copyFrom(const ThingTemplate* that); + + /// called by ThingFactory after all templates have been loaded. + void resolveNames(); + +#ifdef LOAD_TEST_ASSETS + void initForLTA(const AsciiString& name); + inline AsciiString getLTAName() const { return m_LTAName; } +#endif + + /** + return a unique identifier suitable for identifying this ThingTemplate on machines playing + across the net. this should be considered a Magic Cookie and used only for net traffic or + similar sorts of things. To convert an id back to a ThingTemplate, use ThingFactory::findByID(). + Note that 0 is always an invalid id. NOTE that we are not referencing m_override here + because even though we actually have multiple templates here representing overrides, + we still only conceptually have one template and want to always use one single + pointer for comparisons of templates. However, even if we did reference m_override + the IDs would be the same for each one since every override first *COPIES* data + from the current/parent template data. + */ + UnsignedShort getTemplateID() const { return m_templateID; } + + // note that m_override is not used here, see getTemplateID(), for it is the same reasons + const AsciiString& getName() const { return m_nameString; } ///< return the name of this template + + /// get the display color (used for the editor) + Color getDisplayColor() const { return m_displayColor; } + + /// get the editor sorting + EditorSortingType getEditorSorting() const { return (EditorSortingType)m_editorSorting; } + + /// return true iff the template has the specified kindOf flag set. + inline Bool isKindOf(KindOfType t) const + { + return TEST_KINDOFMASK(m_kindof, t); + } + + /// convenience for doing multiple kindof testing at once. + inline Bool isKindOfMulti(const KindOfMaskType& mustBeSet, const KindOfMaskType& mustBeClear) const + { + return TEST_KINDOFMASK_MULTI(m_kindof, mustBeSet, mustBeClear); + } + + inline Bool isAnyKindOf( const KindOfMaskType& anyKindOf ) const + { + return TEST_KINDOFMASK_ANY(m_kindof, anyKindOf); + } + + /// set the display name + const UnicodeString& getDisplayName() const { return m_displayName; } ///< return display name + + RadarPriorityType getDefaultRadarPriority() const { return (RadarPriorityType)m_radarPriority; } ///< return radar priority from INI + + AmmoPipsStyle getAmmoPipsStyle() const { return (AmmoPipsStyle)m_ammoPipsStyle; } ///< return ammo pips style from ini + + + // note, you should not call this directly; rather, call Object::getTransportSlotCount(). + Int getRawTransportSlotCount() const { return m_transportSlotCount; } + + Real getFenceWidth() const { return m_fenceWidth; } // return fence width + + Real getFenceXOffset() const { return m_fenceXOffset; } // return fence offset + + Bool isBridge() const { return m_isBridge; } // return fence offset + + // Only Object can ask this. Everyone else should ask the Object. In fact, you really should ask the Object everything. + Real friend_calcVisionRange() const { return m_visionRange; } ///< get vision range + Real friend_calcShroudClearingRange() const { return m_shroudClearingRange; } ///< get vision range for Shroud ONLY (Design requested split) + + //This one is okay to check directly... because it doesn't get effected by bonuses. + Real getShroudRevealToAllRange() const { return m_shroudRevealToAllRange; } + + // This function is only for use by the AIUpdateModuleData::parseLocomotorSet function. + AIUpdateModuleData *friend_getAIModuleInfo(void); + + ShadowType getShadowType() const { return (ShadowType)m_shadowType; } + Real getShadowSizeX() const { return m_shadowSizeX; } + Real getShadowSizeY() const { return m_shadowSizeY; } + Real getShadowOffsetX() const { return m_shadowOffsetX; } + Real getShadowOffsetY() const { return m_shadowOffsetY; } + + const AsciiString& getShadowTextureName( void ) const { return m_shadowTextureName; } + UnsignedInt getOcclusionDelay(void) const { return m_occlusionDelay;} + + const ModuleInfo& getBehaviorModuleInfo() const { return m_behaviorModuleInfo; } + const ModuleInfo& getDrawModuleInfo() const { return m_drawModuleInfo; } + const ModuleInfo& getClientUpdateModuleInfo() const { return m_clientUpdateModuleInfo; } + + const Image *getSelectedPortraitImage( void ) const { return m_selectedPortraitImage; } + const Image *getButtonImage( void ) const { return m_buttonImage; } + + //Code renderer handles these states now. + //const AsciiString& getInventoryImageName( InventoryImageType type ) const { return m_inventoryImage[ type ]; } + + Int getSkillPointValue(Int level) const; + + Int getExperienceValue(Int level) const { return m_experienceValues[level]; } + Int getExperienceRequired(Int level) const {return m_experienceRequired[level]; } + Bool isTrainable() const{return m_isTrainable; } + Bool isEnterGuard() const{return m_enterGuard; } + Bool isHijackGuard() const{return m_hijackGuard; } + + const AudioEventRTS *getVoiceSelect() const { return getAudio(TTAUDIO_voiceSelect); } + const AudioEventRTS *getVoiceGroupSelect() const { return getAudio(TTAUDIO_voiceGroupSelect); } + const AudioEventRTS *getVoiceMove() const { return getAudio(TTAUDIO_voiceMove); } + const AudioEventRTS *getVoiceAttack() const { return getAudio(TTAUDIO_voiceAttack); } + const AudioEventRTS *getVoiceEnter() const { return getAudio(TTAUDIO_voiceEnter); } + const AudioEventRTS *getVoiceFear() const { return getAudio(TTAUDIO_voiceFear); } + const AudioEventRTS *getVoiceSelectElite() const { return getAudio(TTAUDIO_voiceSelectElite); } + const AudioEventRTS *getVoiceCreated() const { return getAudio(TTAUDIO_voiceCreated); } + const AudioEventRTS *getVoiceNearEnemy() const { return getAudio(TTAUDIO_voiceNearEnemy); } + const AudioEventRTS *getVoiceTaskUnable() const { return getAudio(TTAUDIO_voiceTaskUnable); } + const AudioEventRTS *getVoiceTaskComplete() const { return getAudio(TTAUDIO_voiceTaskComplete); } + const AudioEventRTS *getVoiceMeetEnemy() const { return getAudio(TTAUDIO_voiceMeetEnemy); } + const AudioEventRTS *getVoiceGarrison() const { return getAudio(TTAUDIO_voiceGarrison); } +#ifdef ALLOW_SURRENDER + const AudioEventRTS *getVoiceSurrender() const { return getAudio(TTAUDIO_voiceSurrender); } +#endif + const AudioEventRTS *getVoiceDefect() const { return getAudio(TTAUDIO_voiceDefect); } + const AudioEventRTS *getVoiceAttackSpecial() const { return getAudio(TTAUDIO_voiceAttackSpecial); } + const AudioEventRTS *getVoiceAttackAir() const { return getAudio(TTAUDIO_voiceAttackAir); } + const AudioEventRTS *getVoiceGuard() const { return getAudio(TTAUDIO_voiceGuard); } + const AudioEventRTS *getSoundMoveStart() const { return getAudio(TTAUDIO_soundMoveStart); } + const AudioEventRTS *getSoundMoveStartDamaged() const { return getAudio(TTAUDIO_soundMoveStartDamaged); } + const AudioEventRTS *getSoundMoveLoop() const { return getAudio(TTAUDIO_soundMoveLoop); } + const AudioEventRTS *getSoundMoveLoopDamaged() const { return getAudio(TTAUDIO_soundMoveLoopDamaged); } + const AudioEventRTS *getSoundAmbient() const { return getAudio(TTAUDIO_soundAmbient); } + const AudioEventRTS *getSoundAmbientDamaged() const { return getAudio(TTAUDIO_soundAmbientDamaged); } + const AudioEventRTS *getSoundAmbientReallyDamaged() const { return getAudio(TTAUDIO_soundAmbientReallyDamaged); } + const AudioEventRTS *getSoundAmbientRubble() const { return getAudio(TTAUDIO_soundAmbientRubble); } + const AudioEventRTS *getSoundStealthOn() const { return getAudio(TTAUDIO_soundStealthOn); } + const AudioEventRTS *getSoundStealthOff() const { return getAudio(TTAUDIO_soundStealthOff); } + const AudioEventRTS *getSoundCreated() const { return getAudio(TTAUDIO_soundCreated); } + const AudioEventRTS *getSoundOnDamaged() const { return getAudio(TTAUDIO_soundOnDamaged); } + const AudioEventRTS *getSoundOnReallyDamaged() const { return getAudio(TTAUDIO_soundOnReallyDamaged); } + const AudioEventRTS *getSoundEnter() const { return getAudio(TTAUDIO_soundEnter); } + const AudioEventRTS *getSoundExit() const { return getAudio(TTAUDIO_soundExit); } + const AudioEventRTS *getSoundPromotedVeteran() const { return getAudio(TTAUDIO_soundPromotedVeteran); } + const AudioEventRTS *getSoundPromotedElite() const { return getAudio(TTAUDIO_soundPromotedElite); } + const AudioEventRTS *getSoundPromotedHero() const { return getAudio(TTAUDIO_soundPromotedHero); } + const AudioEventRTS *getSoundFalling() const { return getAudio(TTAUDIO_soundFalling); } + + Bool hasSoundAmbient() const { return hasAudio(TTAUDIO_soundAmbient); } + + const AudioEventRTS *getPerUnitSound(const AsciiString& soundName) const; + const FXList* getPerUnitFX(const AsciiString& fxName) const; + + UnsignedInt getThreatValue() const { return m_threatValue; } + + //------------------------------------------------------------------------------------------------- + /** If this is not NAMEKEY_INVALID, it indicates that all the templates which return the same name key + * should be counted as the same "type" when looking at getMaxSimultaneousOfType(). For instance, + * a Scud Storm and a Scud Storm rebuild hole will return the same value, so that the player + * can't build another Scud Storm while waiting for the rebuild hole to start rebuilding */ + //------------------------------------------------------------------------------------------------- + NameKeyType getMaxSimultaneousLinkKey() const { return m_maxSimultaneousLinkKey; } + UnsignedInt getMaxSimultaneousOfType() const; + + void validate(); + +// The version that does not take an Object argument is labeled friend for use by WorldBuilder. All game requests +// for CommandSet must use Object::getCommandSetString, as we have two different sources for dynamic answers. + const AsciiString& friend_getCommandSetString() const { return m_commandSetString; } + + const std::vector& getBuildVariations() const { return m_buildVariations; } + + Real getAssetScale() const { return m_assetScale; } ///< return uniform scaling + Real getInstanceScaleFuzziness() const { return m_instanceScaleFuzziness; } ///< return uniform scaling + Real getStructureRubbleHeight() const { return (Real)m_structureRubbleHeight; } ///< return uniform scaling + + /* + NOTE: if you have a Thing, don't call this function; call Thing::getGeometryInfo instead, since + geometry can now vary on a per-object basis. Only call this when you have no Thing around, + and want to get info for the "prototype" (eg, for building new Things)... + */ + const GeometryInfo& getTemplateGeometryInfo() const { return m_geometryInfo; } + + // + // these are intended ONLY for the private use of ThingFactory and do not use + // the m_override pointer, it deals only with templates at the "top" level + // + inline void friend_setTemplateName( const AsciiString& name ) { m_nameString = name; } + inline ThingTemplate *friend_getNextTemplate() const { return m_nextThingTemplate; } + inline void friend_setNextTemplate(ThingTemplate *tmplate) { m_nextThingTemplate = tmplate; } + inline void friend_setTemplateID(UnsignedShort id) { m_templateID = id; } + + Int getEnergyProduction() const { return m_energyProduction; } + Int getEnergyBonus() const { return m_energyBonus; } + + // these are NOT publicly available; you should call calcCostToBuild() or calcTimeToBuild() + // instead, because they will take player handicaps into account. + // Int getBuildCost() const { return m_buildCost; } + + Int getRefundValue() const { return m_refundValue; } + + BuildCompletionType getBuildCompletion() const { return (BuildCompletionType)m_buildCompletion; } + + BuildableStatus getBuildable() const; + + Int getPrereqCount() const { return m_prereqInfo.size(); } + const ProductionPrerequisite *getNthPrereq(Int i) const { return &m_prereqInfo[i]; } + + /** + return the BuildFacilityTemplate, if any. + + if this template needs no build facility, null is returned. + + if the template needs a build facility but the given player doesn't have any in existence, + null will be returned. + + if you pass null for player, we'll return the 'natural' build facility. + */ + const ThingTemplate *getBuildFacilityTemplate( const Player *player ) const; + + Bool isBuildableItem(void) const; + + /// calculate how long (in logic frames) it will take the given player to build this unit + Int calcTimeToBuild( const Player* player) const; + + /// calculate how much money it will take the given player to build this unit + Int calcCostToBuild( const Player* player) const; + + /// Used only by Skirmish AI. Everyone else should call calcCostToBuild. + Int friend_getBuildCost() const { return m_buildCost; } + + const AsciiString& getDefaultOwningSide() const { return m_defaultOwningSide; } + + /// get us the table to parse the fields for thing templates + const FieldParse* getFieldParse() const { return s_objectFieldParseTable; } + const FieldParse* getReskinFieldParse() const { return s_objectReskinFieldParseTable; } + + Bool isBuildFacility() const { return m_isBuildFacility; } + Real getPlacementViewAngle( void ) const { return m_placementViewAngle; } + + Real getFactoryExitWidth() const { return m_factoryExitWidth; } + Real getFactoryExtraBibWidth() const { return m_factoryExtraBibWidth; } + + void setCopiedFromDefault(); + // Only set non removable modules as copied when using ObjectExtend void setCopiedFromDefaultExtended(); - void setReskinnedFrom(const ThingTemplate* tt) { DEBUG_ASSERTCRASH(m_reskinnedFrom == NULL, ("should be null")); m_reskinnedFrom = tt; } - - Bool isPrerequisite() const { return m_isPrerequisite; } - - const WeaponTemplateSet* findWeaponTemplateSet(const WeaponSetFlags& t) const; - const ArmorTemplateSet* findArmorTemplateSet(const ArmorSetFlags& t) const; - - // returns true iff we have at least one weaponset that contains a weapon. - // returns false if we have no weaponsets, or they are all empty. - Bool canPossiblyHaveAnyWeapon() const; - - Bool isEquivalentTo(const ThingTemplate* tt) const; - - UnsignedByte getCrushableLevel() const { return m_crushableLevel; } - UnsignedByte getCrusherLevel() const { return m_crusherLevel; } - - AsciiString getUpgradeCameoName( Int n)const{ return m_upgradeCameoUpgradeNames[n]; } - - const WeaponTemplateSetVector& getWeaponTemplateSets(void) const {return m_weaponTemplateSets;} - -protected: - - // - // these are NOT publicly available; you should call calcCostToBuild() or calcTimeToBuild() - // instead, because they will take player handicaps into account. - // - Int getBuildCost() const { return m_buildCost; } - Real getBuildTime() const { return m_buildTime; } - const PerUnitSoundMap* getAllPerUnitSounds( void ) const { return &m_perUnitSounds; } - void validateAudio(); - const AudioEventRTS* getAudio(ThingTemplateAudioType t) const { return m_audioarray.m_audio[t] ? &m_audioarray.m_audio[t]->m_event : &s_audioEventNoSound; } - Bool hasAudio(ThingTemplateAudioType t) const { return m_audioarray.m_audio[t] != NULL; } - - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - /** Table for parsing the object fields */ - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - static void parseArmorTemplateSet( INI* ini, void *instance, void *store, const void* /*userData*/ ); - static void parseWeaponTemplateSet( INI* ini, void *instance, void *store, const void* /*userData*/ ); - static void parsePrerequisites( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ ); - static void parseModuleName(INI* ini, void *instance, void* /*store*/, const void* userData); - static void parseIntList(INI* ini, void *instance, void* store, const void* userData); - - static void parsePerUnitSounds(INI* ini, void *instance, void* store, const void* userData); - static void parsePerUnitFX(INI* ini, void *instance, void* store, const void* userData); - - static void parseAddModule(INI *ini, void *instance, void *store, const void *userData); - static void parseRemoveModule(INI *ini, void *instance, void *store, const void *userData); - static void parseReplaceModule(INI *ini, void *instance, void *store, const void *userData); - static void parseInheritableModule(INI *ini, void *instance, void *store, const void *userData); - static void OverrideableByLikeKind(INI *ini, void *instance, void *store, const void *userData); - - static void parseMaxSimultaneous(INI *ini, void *instance, void *store, const void *userData); - - Bool removeModuleInfo(const AsciiString& moduleToRemove, AsciiString& clearedModuleNameOut); - -private: - static const FieldParse s_objectFieldParseTable[]; ///< the parse table - static const FieldParse s_objectReskinFieldParseTable[]; ///< the parse table - static AudioEventRTS s_audioEventNoSound; - -private: - - // ---- Strings - UnicodeString m_displayName; ///< UI display for onscreen display - AsciiString m_nameString; ///< name of this thing template - AsciiString m_defaultOwningSide; ///< default owning side (owning player is inferred) - AsciiString m_commandSetString; - AsciiString m_selectedPortraitImageName; - AsciiString m_buttonImageName; - AsciiString m_upgradeCameoUpgradeNames[MAX_UPGRADE_CAMEO_UPGRADES]; ///< Use these to find the upgrade images to display on the control bar - AsciiString m_shadowTextureName; ///< name of texture to use for shadow decal - AsciiString m_moduleBeingReplacedName; ///< used only during map.ini loading... name (not tag) of Module being replaced, or empty if not inside ReplaceModule block - AsciiString m_moduleBeingReplacedTag; ///< used only during map.ini loading... tag (not name) of Module being replaced, or empty if not inside ReplaceModule block -#ifdef LOAD_TEST_ASSETS - AsciiString m_LTAName; -#endif - - // ---- Misc Larger-than-int things - GeometryInfo m_geometryInfo; ///< geometry information - KindOfMaskType m_kindof; ///< kindof bits - AudioArray m_audioarray; - ModuleInfo m_behaviorModuleInfo; - ModuleInfo m_drawModuleInfo; - ModuleInfo m_clientUpdateModuleInfo; - - // ---- Misc Arrays-of-things - Int m_skillPointValues[LEVEL_COUNT]; - Int m_experienceValues[LEVEL_COUNT]; ///< How much I am worth at each experience level - Int m_experienceRequired[LEVEL_COUNT]; ///< How many experience points I need for each level - - //Code renderer handles these states now. - //AsciiString m_inventoryImage[ INV_IMAGE_NUM_IMAGES ]; ///< portrait inventory pictures - - // ---- STL-sized things - std::vector m_prereqInfo; ///< the unit Prereqs for this tech - std::vector m_buildVariations; /**< if we build a unit of this type via script or ui, randomly choose one - of these templates instead. (doesn't apply to MapObject-created items) */ - WeaponTemplateSetVector m_weaponTemplateSets; ///< our weaponsets - WeaponTemplateSetFinder m_weaponTemplateSetFinder; ///< helper to allow us to find the best sets, quickly - ArmorTemplateSetVector m_armorTemplateSets; ///< our armorsets - ArmorTemplateSetFinder m_armorTemplateSetFinder; ///< helper to allow us to find the best sets, quickly - PerUnitSoundMap m_perUnitSounds; ///< An additional set of sounds that only apply for this template. - PerUnitFXMap m_perUnitFX; ///< An additional set of fx that only apply for this template. - - // ---- Pointer-sized things - ThingTemplate* m_nextThingTemplate; - const ThingTemplate* m_reskinnedFrom; ///< non NULL if we were generated via a reskin - const Image * m_selectedPortraitImage; /// portrait image when selected (to display in GUI) - const Image * m_buttonImage; - - // ---- Real-sized things - Real m_fenceWidth; ///< Fence width for fence type objects. - Real m_fenceXOffset; ///< Fence X offset for fence type objects. - Real m_visionRange; ///< object "sees" this far around itself - Real m_shroudClearingRange; ///< Since So many things got added to "Seeing" functionality, we need to split this part out. - Real m_shroudRevealToAllRange; ///< When > zero, the shroud gets revealed to all players. - Real m_placementViewAngle; ///< when placing buildings this will be the angle of the building when "floating" at the mouse - Real m_factoryExitWidth; ///< when placing buildings this will be the width of the reserved exit area on the right side. - Real m_factoryExtraBibWidth; ///< when placing buildings this will be the width of the reserved exit area on the right side. - Real m_buildTime; ///< Seconds to build - Real m_assetScale; - Real m_instanceScaleFuzziness; ///< scale randomization tolerance to init for each Drawable instance, - Real m_shadowSizeX; ///< world-space extent of decal shadow texture - Real m_shadowSizeY; ///< world-space extent of decal shadow texture - Real m_shadowOffsetX; ///< world-space offset of decal shadow texture - Real m_shadowOffsetY; ///< world-space offset of decal shadow texture - - // ---- Int-sized things - Int m_energyProduction; ///< how much Energy this takes (negative values produce Energy, rather than consuming it) - Int m_energyBonus; ///< how much extra Energy this produces due to the upgrade - Color m_displayColor; ///< for the editor display color - UnsignedInt m_occlusionDelay; ///< delay after object creation before building occlusion is allowed. - NameKeyType m_maxSimultaneousLinkKey; ///< If this is not NAMEKEY_INVALID, it indicates that all the templates which have the same name key should be counted as the same "type" when looking at getMaxSimultaneousOfType(). - - // ---- Short-sized things - UnsignedShort m_templateID; ///< id for net (etc.) transmission purposes - UnsignedShort m_buildCost; ///< money to build (0 == not buildable) - UnsignedShort m_refundValue; ///< custom resale value, if sold. (0 == use default) - UnsignedShort m_threatValue; ///< Threat map info - UnsignedShort m_maxSimultaneousOfType; ///< max simultaneous of this unit we can have (per player) at one time. (0 == unlimited) - - // ---- Bool-sized things - Bool m_maxSimultaneousDeterminedBySuperweaponRestriction; ///< If true, override value in m_maxSimultaneousOfType with value from GameInfo::getSuperweaponRestriction() - Bool m_isPrerequisite; ///< Is this thing considered in a prerequisite for any other thing? - Bool m_isBridge; ///< True if this model is a bridge. - Bool m_isBuildFacility; ///< is this the build facility for something? (calculated based on other template's prereqs) - Bool m_isTrainable; ///< Whether or not I can even gain experience - Bool m_enterGuard; ///< Whether or not I can enter objects when guarding - Bool m_hijackGuard; ///< Whether or not I can hijack objects when guarding - Bool m_isForbidden; ///< useful when overriding in .ini - Bool m_armorCopiedFromDefault; - Bool m_weaponsCopiedFromDefault; - - // ---- Byte-sized things - Byte m_radarPriority; ///< does object appear on radar, and if so at what priority - Byte m_transportSlotCount; ///< how many "slots" we take in a transport (0 == not transportable) - Byte m_buildable; ///< is this thing buildable at all? - Byte m_buildCompletion; ///< how the units come into the world when build is complete - Byte m_editorSorting; ///< editor sorting type, see EditorSortingType enum - Byte m_structureRubbleHeight; - Byte m_shadowType; ///< settings which determine the type of shadow rendered - Byte m_moduleParsingMode; - UnsignedByte m_crusherLevel; ///< crusher > crushable level to actually crush - UnsignedByte m_crushableLevel; ///< Specifies the level of crushability (must be hit by a crusher greater than this to crush me). - Byte m_ammoPipsStyle; ///< How ammo pips are displayed for this thing - -}; - -//----------------------------------------------------------------------------- -// Inlining -//----------------------------------------------------------------------------- - -//----------------------------------------------------------------------------- -// Externals -//----------------------------------------------------------------------------- - -#endif // __THINGTEMPLATE_H_ - + void setReskinnedFrom(const ThingTemplate* tt) { DEBUG_ASSERTCRASH(m_reskinnedFrom == NULL, ("should be null")); m_reskinnedFrom = tt; } + + Bool isPrerequisite() const { return m_isPrerequisite; } + + const WeaponTemplateSet* findWeaponTemplateSet(const WeaponSetFlags& t) const; + const ArmorTemplateSet* findArmorTemplateSet(const ArmorSetFlags& t) const; + + // returns true iff we have at least one weaponset that contains a weapon. + // returns false if we have no weaponsets, or they are all empty. + Bool canPossiblyHaveAnyWeapon() const; + + Bool isEquivalentTo(const ThingTemplate* tt) const; + + UnsignedByte getCrushableLevel() const { return m_crushableLevel; } + UnsignedByte getCrusherLevel() const { return m_crusherLevel; } + + AsciiString getUpgradeCameoName( Int n)const{ return m_upgradeCameoUpgradeNames[n]; } + + const WeaponTemplateSetVector& getWeaponTemplateSets(void) const {return m_weaponTemplateSets;} + +protected: + + // + // these are NOT publicly available; you should call calcCostToBuild() or calcTimeToBuild() + // instead, because they will take player handicaps into account. + // + Int getBuildCost() const { return m_buildCost; } + Real getBuildTime() const { return m_buildTime; } + const PerUnitSoundMap* getAllPerUnitSounds( void ) const { return &m_perUnitSounds; } + void validateAudio(); + const AudioEventRTS* getAudio(ThingTemplateAudioType t) const { return m_audioarray.m_audio[t] ? &m_audioarray.m_audio[t]->m_event : &s_audioEventNoSound; } + Bool hasAudio(ThingTemplateAudioType t) const { return m_audioarray.m_audio[t] != NULL; } + + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + /** Table for parsing the object fields */ + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + static void parseArmorTemplateSet( INI* ini, void *instance, void *store, const void* /*userData*/ ); + static void parseWeaponTemplateSet( INI* ini, void *instance, void *store, const void* /*userData*/ ); + static void parsePrerequisites( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ ); + static void parseModuleName(INI* ini, void *instance, void* /*store*/, const void* userData); + static void parseIntList(INI* ini, void *instance, void* store, const void* userData); + + static void parsePerUnitSounds(INI* ini, void *instance, void* store, const void* userData); + static void parsePerUnitFX(INI* ini, void *instance, void* store, const void* userData); + + static void parseAddModule(INI *ini, void *instance, void *store, const void *userData); + static void parseRemoveModule(INI *ini, void *instance, void *store, const void *userData); + static void parseReplaceModule(INI *ini, void *instance, void *store, const void *userData); + static void parseInheritableModule(INI *ini, void *instance, void *store, const void *userData); + static void OverrideableByLikeKind(INI *ini, void *instance, void *store, const void *userData); + + static void parseMaxSimultaneous(INI *ini, void *instance, void *store, const void *userData); + + Bool removeModuleInfo(const AsciiString& moduleToRemove, AsciiString& clearedModuleNameOut); + +private: + static const FieldParse s_objectFieldParseTable[]; ///< the parse table + static const FieldParse s_objectReskinFieldParseTable[]; ///< the parse table + static AudioEventRTS s_audioEventNoSound; + +private: + + // ---- Strings + UnicodeString m_displayName; ///< UI display for onscreen display + AsciiString m_nameString; ///< name of this thing template + AsciiString m_defaultOwningSide; ///< default owning side (owning player is inferred) + AsciiString m_commandSetString; + AsciiString m_selectedPortraitImageName; + AsciiString m_buttonImageName; + AsciiString m_upgradeCameoUpgradeNames[MAX_UPGRADE_CAMEO_UPGRADES]; ///< Use these to find the upgrade images to display on the control bar + AsciiString m_shadowTextureName; ///< name of texture to use for shadow decal + AsciiString m_moduleBeingReplacedName; ///< used only during map.ini loading... name (not tag) of Module being replaced, or empty if not inside ReplaceModule block + AsciiString m_moduleBeingReplacedTag; ///< used only during map.ini loading... tag (not name) of Module being replaced, or empty if not inside ReplaceModule block +#ifdef LOAD_TEST_ASSETS + AsciiString m_LTAName; +#endif + + // ---- Misc Larger-than-int things + GeometryInfo m_geometryInfo; ///< geometry information + KindOfMaskType m_kindof; ///< kindof bits + AudioArray m_audioarray; + ModuleInfo m_behaviorModuleInfo; + ModuleInfo m_drawModuleInfo; + ModuleInfo m_clientUpdateModuleInfo; + + // ---- Misc Arrays-of-things + Int m_skillPointValues[LEVEL_COUNT]; + Int m_experienceValues[LEVEL_COUNT]; ///< How much I am worth at each experience level + Int m_experienceRequired[LEVEL_COUNT]; ///< How many experience points I need for each level + + //Code renderer handles these states now. + //AsciiString m_inventoryImage[ INV_IMAGE_NUM_IMAGES ]; ///< portrait inventory pictures + + // ---- STL-sized things + std::vector m_prereqInfo; ///< the unit Prereqs for this tech + std::vector m_buildVariations; /**< if we build a unit of this type via script or ui, randomly choose one + of these templates instead. (doesn't apply to MapObject-created items) */ + WeaponTemplateSetVector m_weaponTemplateSets; ///< our weaponsets + WeaponTemplateSetFinder m_weaponTemplateSetFinder; ///< helper to allow us to find the best sets, quickly + ArmorTemplateSetVector m_armorTemplateSets; ///< our armorsets + ArmorTemplateSetFinder m_armorTemplateSetFinder; ///< helper to allow us to find the best sets, quickly + PerUnitSoundMap m_perUnitSounds; ///< An additional set of sounds that only apply for this template. + PerUnitFXMap m_perUnitFX; ///< An additional set of fx that only apply for this template. + + // ---- Pointer-sized things + ThingTemplate* m_nextThingTemplate; + const ThingTemplate* m_reskinnedFrom; ///< non NULL if we were generated via a reskin + const Image * m_selectedPortraitImage; /// portrait image when selected (to display in GUI) + const Image * m_buttonImage; + + // ---- Real-sized things + Real m_fenceWidth; ///< Fence width for fence type objects. + Real m_fenceXOffset; ///< Fence X offset for fence type objects. + Real m_visionRange; ///< object "sees" this far around itself + Real m_shroudClearingRange; ///< Since So many things got added to "Seeing" functionality, we need to split this part out. + Real m_shroudRevealToAllRange; ///< When > zero, the shroud gets revealed to all players. + Real m_placementViewAngle; ///< when placing buildings this will be the angle of the building when "floating" at the mouse + Real m_factoryExitWidth; ///< when placing buildings this will be the width of the reserved exit area on the right side. + Real m_factoryExtraBibWidth; ///< when placing buildings this will be the width of the reserved exit area on the right side. + Real m_buildTime; ///< Seconds to build + Real m_assetScale; + Real m_instanceScaleFuzziness; ///< scale randomization tolerance to init for each Drawable instance, + Real m_shadowSizeX; ///< world-space extent of decal shadow texture + Real m_shadowSizeY; ///< world-space extent of decal shadow texture + Real m_shadowOffsetX; ///< world-space offset of decal shadow texture + Real m_shadowOffsetY; ///< world-space offset of decal shadow texture + + // ---- Int-sized things + Int m_energyProduction; ///< how much Energy this takes (negative values produce Energy, rather than consuming it) + Int m_energyBonus; ///< how much extra Energy this produces due to the upgrade + Color m_displayColor; ///< for the editor display color + UnsignedInt m_occlusionDelay; ///< delay after object creation before building occlusion is allowed. + NameKeyType m_maxSimultaneousLinkKey; ///< If this is not NAMEKEY_INVALID, it indicates that all the templates which have the same name key should be counted as the same "type" when looking at getMaxSimultaneousOfType(). + + // ---- Short-sized things + UnsignedShort m_templateID; ///< id for net (etc.) transmission purposes + UnsignedShort m_buildCost; ///< money to build (0 == not buildable) + UnsignedShort m_refundValue; ///< custom resale value, if sold. (0 == use default) + UnsignedShort m_threatValue; ///< Threat map info + UnsignedShort m_maxSimultaneousOfType; ///< max simultaneous of this unit we can have (per player) at one time. (0 == unlimited) + + // ---- Bool-sized things + Bool m_maxSimultaneousDeterminedBySuperweaponRestriction; ///< If true, override value in m_maxSimultaneousOfType with value from GameInfo::getSuperweaponRestriction() + Bool m_isPrerequisite; ///< Is this thing considered in a prerequisite for any other thing? + Bool m_isBridge; ///< True if this model is a bridge. + Bool m_isBuildFacility; ///< is this the build facility for something? (calculated based on other template's prereqs) + Bool m_isTrainable; ///< Whether or not I can even gain experience + Bool m_enterGuard; ///< Whether or not I can enter objects when guarding + Bool m_hijackGuard; ///< Whether or not I can hijack objects when guarding + Bool m_isForbidden; ///< useful when overriding in .ini + Bool m_armorCopiedFromDefault; + Bool m_weaponsCopiedFromDefault; + + // ---- Byte-sized things + Byte m_radarPriority; ///< does object appear on radar, and if so at what priority + Byte m_transportSlotCount; ///< how many "slots" we take in a transport (0 == not transportable) + Byte m_buildable; ///< is this thing buildable at all? + Byte m_buildCompletion; ///< how the units come into the world when build is complete + Byte m_editorSorting; ///< editor sorting type, see EditorSortingType enum + Byte m_structureRubbleHeight; + Byte m_shadowType; ///< settings which determine the type of shadow rendered + Byte m_moduleParsingMode; + UnsignedByte m_crusherLevel; ///< crusher > crushable level to actually crush + UnsignedByte m_crushableLevel; ///< Specifies the level of crushability (must be hit by a crusher greater than this to crush me). + Byte m_ammoPipsStyle; ///< How ammo pips are displayed for this thing + +}; + +//----------------------------------------------------------------------------- +// Inlining +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Externals +//----------------------------------------------------------------------------- + +#endif // __THINGTEMPLATE_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/FreeFallProjectileBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/FreeFallProjectileBehavior.h new file mode 100644 index 00000000000..82c15a77c73 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/FreeFallProjectileBehavior.h @@ -0,0 +1,125 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: FreeFallProjectileBehavior.h +// Author: Andi W, June 2025 +// Desc: + +#pragma once + +#ifndef _FreeFallProjectileBehavior_H_ +#define _FreeFallProjectileBehavior_H_ + +#include "Common/GameType.h" +#include "Common/GlobalData.h" +#include "Common/STLTypedefs.h" +#include "GameLogic/Module/BehaviorModule.h" +#include "GameLogic/Module/CollideModule.h" +#include "GameLogic/Module/UpdateModule.h" +#include "GameLogic/WeaponBonusConditionFlags.h" +#include "Common/INI.h" +#include "WWMath/matrix3d.h" + +class ParticleSystem; +class FXList; + + +//------------------------------------------------------------------------------------------------- +class FreeFallProjectileBehaviorModuleData : public UpdateModuleData +{ +public: + /** + These four data define a Bezier curve. The first and last control points are the firer and victim. + */ + + UnsignedInt m_maxLifespan; + Bool m_tumbleRandomly; + Real m_courseCorrectionScalar; + Real m_exitPitchRate; + Bool m_applyLauncherBonus; + // Bool m_inheritTransportVelocity; + Bool m_useWeaponSpeed; + Bool m_detonateCallsKill; + + Bool m_detonateOnGround; + Bool m_detonateOnCollide; + + Int m_garrisonHitKillCount; + KindOfMaskType m_garrisonHitKillKindof; ///< the kind(s) of units that can be collided with + KindOfMaskType m_garrisonHitKillKindofNot; ///< the kind(s) of units that CANNOT be collided with + const FXList* m_garrisonHitKillFX; + + FreeFallProjectileBehaviorModuleData(); + + static void buildFieldParse(MultiIniFieldParse& p); + +}; + +//------------------------------------------------------------------------------------------------- +class FreeFallProjectileBehavior : public UpdateModule, public ProjectileUpdateInterface +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( FreeFallProjectileBehavior, "FreeFallProjectileBehavior" ) + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( FreeFallProjectileBehavior, FreeFallProjectileBehaviorModuleData ); + +public: + + FreeFallProjectileBehavior( Thing *thing, const ModuleData* moduleData ); + // virtual destructor provided by memory pool object + + // UpdateModuleInterface + virtual UpdateSleepTime update(); + virtual ProjectileUpdateInterface* getProjectileUpdateInterface() { return this; } + + // ProjectileUpdateInterface + virtual void projectileLaunchAtObjectOrPosition(const Object *victim, const Coord3D* victimPos, const Object *launcher, WeaponSlotType wslot, Int specificBarrelToUse, const WeaponTemplate* detWeap, const ParticleSystemTemplate* exhaustSysOverride); + virtual void projectileFireAtObjectOrPosition( const Object *victim, const Coord3D *victimPos, const WeaponTemplate *detWeap, const ParticleSystemTemplate* exhaustSysOverride ); + virtual Bool projectileHandleCollision( Object *other ); + virtual Bool projectileIsArmed() const { return true; } + virtual ObjectID projectileGetLauncherID() const { return m_launcherID; } + virtual void setFramesTillCountermeasureDiversionOccurs( UnsignedInt frames ) {} + virtual void projectileNowJammed() {} + virtual Object* getTargetObject(); + virtual const Coord3D* getTargetPosition(); + +protected: + + void positionForLaunch(const Object *launcher, WeaponSlotType wslot, Int specificBarrelToUse); + void detonate(); + +private: + + ObjectID m_launcherID; ///< ID of object that launched us (zero if not yet launched) + ObjectID m_victimID; ///< ID of object we are targeting (zero if not yet launched) + Coord3D m_targetPos; + const WeaponTemplate* m_detonationWeaponTmpl; ///< weapon to fire at end (or null) + UnsignedInt m_lifespanFrame; ///< if we haven't collided by this frame, blow up anyway + WeaponBonusConditionFlags m_extraBonusFlags; + + Bool m_hasDetonated; ///< + +}; + +#endif // _FreeFallProjectileBehavior_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/PhysicsUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/PhysicsUpdate.h index 4210847940b..a7ca3d3b013 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/PhysicsUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/PhysicsUpdate.h @@ -67,6 +67,7 @@ class PhysicsBehaviorModuleData : public UpdateModuleData Real m_fallHeightDamageFactor; Real m_pitchRollYawFactor; Bool m_vehicleCrashAllowAirborne; + Real m_bounceFactor; const WeaponTemplate* m_vehicleCrashesIntoBuildingWeaponTemplate; const WeaponTemplate* m_vehicleCrashesIntoNonBuildingWeaponTemplate; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/FreeFallProjectileBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/FreeFallProjectileBehavior.cpp new file mode 100644 index 00000000000..b55ef3999f0 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/FreeFallProjectileBehavior.cpp @@ -0,0 +1,482 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: FreeFallProjectileBehavior.cpp +// Author: Andi W, June 2025 +// Desc: + +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#include "Common/GameAudio.h" +#include "Common/BezierSegment.h" +#include "Common/GameCommon.h" +#include "Common/GameState.h" +#include "Common/Player.h" +#include "Common/ThingTemplate.h" +#include "Common/RandomValue.h" +#include "Common/Xfer.h" +#include "GameClient/Drawable.h" +#include "GameClient/FXList.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/Object.h" +#include "GameLogic/PartitionManager.h" +#include "GameLogic/Module/ContainModule.h" +#include "GameLogic/Module/FreeFallProjectileBehavior.h" +#include "GameLogic/Module/MissileAIUpdate.h" +#include "GameLogic/Module/PhysicsUpdate.h" +#include "GameLogic/Weapon.h" + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +const Int DEFAULT_MAX_LIFESPAN = 10 * LOGICFRAMES_PER_SECOND; + +//----------------------------------------------------------------------------- +FreeFallProjectileBehaviorModuleData::FreeFallProjectileBehaviorModuleData() : + m_maxLifespan(DEFAULT_MAX_LIFESPAN), + m_detonateCallsKill(FALSE), + m_tumbleRandomly(FALSE), + m_courseCorrectionScalar(1.0f), + m_exitPitchRate(1.0f), + m_applyLauncherBonus(FALSE), + // m_inheritTransportVelocity(FALSE), + m_useWeaponSpeed(FALSE), + m_garrisonHitKillCount(0), + m_garrisonHitKillFX(NULL), + m_detonateOnGround(TRUE), + m_detonateOnCollide(TRUE) +{ +} + +//----------------------------------------------------------------------------- +void FreeFallProjectileBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + UpdateModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + { "MaxLifespan", INI::parseDurationUnsignedInt, NULL, offsetof(FreeFallProjectileBehaviorModuleData, m_maxLifespan) }, + { "TumbleRandomly", INI::parseBool, NULL, offsetof(FreeFallProjectileBehaviorModuleData, m_tumbleRandomly) }, + { "DetonateCallsKill", INI::parseBool, NULL, offsetof(FreeFallProjectileBehaviorModuleData, m_detonateCallsKill) }, + { "CourseCorrectionScalar", INI::parseReal, NULL, offsetof(FreeFallProjectileBehaviorModuleData, m_courseCorrectionScalar) }, + { "ExitPitchRate", INI::parseAngularVelocityReal, NULL, offsetof(FreeFallProjectileBehaviorModuleData, m_exitPitchRate) }, + { "UseWeaponSpeed", INI::parseBool, NULL, offsetof(FreeFallProjectileBehaviorModuleData, m_useWeaponSpeed) }, + //{ "InheritShooterVelocity", INI::parseBool, NULL, offsetof(FreeFallProjectileBehaviorModuleData, m_inheritTransportVelocity) }, + { "ApplyLauncherBonus", INI::parseBool, NULL, offsetof(FreeFallProjectileBehaviorModuleData, m_applyLauncherBonus) }, + + { "DetonateOnGround", INI::parseBool, NULL, offsetof(FreeFallProjectileBehaviorModuleData, m_detonateOnGround) }, + { "DetonateOnCollide", INI::parseBool, NULL, offsetof(FreeFallProjectileBehaviorModuleData, m_detonateOnCollide) }, + + { "GarrisonHitKillRequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof(FreeFallProjectileBehaviorModuleData, m_garrisonHitKillKindof) }, + { "GarrisonHitKillForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof(FreeFallProjectileBehaviorModuleData, m_garrisonHitKillKindofNot) }, + { "GarrisonHitKillCount", INI::parseUnsignedInt, NULL, offsetof(FreeFallProjectileBehaviorModuleData, m_garrisonHitKillCount) }, + { "GarrisonHitKillFX", INI::parseFXList, NULL, offsetof(FreeFallProjectileBehaviorModuleData, m_garrisonHitKillFX) }, + + { 0, 0, 0, 0 } + }; + + p.add(dataFieldParse); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- +FreeFallProjectileBehavior::FreeFallProjectileBehavior(Thing* thing, const ModuleData* moduleData) : UpdateModule(thing, moduleData) +{ + m_launcherID = INVALID_ID; + m_victimID = INVALID_ID; + m_targetPos.zero(); + m_detonationWeaponTmpl = NULL; + m_lifespanFrame = 0; + m_extraBonusFlags = 0; + + m_hasDetonated = FALSE; +} + +//------------------------------------------------------------------------------------------------- +FreeFallProjectileBehavior::~FreeFallProjectileBehavior() +{ +} + + +//------------------------------------------------------------------------------------------------- +// Prepares the missile for launch via proper weapon-system channels. +//------------------------------------------------------------------------------------------------- +void FreeFallProjectileBehavior::projectileLaunchAtObjectOrPosition( + const Object* victim, + const Coord3D* victimPos, + const Object* launcher, + WeaponSlotType wslot, + Int specificBarrelToUse, + const WeaponTemplate* detWeap, + const ParticleSystemTemplate* exhaustSysOverride +) +{ + const FreeFallProjectileBehaviorModuleData* d = getFreeFallProjectileBehaviorModuleData(); + + DEBUG_ASSERTCRASH(specificBarrelToUse >= 0, ("specificBarrelToUse must now be explicit")); + + m_launcherID = launcher ? launcher->getID() : INVALID_ID; + m_extraBonusFlags = launcher ? launcher->getWeaponBonusCondition() : 0; + + if (d->m_applyLauncherBonus && m_extraBonusFlags != 0) { + getObject()->setWeaponBonusConditionFlags(m_extraBonusFlags); + } + + m_victimID = victim ? victim->getID() : INVALID_ID; + m_detonationWeaponTmpl = detWeap; + m_lifespanFrame = TheGameLogic->getFrame() + d->m_maxLifespan; + + Object* projectile = getObject(); + + Weapon::positionProjectileForLaunch(projectile, launcher, wslot, specificBarrelToUse); + + projectileFireAtObjectOrPosition(victim, victimPos, detWeap, exhaustSysOverride); +} + +//------------------------------------------------------------------------------------------------- +// The actual firing of the missile once setup. Uses a Bezier curve with points parameterized in ini +//------------------------------------------------------------------------------------------------- +void FreeFallProjectileBehavior::projectileFireAtObjectOrPosition(const Object* victim, const Coord3D* victimPos, const WeaponTemplate* detWeap, const ParticleSystemTemplate* exhaustSysOverride) +{ + const FreeFallProjectileBehaviorModuleData* d = getFreeFallProjectileBehaviorModuleData(); + Object* projectile = getObject(); + + // if an object, aim at the center, not the ground part + Coord3D victimPosToUse; + if (victim) + victim->getGeometryInfo().getCenterPosition(*victim->getPosition(), victimPosToUse); + else + victimPosToUse = *victimPos; + + m_targetPos = victimPosToUse; + + PhysicsBehavior* physics = projectile->getPhysics(); + if (physics) { + + Real pitchRate = physics->getCenterOfMassOffset() * d->m_exitPitchRate; + + if (d->m_tumbleRandomly) + { + pitchRate += GameLogicRandomValueReal(-1.0f / PI, 1.0f / PI); + physics->setYawRate(GameLogicRandomValueReal(-1.0f / PI, 1.0f / PI)); + physics->setRollRate(GameLogicRandomValueReal(-1.0f / PI, 1.0f / PI)); + } + + physics->setPitchRate(pitchRate); + + // Note: The weapon actually does this already + + //if (d->m_inheritTransportVelocity) + //{ + // Coord3D velocity = *owner->getPhysics()->getVelocity(); + // physics->applyForce(&velocity); + //} + + if (d->m_useWeaponSpeed) { + Real weaponSpeed = detWeap ? detWeap->getWeaponSpeed() : 0.0f; + Real minWeaponSpeed = detWeap ? detWeap->getMinWeaponSpeed() : 0.0f; + + if (detWeap && detWeap->isScaleWeaponSpeed()) + { + // Some weapons want to scale their start speed to the range + Real minRange = detWeap->getMinimumAttackRange(); + Real maxRange = detWeap->getUnmodifiedAttackRange(); + Real range = sqrt(ThePartitionManager->getDistanceSquared(projectile, &victimPosToUse, FROM_CENTER_2D)); + Real rangeRatio = (range - minRange) / (maxRange - minRange); + weaponSpeed = (rangeRatio * (weaponSpeed - minWeaponSpeed)) + minWeaponSpeed; + } + + Coord3D velocity; + projectile->getUnitDirectionVector3D(velocity); + velocity.scale(weaponSpeed); + physics->applyForce(&velocity); + + } + } // If we don't have physics, this module is kinda useless, but whatever + + projectile->setModelConditionState(MODELCONDITION_FREEFALL); + + AudioEventRTS fallingSound = *projectile->getTemplate()->getSoundFalling(); + fallingSound.setObjectID(projectile->getID()); + TheAudio->addAudioEvent(&fallingSound); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool FreeFallProjectileBehavior::projectileHandleCollision(Object* other) +{ + const FreeFallProjectileBehaviorModuleData* d = getFreeFallProjectileBehaviorModuleData(); + + if (other != NULL) + { + Object* projectileLauncher = TheGameLogic->findObjectByID(projectileGetLauncherID()); + + // if it's not the specific thing we were targeting, see if we should incidentally collide... + if (!m_detonationWeaponTmpl->shouldProjectileCollideWith(projectileLauncher, getObject(), other, m_victimID)) + { + //DEBUG_LOG(("ignoring projectile collision with %s at frame %d\n",other->getTemplate()->getName().str(),TheGameLogic->getFrame())); + return true; + } + + if (d->m_garrisonHitKillCount > 0) + { + ContainModuleInterface* contain = other->getContain(); + if (contain && contain->getContainCount() > 0 && contain->isGarrisonable() && !contain->isImmuneToClearBuildingAttacks()) + { + Int numKilled = 0; + + // garrisonable buildings subvert the normal process here. + const ContainedItemsList* items = contain->getContainedItemsList(); + if (items) + { + for (ContainedItemsList::const_iterator it = items->begin(); it != items->end() && numKilled < d->m_garrisonHitKillCount; ) + { + Object* thingToKill = *it++; + if (!thingToKill->isEffectivelyDead() && thingToKill->isKindOfMulti(d->m_garrisonHitKillKindof, d->m_garrisonHitKillKindofNot)) + { + //DEBUG_LOG(("Killed a garrisoned unit (%08lx %s) via Flash-Bang!\n",thingToKill,thingToKill->getTemplate()->getName().str())); + if (projectileLauncher) + projectileLauncher->scoreTheKill(thingToKill); + thingToKill->kill(); + ++numKilled; + } + } // next contained item + } // if items + + if (numKilled > 0) + { + // note, fx is played at center of building, not at grenade's location + FXList::doFXObj(d->m_garrisonHitKillFX, other, NULL); + + // don't do the normal explosion; just destroy ourselves & return + TheGameLogic->destroyObject(getObject()); + + return true; + } + } // if a garrisonable thing + } + + if (!d->m_detonateOnCollide) { + return true; + } + + } + + if (!d->m_detonateOnGround) { + return true; + } + + // collided with something... blow'd up! + detonate(); + + // mark ourself as "no collisions" (since we might still exist in slow death mode) + getObject()->setStatus(MAKE_OBJECT_STATUS_MASK(OBJECT_STATUS_NO_COLLISIONS)); + return true; +} + +//------------------------------------------------------------------------------------------------- +void FreeFallProjectileBehavior::detonate() +{ + if (m_hasDetonated) + return; + + Object* obj = getObject(); + if (m_detonationWeaponTmpl) + { + TheWeaponStore->handleProjectileDetonation(m_detonationWeaponTmpl, obj, obj->getPosition(), m_extraBonusFlags); + + if (getFreeFallProjectileBehaviorModuleData()->m_detonateCallsKill) + { + // don't call kill(); do it manually, so we can specify DEATH_DETONATED + DamageInfo damageInfo; + damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE; + damageInfo.in.m_deathType = DEATH_DETONATED; + damageInfo.in.m_sourceID = INVALID_ID; + damageInfo.in.m_amount = obj->getBodyModule()->getMaxHealth(); + obj->attemptDamage(&damageInfo); + } + else + { + TheGameLogic->destroyObject(obj); + } + + } + else + { + // don't call kill(); do it manually, so we can specify DEATH_DETONATED + DamageInfo damageInfo; + damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE; + damageInfo.in.m_deathType = DEATH_DETONATED; + damageInfo.in.m_sourceID = INVALID_ID; + damageInfo.in.m_amount = obj->getBodyModule()->getMaxHealth(); + obj->attemptDamage(&damageInfo); + } + + if (obj->getDrawable()) + obj->getDrawable()->setDrawableHidden(true); + + m_hasDetonated = TRUE; + +} + +//------------------------------------------------------------------------------------------------- +/** + * Simulate one frame of a missile's behavior + */ +UpdateSleepTime FreeFallProjectileBehavior::update() +{ + const FreeFallProjectileBehaviorModuleData* d = getFreeFallProjectileBehaviorModuleData(); + + if (m_lifespanFrame != 0 && TheGameLogic->getFrame() >= m_lifespanFrame) + { + // lifetime demands detonation + detonate(); + return UPDATE_SLEEP_NONE; + } + + { // SmartBombTargetingUpdate + Object* self = getObject(); + if (!self) + return UPDATE_SLEEP_NONE; + + if (!self->isSignificantlyAboveTerrain()) + return UPDATE_SLEEP_NONE; + + const Coord3D* currentPos = self->getPosition(); + + Coord3D pos; + pos.zero(); + + Real statusCoeff = MAX(0.0f, MIN(1.0f, d->m_courseCorrectionScalar)); + Real targetCoeff = 1.0f - statusCoeff; + + pos.x = m_targetPos.x * targetCoeff + currentPos->x * statusCoeff; + pos.y = m_targetPos.y * targetCoeff + currentPos->y * statusCoeff; + pos.z = currentPos->z; + + self->setPosition(&pos); + } + + + + return UPDATE_SLEEP_NONE;//This no longer flys with physics, so it needs to not sleep +} + +// ------------------------------------------------------------------------------------------------ +const Coord3D* FreeFallProjectileBehavior::getTargetPosition() +{ + return &m_targetPos; +} +// ------------------------------------------------------------------------------------------------ +Object* FreeFallProjectileBehavior::getTargetObject() +{ + return TheGameLogic->findObjectByID(m_victimID); +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void FreeFallProjectileBehavior::crc(Xfer* xfer) +{ + + // extend base class + UpdateModule::crc(xfer); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ + // ------------------------------------------------------------------------------------------------ +void FreeFallProjectileBehavior::xfer(Xfer* xfer) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion(&version, currentVersion); + + // extend base class + UpdateModule::xfer(xfer); + + // launcher + xfer->xferObjectID(&m_launcherID); + + // victim ID + xfer->xferObjectID(&m_victimID); + + // target pos + xfer->xferCoord3D(&m_targetPos); + + // weapon template + AsciiString weaponTemplateName = AsciiString::TheEmptyString; + if (m_detonationWeaponTmpl) + weaponTemplateName = m_detonationWeaponTmpl->getName(); + xfer->xferAsciiString(&weaponTemplateName); + if (xfer->getXferMode() == XFER_LOAD) + { + + if (weaponTemplateName == AsciiString::TheEmptyString) + m_detonationWeaponTmpl = NULL; + else + { + + // find template + m_detonationWeaponTmpl = TheWeaponStore->findWeaponTemplate(weaponTemplateName); + + // sanity + if (m_detonationWeaponTmpl == NULL) + { + + DEBUG_CRASH(("FreeFallProjectileBehavior::xfer - Unknown weapon template '%s'\n", + weaponTemplateName.str())); + throw SC_INVALID_DATA; + + } // end if + + } // end else + + } // end if + + // lifespan frame + // xfer->xferUnsignedInt(&m_lifespanFrame); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void FreeFallProjectileBehavior::loadPostProcess(void) +{ + + // extend base class + UpdateModule::loadPostProcess(); + +} // end loadPostProcess diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index 371fee36fd3..12cd6b7ce1d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp @@ -141,7 +141,7 @@ PhysicsBehaviorModuleData::PhysicsBehaviorModuleData() m_vehicleCrashesIntoBuildingWeaponTemplate = TheWeaponStore->findWeaponTemplate("VehicleCrashesIntoBuildingWeapon"); m_vehicleCrashesIntoNonBuildingWeaponTemplate = TheWeaponStore->findWeaponTemplate("VehicleCrashesIntoNonBuildingWeapon"); m_vehicleCrashAllowAirborne = FALSE; - + m_bounceFactor = 1.0f; } //------------------------------------------------------------------------------------------------- @@ -185,6 +185,8 @@ static void parseFrictionPerSec( INI* ini, void * /*instance*/, void *store, con { "AllowCollideForce", INI::parseBool, NULL, offsetof( PhysicsBehaviorModuleData, m_allowCollideForce ) }, { "KillWhenRestingOnGround", INI::parseBool, NULL, offsetof( PhysicsBehaviorModuleData, m_killWhenRestingOnGround) }, + { "BounceFactor", INI::parseReal, NULL, offsetof( PhysicsBehaviorModuleData, m_bounceFactor) }, + { "MinFallHeightForDamage", parseHeightToSpeed, NULL, offsetof( PhysicsBehaviorModuleData, m_minFallSpeedForDamage) }, { "FallHeightDamageFactor", INI::parseReal, NULL, offsetof( PhysicsBehaviorModuleData, m_fallHeightDamageFactor) }, { "PitchRollYawFactor", INI::parseReal, NULL, offsetof( PhysicsBehaviorModuleData, m_pitchRollYawFactor) }, @@ -508,8 +510,12 @@ Bool PhysicsBehavior::handleBounce(Real oldZ, Real newZ, Real groundZ, Coord3D* if (getFlag(ALLOW_BOUNCE) && newZ <= groundZ) { const Real MIN_STIFF = 0.01f; - const Real MAX_STIFF = 0.99f; + // const Real MAX_STIFF = 0.99f; + const Real MAX_STIFF = 10.0f; // Why not more? :D Real stiffness = TheGlobalData->m_groundStiffness; + + stiffness *= getPhysicsBehaviorModuleData()->m_bounceFactor; + if (stiffness < MIN_STIFF) stiffness = MIN_STIFF; if (stiffness > MAX_STIFF) stiffness = MAX_STIFF; From 64be7f398af379d1fdd0fc022ed65e862cb9f00a Mon Sep 17 00:00:00 2001 From: Andi Date: Sun, 8 Jun 2025 08:41:52 +0200 Subject: [PATCH 18/42] fixed line endings --- .../Object/Update/AIUpdate/JetAIUpdate.cpp | 5306 ++++++++--------- 1 file changed, 2653 insertions(+), 2653 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp index e84bbe419f6..66c80683f4b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp @@ -1,2655 +1,2655 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// JetAIUpdate.cpp ////////// - -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -#define DEFINE_LOCOMOTORSET_NAMES - -#include "Common/ActionManager.h" -#include "Common/GlobalData.h" -#include "Common/MiscAudio.h" -#include "Common/ThingFactory.h" -#include "Common/ThingTemplate.h" -#include "GameClient/Drawable.h" -#include "GameClient/GameClient.h" -#include "GameLogic/ExperienceTracker.h" -#include "GameLogic/Locomotor.h" -#include "GameLogic/Module/BodyModule.h" -#include "GameLogic/Module/CountermeasuresBehavior.h" -#include "GameLogic/Module/JetAIUpdate.h" -#include "GameLogic/Module/ParkingPlaceBehavior.h" -#include "GameLogic/Module/PhysicsUpdate.h" -#include "GameLogic/Object.h" -#include "GameLogic/AIPathfind.h" -#include "GameLogic/PartitionManager.h" -#include "GameLogic/Weapon.h" - -const Real BIGNUM = 99999.0f; - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// JetAIUpdate.cpp ////////// + +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#define DEFINE_LOCOMOTORSET_NAMES + +#include "Common/ActionManager.h" +#include "Common/GlobalData.h" +#include "Common/MiscAudio.h" +#include "Common/ThingFactory.h" +#include "Common/ThingTemplate.h" +#include "GameClient/Drawable.h" +#include "GameClient/GameClient.h" +#include "GameLogic/ExperienceTracker.h" +#include "GameLogic/Locomotor.h" +#include "GameLogic/Module/BodyModule.h" +#include "GameLogic/Module/CountermeasuresBehavior.h" +#include "GameLogic/Module/JetAIUpdate.h" +#include "GameLogic/Module/ParkingPlaceBehavior.h" +#include "GameLogic/Module/PhysicsUpdate.h" +#include "GameLogic/Object.h" +#include "GameLogic/AIPathfind.h" +#include "GameLogic/PartitionManager.h" +#include "GameLogic/Weapon.h" + +const Real BIGNUM = 99999.0f; + #ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -//------------------------------------------------------------------------------------------------- -enum TaxiType CPP_11(: Int) -{ - FROM_HANGAR, - FROM_PARKING, - TO_PARKING -}; - -//------------------------------------------------------------------------------------------------- -enum JetAIStateType CPP_11(: Int) -{ - // note that these must be distinct (numerically) from AIStateType. ick. - JETAISTATETYPE_FIRST = 1000, - - TAXI_FROM_HANGAR, - TAKING_OFF_AWAIT_CLEARANCE, - TAXI_TO_TAKEOFF, - PAUSE_BEFORE_TAKEOFF, - TAKING_OFF, - LANDING_AWAIT_CLEARANCE, - LANDING, - TAXI_FROM_LANDING, - ORIENT_FOR_PARKING_PLACE, - RELOAD_AMMO, - RETURNING_FOR_LANDING, - RETURN_TO_DEAD_AIRFIELD, - CIRCLING_DEAD_AIRFIELD, - - JETAISTATETYPE_LAST -}; - - -//------------------------------------------------------------------------------------------------- -Bool JetAIUpdate::getFlag( FlagType f ) const -{ - return (m_flags & (1<getWeaponInWeaponSlot((WeaponSlotType)i); - if (weapon == NULL || weapon->getReloadType() != RETURN_TO_BASE_TO_RELOAD) - continue; - ++specials; - if (weapon->getStatus() == OUT_OF_AMMO) - ++out; - } - return specials > 0 && out == specials; -} - -//------------------------------------------------------------------------------------------------- -static ParkingPlaceBehaviorInterface* getPP(ObjectID id, Object** airfieldPP = NULL) -{ - if (airfieldPP) - *airfieldPP = NULL; - - Object* airfield = TheGameLogic->findObjectByID( id ); - if (airfield == NULL || airfield->isEffectivelyDead() || !airfield->isKindOf(KINDOF_FS_AIRFIELD) || airfield->testStatus(OBJECT_STATUS_SOLD)) - return NULL; - - if (airfieldPP) - *airfieldPP = airfield; - - ParkingPlaceBehaviorInterface* pp = NULL; - for (BehaviorModule** i = airfield->getBehaviorModules(); *i; ++i) - { - if ((pp = (*i)->getParkingPlaceBehaviorInterface()) != NULL) - break; - } - - return pp; -} - -//------------------------------------------------------------------------------------------------- -class PartitionFilterHasParkingPlace : public PartitionFilter -{ -private: - ObjectID m_id; -public: - PartitionFilterHasParkingPlace(ObjectID id) : m_id(id) { } -protected: +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +//------------------------------------------------------------------------------------------------- +enum TaxiType CPP_11(: Int) +{ + FROM_HANGAR, + FROM_PARKING, + TO_PARKING +}; + +//------------------------------------------------------------------------------------------------- +enum JetAIStateType CPP_11(: Int) +{ + // note that these must be distinct (numerically) from AIStateType. ick. + JETAISTATETYPE_FIRST = 1000, + + TAXI_FROM_HANGAR, + TAKING_OFF_AWAIT_CLEARANCE, + TAXI_TO_TAKEOFF, + PAUSE_BEFORE_TAKEOFF, + TAKING_OFF, + LANDING_AWAIT_CLEARANCE, + LANDING, + TAXI_FROM_LANDING, + ORIENT_FOR_PARKING_PLACE, + RELOAD_AMMO, + RETURNING_FOR_LANDING, + RETURN_TO_DEAD_AIRFIELD, + CIRCLING_DEAD_AIRFIELD, + + JETAISTATETYPE_LAST +}; + + +//------------------------------------------------------------------------------------------------- +Bool JetAIUpdate::getFlag( FlagType f ) const +{ + return (m_flags & (1<getWeaponInWeaponSlot((WeaponSlotType)i); + if (weapon == NULL || weapon->getReloadType() != RETURN_TO_BASE_TO_RELOAD) + continue; + ++specials; + if (weapon->getStatus() == OUT_OF_AMMO) + ++out; + } + return specials > 0 && out == specials; +} + +//------------------------------------------------------------------------------------------------- +static ParkingPlaceBehaviorInterface* getPP(ObjectID id, Object** airfieldPP = NULL) +{ + if (airfieldPP) + *airfieldPP = NULL; + + Object* airfield = TheGameLogic->findObjectByID( id ); + if (airfield == NULL || airfield->isEffectivelyDead() || !airfield->isKindOf(KINDOF_FS_AIRFIELD) || airfield->testStatus(OBJECT_STATUS_SOLD)) + return NULL; + + if (airfieldPP) + *airfieldPP = airfield; + + ParkingPlaceBehaviorInterface* pp = NULL; + for (BehaviorModule** i = airfield->getBehaviorModules(); *i; ++i) + { + if ((pp = (*i)->getParkingPlaceBehaviorInterface()) != NULL) + break; + } + + return pp; +} + +//------------------------------------------------------------------------------------------------- +class PartitionFilterHasParkingPlace : public PartitionFilter +{ +private: + ObjectID m_id; +public: + PartitionFilterHasParkingPlace(ObjectID id) : m_id(id) { } +protected: #if defined(RTS_DEBUG) || defined(RTS_INTERNAL) - virtual const char* debugGetName() { return "PartitionFilterHasParkingPlace"; } -#endif - virtual Bool allow(Object *objOther) - { - ParkingPlaceBehaviorInterface* pp = getPP(objOther->getID()); - if (pp != NULL && pp->reserveSpace(m_id, 0.0f, NULL)) - return true; - return false; - } -}; - -//------------------------------------------------------------------------------------------------- -static Object* findSuitableAirfield(Object* jet) -{ - PartitionFilterAcceptByKindOf filterKind(MAKE_KINDOF_MASK(KINDOF_FS_AIRFIELD), KINDOFMASK_NONE); - PartitionFilterRejectByObjectStatus filterStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_UNDER_CONSTRUCTION ), OBJECT_STATUS_MASK_NONE ); - PartitionFilterRejectByObjectStatus filterStatusTwo( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_SOLD ), OBJECT_STATUS_MASK_NONE ); // Independent to make it an OR - PartitionFilterRelationship filterTeam(jet, PartitionFilterRelationship::ALLOW_ALLIES); - PartitionFilterAlive filterAlive; - PartitionFilterSameMapStatus filterMapStatus(jet); - PartitionFilterHasParkingPlace filterPP(jet->getID()); - - PartitionFilter *filters[16]; - Int numFilters = 0; - filters[numFilters++] = &filterKind; - filters[numFilters++] = &filterStatus; - filters[numFilters++] = &filterStatusTwo; - filters[numFilters++] = &filterTeam; - filters[numFilters++] = &filterAlive; - filters[numFilters++] = &filterPP; - filters[numFilters++] = &filterMapStatus; - filters[numFilters] = NULL; - - return ThePartitionManager->getClosestObject( jet, HUGE_DIST, FROM_CENTER_2D, filters ); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- -/* - Success: we have runway clearance - Failure: no runway clearance -*/ -class JetAwaitingRunwayState : public State -{ - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetAwaitingRunwayState, "JetAwaitingRunwayState") -protected: - // snapshot interface STUBBED. - virtual void crc( Xfer *xfer ){}; - virtual void xfer( Xfer *xfer ){XferVersion cv = 1; XferVersion v = cv; xfer->xferVersion( &v, cv );} - virtual void loadPostProcess(){}; -private: - const Bool m_landing; - -public: - JetAwaitingRunwayState( StateMachine *machine, Bool landing ) : m_landing(landing), State( machine, "JetAwaitingRunwayState") { } - - virtual StateReturnType onEnter() - { - Object* jet = getMachineOwner(); - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if( !jetAI ) - return STATE_FAILURE; - - jetAI->friend_setTakeoffInProgress(!m_landing); - jetAI->friend_setLandingInProgress(m_landing); - jetAI->friend_setAllowCircling(true); - return STATE_CONTINUE; - } - - virtual StateReturnType update() - { - Object* jet = getMachineOwner(); - if (jet->isEffectivelyDead()) - return STATE_FAILURE; - - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if( !jetAI ) - return STATE_FAILURE; - - ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); - if (pp == NULL) - { - // no producer? just skip this step. - return STATE_SUCCESS; - } - - // gotta reserve a space in order to reserve a runway - if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), NULL)) - { - DEBUG_ASSERTCRASH(m_landing, ("hmm, this should never happen for taking-off things")); - return STATE_FAILURE; - } - - if (pp->reserveRunway(jet->getID(), m_landing)) - { - return STATE_SUCCESS; - } - else if( jet->testStatus( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) && !m_landing ) - { - //If we're trying to take off an aircraft carrier and fail to reserve a - //runway, it's because we need to be at the front of the carrier queue. - //Therefore, we need to move forward whenever possible until we are in - //the front. - Coord3D bestPos; - if( pp->calcBestParkingAssignment( jet->getID(), &bestPos ) ) - { - jetAI->friend_setTaxiInProgress(true); - jetAI->friend_setAllowAirLoco(false); - jetAI->chooseLocomotorSet(LOCOMOTORSET_TAXIING); - - jetAI->destroyPath(); - Path *movePath; - movePath = newInstance(Path); - Coord3D pos = *jet->getPosition(); - movePath->prependNode( &pos, LAYER_GROUND ); - movePath->markOptimized(); - movePath->appendNode( &bestPos, LAYER_GROUND ); - - TheAI->pathfinder()->setDebugPath(movePath); - - jetAI->friend_setPath( movePath ); - DEBUG_ASSERTCRASH(jetAI->getCurLocomotor(), ("no loco")); - jetAI->getCurLocomotor()->setUsePreciseZPos(true); - jetAI->getCurLocomotor()->setUltraAccurate(true); - jetAI->getCurLocomotor()->setAllowInvalidPosition(true); - jetAI->ignoreObstacleID(jet->getProducerID()); - } - } - - // can't get a runway? gotta wait. - jetAI->setLocomotorGoalNone(); - return STATE_CONTINUE; - } - - virtual void onExit(StateExitType status) - { - Object* jet = getMachineOwner(); - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if (jetAI) - { - jetAI->friend_setTakeoffInProgress(false); - jetAI->friend_setLandingInProgress(false); - jetAI->friend_setAllowCircling(false); - } - } - -}; -EMPTY_DTOR(JetAwaitingRunwayState) - -//------------------------------------------------------------------------------------------------- -/* - Success: a new suitable airfield has appeared - Failure: shouldn't normally happen -*/ -class JetOrHeliCirclingDeadAirfieldState : public State -{ - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetOrHeliCirclingDeadAirfieldState, "JetOrHeliCirclingDeadAirfieldState") -protected: - // snapshot interface STUBBED. - // The state will check immediately after a load game, but I think that's ok. jba. - virtual void crc( Xfer *xfer ){}; - virtual void xfer( Xfer *xfer ){XferVersion cv = 1; XferVersion v = cv; xfer->xferVersion( &v, cv );} - virtual void loadPostProcess(){}; - -private: - Int m_checkAirfield; - - enum - { - // only recheck for new airfields every second or so - HOW_OFTEN_TO_CHECK = LOGICFRAMES_PER_SECOND - }; - -public: - JetOrHeliCirclingDeadAirfieldState( StateMachine *machine ) : - State( machine, "JetOrHeliCirclingDeadAirfieldState"), - m_checkAirfield(0) { } - - virtual StateReturnType onEnter() - { - Object* jet = getMachineOwner(); - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if( !jetAI ) - { - return STATE_FAILURE; - } - - // obscure case: if the jet wasn't spawned, but just placed directly on the map, - // it might not have an owning airfield, and it might be trying to return - // simply due to being idle, not out of ammo. so check and don't die in that - // case, but just punt back out to idle. - if (!jetAI->isOutOfSpecialReloadAmmo() && jet->getProducerID() == INVALID_ID) - { - return STATE_FAILURE; - } - - // just stay where we are. - jetAI->setLocomotorGoalNone(); - - m_checkAirfield = HOW_OFTEN_TO_CHECK; - - //Play the "low fuel" voice whenever the craft is circling above the airfield. - AudioEventRTS soundToPlay = *jet->getTemplate()->getPerUnitSound( "VoiceLowFuel" ); - soundToPlay.setObjectID( jet->getID() ); - TheAudio->addAudioEvent( &soundToPlay ); - - return STATE_CONTINUE; - } - - virtual StateReturnType update() - { - Object* jet = getMachineOwner(); - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if( !jetAI ) - { - return STATE_FAILURE; - } - - // just stay where we are. - jetAI->setLocomotorGoalNone(); - - Real damageRate = jetAI->friend_getOutOfAmmoDamagePerSecond(); - if (damageRate > 0) - { - // convert to damage/sec to damage/frame - damageRate *= SECONDS_PER_LOGICFRAME_REAL; - // since it's a percentage, multiply times the max health - damageRate *= jet->getBodyModule()->getMaxHealth(); - - DamageInfo damageInfo; - damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE; - damageInfo.in.m_deathType = DEATH_NORMAL; - damageInfo.in.m_sourceID = INVALID_ID; - damageInfo.in.m_amount = damageRate; - jet->attemptDamage( &damageInfo ); - } - - if (--m_checkAirfield <= 0) - { - m_checkAirfield = HOW_OFTEN_TO_CHECK; - Object* airfield = findSuitableAirfield( jet ); - if (airfield) - { - jet->setProducer(airfield); - return STATE_SUCCESS; - } - } - - return STATE_CONTINUE; - } - -}; -EMPTY_DTOR(JetOrHeliCirclingDeadAirfieldState) - -//------------------------------------------------------------------------------------------------- -/* - Success: we returned to the dead-airfield location - Failure: shouldn't normally happen -*/ -class JetOrHeliReturningToDeadAirfieldState : public AIInternalMoveToState -{ - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetOrHeliReturningToDeadAirfieldState, "JetOrHeliReturningToDeadAirfieldState") -public: - JetOrHeliReturningToDeadAirfieldState( StateMachine *machine ) : AIInternalMoveToState( machine, "JetOrHeliReturningToDeadAirfieldState") { } - - virtual StateReturnType onEnter() - { - Object* jet = getMachineOwner(); - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if( !jetAI ) - { - return STATE_FAILURE; - } - - setAdjustsDestination(true); - m_goalPosition = *jetAI->friend_getProducerLocation(); - - return AIInternalMoveToState::onEnter(); - } - -}; -EMPTY_DTOR(JetOrHeliReturningToDeadAirfieldState) - -//------------------------------------------------------------------------------------------------- -// This solution uses the -// http://www.faqs.org/faqs/graphics/algorithms-faq/ -// Subject 1.03 -static Bool intersectInfiniteLine2D -( - Real ax, Real ay, Real ao, - Real cx, Real cy, Real co, - Real& ix, Real& iy -) -{ - Real bx = ax + Cos(ao); - Real by = ay + Sin(ao); - Real dx = cx + Cos(co); - Real dy = cy + Sin(co); - - Real denom = ((bx - ax) * (dy - cy) - (by - ay) * (dx - cx)); - if (denom == 0.0f) - { - // the lines are parallel. - return false; - } - - // The lines intersect. - Real r = ((ay - cy) * (dx - cx) - (ax - cx) * (dy - cy) ) / denom; - ix = ax + r * (bx - ax); - iy = ay + r * (by - ay); - return true; -} - -//------------------------------------------------------------------------------------------------- -/* - Success: we are on the ground at the runway start - Failure: we are unable to get on the ground -*/ -class JetOrHeliTaxiState : public AIMoveOutOfTheWayState -{ - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetOrHeliTaxiState, "JetOrHeliTaxiState") -private: - TaxiType m_taxiMode; -public: - JetOrHeliTaxiState( StateMachine *machine, TaxiType m ) : m_taxiMode(m), AIMoveOutOfTheWayState( machine ) { } - - virtual StateReturnType onEnter() - { - Object* jet = getMachineOwner(); - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if( !jetAI ) - return STATE_FAILURE; - - jetAI->setCanPathThroughUnits(true); - jetAI->friend_setTakeoffInProgress(m_taxiMode != TO_PARKING); - jetAI->friend_setLandingInProgress(m_taxiMode == TO_PARKING); - jetAI->friend_setTaxiInProgress(true); - - if( m_taxiMode == TO_PARKING ) - { - //Instantly reload flares. - CountermeasuresBehaviorInterface *cbi = jet->getCountermeasuresBehaviorInterface(); - if( cbi ) - { - cbi->reloadCountermeasures(); - } - } - - jetAI->friend_setAllowAirLoco(false); - jetAI->chooseLocomotorSet(LOCOMOTORSET_TAXIING); - DEBUG_ASSERTCRASH(jetAI->getCurLocomotor(), ("no loco")); - - Object* airfield; - ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID(), &airfield); - if (pp == NULL) - return STATE_SUCCESS; // no airfield? just skip this step. - - ParkingPlaceBehaviorInterface::PPInfo ppinfo; - if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo)) - return STATE_FAILURE; // full? - - Coord3D intermedPt; - Bool intermed = false; - Real orient = atan2(ppinfo.runwayPrep.y - ppinfo.parkingSpace.y, ppinfo.runwayPrep.x - ppinfo.parkingSpace.x); - - - if (fabs(stdAngleDiff(orient, ppinfo.parkingOrientation)) > PI/128) - { - intermedPt.z = (ppinfo.parkingSpace.z + ppinfo.runwayPrep.z) * 0.5f; - intermed = intersectInfiniteLine2D( - ppinfo.parkingSpace.x, ppinfo.parkingSpace.y, ppinfo.parkingOrientation, - ppinfo.runwayPrep.x, ppinfo.runwayPrep.y, ppinfo.parkingOrientation + PI/2, - intermedPt.x, intermedPt.y); - } - - jetAI->destroyPath(); - Path *movePath; - movePath = newInstance(Path); - Coord3D pos = *jet->getPosition(); - movePath->prependNode( &pos, LAYER_GROUND ); - movePath->markOptimized(); - - if (m_taxiMode == TO_PARKING) - { - if( jet->testStatus( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) ) - { - //We're on an aircraft carrier. - const std::vector *pTaxiLocations = pp->getTaxiLocations( jet->getID() ); - if( pTaxiLocations ) - { - std::vector::const_iterator it; - for( it = pTaxiLocations->begin(); it != pTaxiLocations->end(); it++ ) - { - movePath->appendNode( &(*it), LAYER_GROUND ); - } - } - - //We just landed... see if we can get a better space forward so we don't stop and pause - //at our initially assigned spot. - Coord3D pos; - pp->calcBestParkingAssignment( jet->getID(), &pos ); - - movePath->appendNode( &pos, LAYER_GROUND ); - } - else - { - //We're on a normal airfield - movePath->appendNode( &ppinfo.runwayPrep, LAYER_GROUND ); - if (intermed) - movePath->appendNode( &intermedPt, LAYER_GROUND ); - movePath->appendNode( &ppinfo.parkingSpace, LAYER_GROUND ); - } - } - else if (m_taxiMode == FROM_PARKING) - { - if( jet->testStatus( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) ) - { - if( !(ppinfo.runwayStart == ppinfo.runwayPrep) ) - { - movePath->appendNode( &ppinfo.runwayStart, LAYER_GROUND ); - } - } - else - { - if (intermed) - movePath->appendNode( &intermedPt, LAYER_GROUND ); - movePath->appendNode( &ppinfo.runwayPrep, LAYER_GROUND ); - movePath->appendNode( &ppinfo.runwayStart, LAYER_GROUND ); - } - } - else if (m_taxiMode == FROM_HANGAR) - { - if( jet->testStatus( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) ) - { - //Aircraft carrier - if( jet->testStatus( OBJECT_STATUS_REASSIGN_PARKING ) ) - { - //This status means we are being reassigned a parking space. We're not actually moving from the - //hangar. So simply move to the new parking spot which was just switched from under us in - //FlightDeckBehavior::update() - jet->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_REASSIGN_PARKING ) ); - movePath->appendNode( &ppinfo.runwayPrep, LAYER_GROUND ); - } - else - { - const std::vector *pCreationLocations = pp->getCreationLocations( jet->getID() ); - if( !pCreationLocations ) - { - DEBUG_CRASH( ("No creation locations specified for runway for JetAIBehavior -- taxiing from hanger (Kris).") ); - return STATE_FAILURE; - } - std::vector::const_iterator it; - Bool firstNode = TRUE; - for( it = pCreationLocations->begin(); it != pCreationLocations->end(); it++ ) - { - if( firstNode ) - { - //Skip the first node because it's the creation location. - firstNode = FALSE; - continue; - } - movePath->appendNode( &(*it), LAYER_GROUND ); - } - movePath->appendNode( &ppinfo.runwayPrep, LAYER_GROUND ); - } - } - else - { - //Airfield - movePath->appendNode( &ppinfo.parkingSpace, LAYER_GROUND ); - } - } - - m_waitingForPath = FALSE; - TheAI->pathfinder()->setDebugPath(movePath); - - setAdjustsDestination(false); // precision is necessary - - jetAI->friend_setPath( movePath ); - DEBUG_ASSERTCRASH(jetAI->getCurLocomotor(), ("no loco")); - jetAI->getCurLocomotor()->setUsePreciseZPos(true); - jetAI->getCurLocomotor()->setUltraAccurate(true); - jetAI->getCurLocomotor()->setAllowInvalidPosition(true); - jetAI->ignoreObstacleID(jet->getProducerID()); - - StateReturnType ret = AIMoveOutOfTheWayState::onEnter(); - return ret; - } - - virtual StateReturnType update() - { - Object* jet = getMachineOwner(); - if (jet->isEffectivelyDead()) - return STATE_FAILURE; - - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if( !jetAI ) - return STATE_FAILURE; - - if( m_taxiMode == TO_PARKING || m_taxiMode == FROM_HANGAR ) - { - //Keep checking to see if there is a better spot as it moves forward. If we find a better spot, then - //append the position to our move. - ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); - Coord3D bestPos; - Int oldIndex, newIndex; - // Check pp for null, as it is possible for your airfield to get destroyed while taxiing.jba [8/27/2003] - if( pp!=NULL && pp->calcBestParkingAssignment( jet->getID(), &bestPos, &oldIndex, &newIndex ) ) - { - Path *path = jetAI->friend_getPath(); - if( path ) - { - path->appendNode( &bestPos, LAYER_GROUND ); - } - } - } - - return AIMoveOutOfTheWayState::update(); - } - - virtual void onExit( StateExitType status ) - { - Object* jet = getMachineOwner(); - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if (jetAI) - { - jetAI->getCurLocomotor()->setUsePreciseZPos(false); - jetAI->getCurLocomotor()->setUltraAccurate(false); - jetAI->getCurLocomotor()->setAllowInvalidPosition(false); - jetAI->friend_setTakeoffInProgress(false); - jetAI->friend_setLandingInProgress(false); - jetAI->friend_setTaxiInProgress(false); - jetAI->setCanPathThroughUnits(false); - } - - AIMoveOutOfTheWayState::onExit(status); - } - -}; -EMPTY_DTOR(JetOrHeliTaxiState) - -//------------------------------------------------------------------------------------------------- -/* - Success: we are on the ground at the runway start - Failure: we are unable to get on the ground -*/ -class JetTakeoffOrLandingState : public AIFollowPathState -{ - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetTakeoffOrLandingState, "JetTakeoffOrLandingState") -private: - Real m_maxLift; - Real m_maxSpeed; -#ifdef CIRCLE_FOR_LANDING - Coord3D m_circleForLandingPos; -#endif - Bool m_landing; - Bool m_landingSoundPlayed; - -public: - JetTakeoffOrLandingState( StateMachine *machine, Bool landing ) : m_landing(landing), AIFollowPathState( machine, "JetTakeoffOrLandingState" ) { } - - virtual StateReturnType onEnter() - { - Object* jet = getMachineOwner(); - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if (!jetAI) - return STATE_FAILURE; - - if (jet->isEffectivelyDead()) - return STATE_FAILURE; - - jetAI->friend_setTakeoffInProgress(!m_landing); - jetAI->friend_setLandingInProgress(m_landing); - jetAI->friend_setAllowAirLoco(true); - jetAI->chooseLocomotorSet(LOCOMOTORSET_NORMAL); - Locomotor* loco = jetAI->getCurLocomotor(); - DEBUG_ASSERTCRASH(loco, ("no loco")); - loco->setMaxLift(BIGNUM); - BodyDamageType bdt = jet->getBodyModule()->getDamageState(); - m_maxLift = loco->getMaxLift(bdt); - m_maxSpeed = loco->getMaxSpeedForCondition(bdt); - m_landingSoundPlayed = FALSE; - if (m_landing) - { - loco->setMaxSpeed(loco->getMinSpeed()); - } - else - { - loco->setMaxLift(0); - } - loco->setUsePreciseZPos(true); - loco->setUltraAccurate(true); - jetAI->ignoreObstacleID(jet->getProducerID()); - - ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); - if (pp == NULL) - return STATE_SUCCESS; // no airfield? just skip this step - - ParkingPlaceBehaviorInterface::PPInfo ppinfo; - if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo)) - { - // it's full. - return STATE_FAILURE; - } - - // only check this for landing; we might have already given up the reservation to the guy behind us for takeoff - if (m_landing) - { - if (!pp->reserveRunway(jet->getID(), m_landing)) - { - DEBUG_CRASH(("we should never get to this state unless we have a runway available")); - return STATE_FAILURE; - } - } - - std::vector path; - if (m_landing) - { -#ifdef CIRCLE_FOR_LANDING - m_circleForLandingPos = ppinfo.runwayApproach; - m_circleForLandingPos.z = (ppinfo.runwayEnd.z + ppinfo.runwayApproach.z)*0.5f; -#else - path.push_back(ppinfo.runwayApproach); -#endif - if( jet->testStatus( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) ) - { - //Assigned to an aircraft carrier which has separate landing strips. - path.push_back( ppinfo.runwayLandingStart ); - path.push_back( ppinfo.runwayLandingEnd ); - } - else - { - //Assigned to an airstrip -- land the same way we took off but in reverse. - path.push_back(ppinfo.runwayEnd); - path.push_back(ppinfo.runwayStart); - } - } - else - { - ppinfo.runwayEnd.z = ppinfo.runwayApproach.z; - path.push_back(ppinfo.runwayEnd); - path.push_back(ppinfo.runwayExit); - } - - setAdjustsDestination(false); // precision is necessary - setAdjustFinalDestination(false); // especially at the endpoint! - - jetAI->friend_setGoalPath( &path ); - - StateReturnType ret = AIFollowPathState::onEnter(); - - return ret; - } - - virtual StateReturnType update() - { - Object* jet = getMachineOwner(); - if (jet->isEffectivelyDead()) - return STATE_FAILURE; - - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if( !jetAI ) - return STATE_FAILURE; - - if (m_landing) - { -#ifdef CIRCLE_FOR_LANDING - if (jet->getPosition()->z > m_circleForLandingPos.z) - { - const Real THRESH = 4.0f; - jetAI->getCurLocomotor()->setAltitudeChangeThresholdForCircling(THRESH); - jetAI->setLocomotorGoalPositionExplicit(m_circleForLandingPos); - return STATE_CONTINUE; - } - else -#endif - { - jetAI->getCurLocomotor()->setMaxLift(BIGNUM); -#ifdef CIRCLE_FOR_LANDING - jetAI->getCurLocomotor()->setAltitudeChangeThresholdForCircling(0); -#endif - } - - if( !m_landingSoundPlayed ) - { - ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); - Real zPos = jet->getPosition()->z; - Real zSlop = 0.25f; - PathfindLayerEnum layer = TheTerrainLogic->getHighestLayerForDestination( jet->getPosition() ); - Real groundZ = TheTerrainLogic->getLayerHeight( jet->getPosition()->x, jet->getPosition()->y, layer ); - if( pp ) - { - groundZ += pp->getLandingDeckHeightOffset(); - } - - if( zPos - zSlop <= groundZ ) - { - m_landingSoundPlayed = TRUE; - AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_aircraftWheelScreech; - soundToPlay.setPosition( jet->getPosition() ); - TheAudio->addAudioEvent( &soundToPlay ); - } - } - } - else - { - ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); - if (pp) - pp->transferRunwayReservationToNextInLineForTakeoff(jet->getID()); - - //Calculate the distance of the jet from the end of the runway as a ratio from the start. - //As it approaches the end of the runway, the plane will gain more lift, even if it's already - //going quickly. Using speed for lift is bad in the case of the aircraft carrier, because - //we don't want it to take off quickly. - ParkingPlaceBehaviorInterface::PPInfo ppinfo; - pp->calcPPInfo( jet->getID(), &ppinfo ); - Coord3D vector = ppinfo.runwayEnd; - vector.sub( jet->getPosition() ); - Real dist = vector.length(); - - Real ratio = 1.0f - (dist / ppinfo.runwayTakeoffDist); - ratio *= ratio; //dampen it.... - if (ratio < 0.0f) ratio = 0.0f; - if (ratio > 1.0f) ratio = 1.0f; - jetAI->getCurLocomotor()->setMaxLift(m_maxLift * ratio); - } - - StateReturnType ret = AIFollowPathState::update(); - return ret; - } - - virtual void onExit( StateExitType status ) - { - AIFollowPathState::onExit(status); - - // just in case. - Object* jet = getMachineOwner(); - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if( !jetAI ) - return; - - jetAI->friend_setTakeoffInProgress(false); - jetAI->friend_setLandingInProgress(false); - jetAI->friend_enableAfterburners(false); - - // Paranoia checks - sometimes onExit is called when we are - // shutting down, and not all pieces are valid. CurLocomotor - // is definitely null in some cases. jba. - Locomotor* loco = jetAI->getCurLocomotor(); - if (loco) - { - loco->setUsePreciseZPos(false); - loco->setUltraAccurate(false); - // don't restore lift if dead -- this may fight with JetSlowDeathBehavior! - if (!jet->isEffectivelyDead()) - loco->setMaxLift(BIGNUM); -#ifdef CIRCLE_FOR_LANDING - loco->setAltitudeChangeThresholdForCircling(0); -#endif - } - jetAI->ignoreObstacleID(INVALID_ID); - ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); - if (!m_landing) - { - if (pp && !jetAI->friend_keepsParkingSpaceWhenAirborne()) - pp->releaseSpace(jet->getID()); - } - if (pp) - pp->releaseRunway(jet->getID()); - } -}; -EMPTY_DTOR(JetTakeoffOrLandingState) - -//------------------------------------------------------------------------------------------------- -static Real calcDistSqr(const Coord3D& a, const Coord3D& b) -{ - return sqr(a.x-b.x) + sqr(a.y-b.y) + sqr(a.z-b.z); -} - -//------------------------------------------------------------------------------------------------- -/* - Success: we are on the ground at the runway start - Failure: we are unable to get on the ground -*/ -class HeliTakeoffOrLandingState : public State -{ - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(HeliTakeoffOrLandingState, "HeliTakeoffOrLandingState") -protected: - // snapshot interface - virtual void crc( Xfer *xfer ) - { - // empty. jba. - } - - virtual void xfer( Xfer *xfer ) - { - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // set on create. xfer->xferBool(&m_landing); - xfer->xferCoord3D(&m_path[0]); - xfer->xferCoord3D(&m_path[1]); - xfer->xferInt(&m_index); - xfer->xferCoord3D(&m_parkingLoc); - xfer->xferReal(&m_parkingOrientation); - } - virtual void loadPostProcess() - { - // empty. jba. - } - -private: - Coord3D m_path[2]; - Int m_index; - Coord3D m_parkingLoc; - Real m_parkingOrientation; - Bool m_landing; -public: - HeliTakeoffOrLandingState( StateMachine *machine, Bool landing ) : m_landing(landing), - State( machine, "HeliTakeoffOrLandingState" ), m_index(0) - { - m_parkingLoc.zero(); - } - - virtual StateReturnType onEnter() - { - Object* jet = getMachineOwner(); - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if( !jetAI ) - return STATE_FAILURE; - - jetAI->friend_setTakeoffInProgress(!m_landing); - jetAI->friend_setLandingInProgress(m_landing); - jetAI->friend_setAllowAirLoco(true); - jetAI->chooseLocomotorSet(LOCOMOTORSET_NORMAL); - - Locomotor* loco = jetAI->getCurLocomotor(); - DEBUG_ASSERTCRASH(loco, ("no loco")); - loco->setUsePreciseZPos(true); - loco->setUltraAccurate(true); - jetAI->ignoreObstacleID(jet->getProducerID()); - - Object* airfield; - ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID(), &airfield); - if (pp == NULL) - return STATE_SUCCESS; // no airfield? just skip this step - - Coord3D landingApproach; - if (jet->isKindOf(KINDOF_PRODUCED_AT_HELIPAD)) - { - if (m_landing) - { - m_parkingLoc = jetAI->friend_getLandingPosForHelipadStuff(); - m_parkingOrientation = jet->getOrientation(); - } - else - { - m_parkingOrientation = jet->getOrientation(); - m_parkingLoc = *jet->getPosition(); - } - landingApproach = m_parkingLoc; - landingApproach.z += pp->getApproachHeight() + pp->getLandingDeckHeightOffset(); - } - else - { - ParkingPlaceBehaviorInterface::PPInfo ppinfo; - - if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo)) - return STATE_FAILURE; - m_parkingLoc = ppinfo.parkingSpace; - m_parkingOrientation = ppinfo.parkingOrientation; - landingApproach = m_parkingLoc; - landingApproach.z += (ppinfo.runwayApproach.z - ppinfo.runwayEnd.z); - } - - if (m_landing) - { - m_path[0] = landingApproach; - m_path[1] = m_parkingLoc; - } - else - { - m_path[0] = m_parkingLoc; - m_path[1] = landingApproach; - m_path[1].z = landingApproach.z; - } - m_index = 0; - - return STATE_CONTINUE; - } - - virtual StateReturnType update() - { - Object* jet = getMachineOwner(); - if (jet->isEffectivelyDead()) - return STATE_FAILURE; - - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if( !jetAI ) - return STATE_FAILURE; - -// I have disabled this because it is no longer necessary and is a bit funky lookin' (srj) -#ifdef NOT_IN_USE - // magically position it correctly. - jet->getPhysics()->scrubVelocity2D(0); - Coord3D hoverloc = m_path[m_index]; - hoverloc.z = jet->getPosition()->z; -#if 1 - Coord3D pos = *jet->getPosition(); - Real dx = hoverloc.x - pos.x; - Real dy = hoverloc.y - pos.y; - Real dSqr = dx*dx+dy*dy; - const Real DARN_CLOSE = 0.25f; - if (dSqr < DARN_CLOSE) - { - jet->setPosition(&hoverloc); - } - else - { - Real dist = sqrtf(dSqr); - if (dist<1) dist = 1; - pos.x += PATHFIND_CELL_SIZE_F*dx/(dist*LOGICFRAMES_PER_SECOND); - pos.y += PATHFIND_CELL_SIZE_F*dy/(dist*LOGICFRAMES_PER_SECOND); - jet->setPosition(&pos); - } -#else - jet->setPosition(&hoverloc); -#endif - jet->setOrientation(m_parkingOrientation); -#endif - - if (jet->isKindOf(KINDOF_PRODUCED_AT_HELIPAD) || !m_landing) - { - TheAI->pathfinder()->adjustDestination(jet, jetAI->getLocomotorSet(), &m_path[m_index]); - TheAI->pathfinder()->updateGoal(jet, &m_path[m_index], LAYER_GROUND); - } - - jetAI->setLocomotorGoalPositionExplicit(m_path[m_index]); - - const Real THRESH = 3.0f; - const Real THRESH_SQR = THRESH*THRESH; - const Coord3D* a = jet->getPosition(); - const Coord3D* b = &m_path[m_index]; - Real distSqr = calcDistSqr(*a, *b); - if (distSqr <= THRESH_SQR) - ++m_index; - - if (m_index >= 2) - return STATE_SUCCESS; - - return STATE_CONTINUE; - } - - virtual void onExit( StateExitType status ) - { - // just in case. - Object* jet = getMachineOwner(); - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if( !jetAI ) - return; - - jetAI->friend_setTakeoffInProgress(false); - jetAI->friend_setLandingInProgress(false); - - // Paranoia checks - sometimes onExit is called when we are - // shutting down, and not all pieces are valid. CurLocomotor - // is definitely null in some cases. jba. - Locomotor* loco = jetAI->getCurLocomotor(); - if (loco) - { - loco->setUsePreciseZPos(false); - loco->setUltraAccurate(false); - // don't restore lift if dead -- this may fight with JetSlowDeathBehavior! - if (!jet->isEffectivelyDead()) - loco->setMaxLift(BIGNUM); - } - - jetAI->ignoreObstacleID(INVALID_ID); - ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); - if (m_landing) - { - jetAI->friend_setAllowAirLoco(false); - jetAI->chooseLocomotorSet(LOCOMOTORSET_TAXIING); - } - else - { - if (pp && !jetAI->friend_keepsParkingSpaceWhenAirborne()) - pp->releaseSpace(jet->getID()); - } - } - -}; -EMPTY_DTOR(HeliTakeoffOrLandingState) - -//------------------------------------------------------------------------------------------------- -class JetOrHeliParkOrientState : public State -{ - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetOrHeliParkOrientState, "JetOrHeliParkOrientState") -protected: - // snapshot interface STUBBED. - virtual void crc( Xfer *xfer ){}; - virtual void xfer( Xfer *xfer ){XferVersion cv = 1; XferVersion v = cv; xfer->xferVersion( &v, cv );} - virtual void loadPostProcess(){}; - -public: - JetOrHeliParkOrientState( StateMachine *machine ) : State( machine, "JetOrHeliParkOrientState") { } - - virtual StateReturnType onEnter() - { - Object* jet = getMachineOwner(); - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if( !jetAI ) - return STATE_FAILURE; - - if (jet->isKindOf(KINDOF_PRODUCED_AT_HELIPAD)) - { - return STATE_SUCCESS; - } - - jetAI->friend_setTakeoffInProgress(false); - jetAI->friend_setLandingInProgress(true); - - jetAI->ignoreObstacleID(jet->getProducerID()); - return STATE_CONTINUE; - } - - virtual StateReturnType update() - { - Object* jet = getMachineOwner(); - if (jet->isEffectivelyDead()) - return STATE_FAILURE; - - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if( !jetAI ) - { - return STATE_FAILURE; - } - - ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); - if (pp == NULL) - return STATE_FAILURE; - - ParkingPlaceBehaviorInterface::PPInfo ppinfo; - if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo)) - return STATE_FAILURE; - - const Real THRESH = 0.001f; - if (fabs(stdAngleDiff(jet->getOrientation(), ppinfo.parkingOrientation)) <= THRESH) - return STATE_SUCCESS; - - // magically position it correctly. - jet->getPhysics()->scrubVelocity2D(0); - Coord3D hoverloc = ppinfo.parkingSpace; - if( jet->testStatus( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) ) - { - hoverloc = ppinfo.runwayPrep; - } - - hoverloc.z = jet->getPosition()->z; - jet->setPosition(&hoverloc); - - jetAI->setLocomotorGoalOrientation(ppinfo.parkingOrientation); - - return STATE_CONTINUE; - } - - virtual void onExit( StateExitType status ) - { - Object* jet = getMachineOwner(); - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if( !jetAI ) - return; - - jetAI->friend_setTakeoffInProgress(false); - jetAI->friend_setLandingInProgress(false); - jetAI->ignoreObstacleID(INVALID_ID); - } -}; -EMPTY_DTOR(JetOrHeliParkOrientState) - -//------------------------------------------------------------------------------------------------- -class JetPauseBeforeTakeoffState : public AIFaceState -{ - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetPauseBeforeTakeoffState, "JetPauseBeforeTakeoffState") -protected: - // snapshot interface - virtual void crc( Xfer *xfer ) - { - // empty. jba. - } - - virtual void xfer( Xfer *xfer ) - { - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // set on create. xfer->xferBool(&m_landing); - xfer->xferUnsignedInt(&m_when); - xfer->xferUnsignedInt(&m_whenTransfer); - xfer->xferBool(&m_afterburners); - xfer->xferBool(&m_resetTimer); - xfer->xferObjectID(&m_waitedForTaxiID); - } - virtual void loadPostProcess() - { - // empty. jba. - } - -private: - UnsignedInt m_when; - UnsignedInt m_whenTransfer; - ObjectID m_waitedForTaxiID; - Bool m_resetTimer; - Bool m_afterburners; - - Bool findWaiter() - { - Object* jet = getMachineOwner(); - ParkingPlaceBehaviorInterface* pp = getPP(getMachineOwner()->getProducerID()); - if (pp) - { - Int count = pp->getRunwayCount(); - for (Int i = 0; i < count; ++i) - { - Object* otherJet = TheGameLogic->findObjectByID( pp->getRunwayReservation( i, RESERVATION_TAKEOFF ) ); - if (otherJet == NULL || otherJet == jet) - continue; - - AIUpdateInterface* ai = otherJet->getAIUpdateInterface(); - if (ai == NULL) - continue; - - if (ai->getCurrentStateID() == TAXI_TO_TAKEOFF) - { - if (m_waitedForTaxiID == INVALID_ID) - { - m_waitedForTaxiID = otherJet->getID(); - } - return true; - } - } - } - return false; - } - -public: - JetPauseBeforeTakeoffState( StateMachine *machine ) : - AIFaceState(machine, false), - m_when(0), - m_whenTransfer(0), - m_waitedForTaxiID(INVALID_ID), - m_resetTimer(false), - m_afterburners(false) - { - // nothing - } - - virtual StateReturnType onEnter() - { - Object* jet = getMachineOwner(); - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if( !jetAI ) - return STATE_FAILURE; - - jetAI->friend_setTakeoffInProgress(true); - jetAI->friend_setLandingInProgress(false); - - m_when = 0; - m_whenTransfer = 0; - m_waitedForTaxiID = INVALID_ID; - m_resetTimer = false; - m_afterburners = false; - - ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); - if (pp == NULL) - return STATE_SUCCESS; // no airfield? just skip this step. - - ParkingPlaceBehaviorInterface::PPInfo ppinfo; - if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo)) - return STATE_SUCCESS; // full? - - getMachine()->setGoalPosition(&ppinfo.runwayEnd); - - return AIFaceState::onEnter(); - } - - virtual StateReturnType update() - { - Object* jet = getMachineOwner(); - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if (jet->isEffectivelyDead()) - return STATE_FAILURE; - - // always call this. - StateReturnType superStatus = AIFaceState::update(); - - if (findWaiter()) - return STATE_CONTINUE; - - UnsignedInt now = TheGameLogic->getFrame(); - if (!m_resetTimer) - { - // we had to wait, but now everyone else is ready, so restart our countdown. - m_when = now + jetAI->friend_getTakeoffPause(); - if (m_waitedForTaxiID == INVALID_ID) - { - m_waitedForTaxiID = jet->getID(); // just so we don't pick up anyone else - m_whenTransfer = now + 1; - } - else - { - m_whenTransfer = now + 2; // 2 seems odd, but is correct - } - m_resetTimer = true; - } - - if (!m_afterburners) - { - jetAI->friend_enableAfterburners(true); - m_afterburners = true; - } - - DEBUG_ASSERTCRASH(m_when != 0, ("hmm")); - DEBUG_ASSERTCRASH(m_whenTransfer != 0, ("hmm")); - - // once we start the final wait, release the runways for guys behind us, so they can start taxiing - ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); - if (pp && now >= m_whenTransfer) - { - pp->transferRunwayReservationToNextInLineForTakeoff(jet->getID()); - } - - if (now >= m_when) - return superStatus; - - return STATE_CONTINUE; - } - - virtual void onExit(StateExitType status) - { - Object* jet = getMachineOwner(); - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - jetAI->friend_setTakeoffInProgress(false); - jetAI->friend_setLandingInProgress(false); - AIFaceState::onExit(status); - } - -}; -EMPTY_DTOR(JetPauseBeforeTakeoffState) - -//------------------------------------------------------------------------------------------------- -class JetOrHeliReloadAmmoState : public State -{ - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetOrHeliReloadAmmoState, "JetOrHeliReloadAmmoState") -private: - UnsignedInt m_reloadTime; - UnsignedInt m_reloadDoneFrame; - -protected: - - // snapshot interface - virtual void crc( Xfer *xfer ) - { - // empty. jba. - } - - virtual void xfer( Xfer *xfer ) - { - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // set on create. xfer->xferBool(&m_landing); - xfer->xferUnsignedInt(&m_reloadTime); - xfer->xferUnsignedInt(&m_reloadDoneFrame); - } - virtual void loadPostProcess() - { - // empty. jba. - } - -public: - JetOrHeliReloadAmmoState( StateMachine *machine ) : State( machine, "JetOrHeliReloadAmmoState") { } - - virtual StateReturnType onEnter() - { - Object* jet = getMachineOwner(); - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if( !jetAI ) - return STATE_FAILURE; - - jetAI->friend_setTakeoffInProgress(false); - jetAI->friend_setLandingInProgress(false); - jetAI->friend_setUseSpecialReturnLoco(false); - - // AW: Workaround for VTOL aircraft rotating towards 0 degrees on reloading. - if (!jetAI->friend_needsRunway()) { - ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); - ParkingPlaceBehaviorInterface::PPInfo ppinfo; - if ((pp) && pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo)) { - // DEBUG_LOG((">> JetOrHeliReloadAmmoState - onEnter - parkingOrientation = %f.\n", ppinfo.parkingOrientation)); - jetAI->setLocomotorGoalOrientation(ppinfo.parkingOrientation); - } - } - - m_reloadTime = 0; - for (Int i = 0; i < WEAPONSLOT_COUNT; ++i) - { - const Weapon* w = jet->getWeaponInWeaponSlot((WeaponSlotType)i); - if (w == NULL) - continue; - - Int remaining = w->getRemainingAmmo(); - Int clipSize = w->getClipSize(); - Int rt = w->getClipReloadTime(jet); - if (clipSize > 0) - { - // bias by amount empty. - Int needed = clipSize - remaining; - rt = (rt * needed) / clipSize; - } - if (rt > m_reloadTime) - m_reloadTime = rt; - } - - if (m_reloadTime < 1) - m_reloadTime = 1; - m_reloadDoneFrame = m_reloadTime + TheGameLogic->getFrame(); - return STATE_CONTINUE; - } - - virtual StateReturnType update() - { - Object* jet = getMachineOwner(); - - UnsignedInt now = TheGameLogic->getFrame(); - Bool allDone = true; - for (Int i = 0; i < WEAPONSLOT_COUNT; ++i) - { - Weapon* w = jet->getWeaponInWeaponSlot((WeaponSlotType)i); - if (w == NULL) - continue; - - if (now >= m_reloadDoneFrame) - w->setClipPercentFull(1.0f, false); - else - w->setClipPercentFull((Real)(m_reloadTime - (m_reloadDoneFrame - now)) / m_reloadTime, false); - - if (w->getRemainingAmmo() != w->getClipSize()) - allDone = false; - } - - if (allDone) - return STATE_SUCCESS; - - return STATE_CONTINUE; - } - - virtual void onExit(StateExitType status) - { - Object* jet = getMachineOwner(); - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - jetAI->friend_setTakeoffInProgress(false); - jetAI->friend_setLandingInProgress(false); - } - -}; -EMPTY_DTOR(JetOrHeliReloadAmmoState) - -//------------------------------------------------------------------------------------------------- -/* - Success: we are close enough to a friendly airfield to land - Failure: we are unable to get close enough to a friendly airfield to land -*/ -class JetOrHeliReturnForLandingState : public AIInternalMoveToState -{ - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetOrHeliReturnForLandingState, "JetOrHeliReturnForLandingState") -public: - JetOrHeliReturnForLandingState( StateMachine *machine ) : AIInternalMoveToState( machine, "JetOrHeliReturnForLandingState") { } - - virtual StateReturnType onEnter() - { - Object* jet = getMachineOwner(); - JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - - ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); - if (pp == NULL) - { - // nuke the producer id, since it's dead - jet->setProducer(NULL); - - Object* airfield = findSuitableAirfield( jet ); - pp = airfield ? getPP(airfield->getID()) : NULL; - if (airfield && pp) - { - jet->setProducer(airfield); - } - else - { - return STATE_FAILURE; - } - } - - if (jet->isKindOf(KINDOF_PRODUCED_AT_HELIPAD)) - { - m_goalPosition = jetAI->friend_getLandingPosForHelipadStuff(); - } - else - { - ParkingPlaceBehaviorInterface::PPInfo ppinfo; - if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo)) - return STATE_FAILURE; - - m_goalPosition = jetAI->friend_needsRunway() ? ppinfo.runwayApproach : ppinfo.parkingSpace; - } - setAdjustsDestination(false); // precision is necessary - - return AIInternalMoveToState::onEnter(); - } -}; -EMPTY_DTOR(JetOrHeliReturnForLandingState) - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- -class JetAIStateMachine : public AIStateMachine -{ - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( JetAIStateMachine, "JetAIStateMachine" ); - -public: - JetAIStateMachine( Object *owner, AsciiString name ); - -}; - -//------------------------------------------------------------------------------------------------- -JetAIStateMachine::JetAIStateMachine(Object *owner, AsciiString name) : AIStateMachine(owner, name) -{ - defineState( RETURNING_FOR_LANDING, newInstance(JetOrHeliReturnForLandingState)( this ), LANDING_AWAIT_CLEARANCE, RETURN_TO_DEAD_AIRFIELD ); - defineState( TAKING_OFF_AWAIT_CLEARANCE, newInstance(JetAwaitingRunwayState)( this, false ), TAXI_TO_TAKEOFF, AI_IDLE ); - defineState( TAXI_TO_TAKEOFF, newInstance(JetOrHeliTaxiState)( this, FROM_PARKING ), PAUSE_BEFORE_TAKEOFF, AI_IDLE ); - defineState( PAUSE_BEFORE_TAKEOFF, newInstance(JetPauseBeforeTakeoffState)( this ), TAKING_OFF, AI_IDLE ); - defineState( TAKING_OFF, newInstance(JetTakeoffOrLandingState)( this, false ), AI_IDLE, AI_IDLE ); - defineState( LANDING_AWAIT_CLEARANCE, newInstance(JetAwaitingRunwayState)( this, true ), LANDING, AI_IDLE ); - defineState( LANDING, newInstance(JetTakeoffOrLandingState)( this, true ), TAXI_FROM_LANDING, AI_IDLE ); - defineState( TAXI_FROM_LANDING, newInstance(JetOrHeliTaxiState)( this, TO_PARKING ), ORIENT_FOR_PARKING_PLACE, AI_IDLE ); - defineState( TAXI_FROM_HANGAR, newInstance(JetOrHeliTaxiState)( this, FROM_HANGAR ), ORIENT_FOR_PARKING_PLACE, AI_IDLE ); - defineState( ORIENT_FOR_PARKING_PLACE, newInstance(JetOrHeliParkOrientState)( this ), RELOAD_AMMO, AI_IDLE ); - defineState( RELOAD_AMMO, newInstance(JetOrHeliReloadAmmoState)( this ), AI_IDLE, AI_IDLE ); - defineState( RETURN_TO_DEAD_AIRFIELD, newInstance(JetOrHeliReturningToDeadAirfieldState)( this ), CIRCLING_DEAD_AIRFIELD, RETURN_TO_DEAD_AIRFIELD ); - defineState( CIRCLING_DEAD_AIRFIELD, newInstance(JetOrHeliCirclingDeadAirfieldState)( this ), AI_IDLE, AI_IDLE ); -} - -//------------------------------------------------------------------------------------------------- -JetAIStateMachine::~JetAIStateMachine() -{ -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- -class HeliAIStateMachine : public AIStateMachine -{ - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( HeliAIStateMachine, "HeliAIStateMachine" ); - -public: - HeliAIStateMachine( Object *owner, AsciiString name ); - -}; - -//------------------------------------------------------------------------------------------------- -HeliAIStateMachine::HeliAIStateMachine(Object *owner, AsciiString name) : AIStateMachine(owner, name) -{ - defineState( RETURNING_FOR_LANDING, newInstance(JetOrHeliReturnForLandingState)( this ), LANDING_AWAIT_CLEARANCE, RETURN_TO_DEAD_AIRFIELD ); - defineState( TAKING_OFF_AWAIT_CLEARANCE, newInstance(SuccessState)( this ), TAKING_OFF, AI_IDLE ); - defineState( TAKING_OFF, newInstance(HeliTakeoffOrLandingState)( this, false ), AI_IDLE, AI_IDLE ); - defineState( LANDING_AWAIT_CLEARANCE, newInstance(SuccessState)( this ), ORIENT_FOR_PARKING_PLACE, AI_IDLE ); - defineState( ORIENT_FOR_PARKING_PLACE, newInstance(JetOrHeliParkOrientState)( this ), LANDING, AI_IDLE ); - defineState( LANDING, newInstance(HeliTakeoffOrLandingState)( this, true ), RELOAD_AMMO, AI_IDLE ); - defineState( RELOAD_AMMO, newInstance(JetOrHeliReloadAmmoState)( this ), AI_IDLE, AI_IDLE ); - defineState( RETURN_TO_DEAD_AIRFIELD, newInstance(JetOrHeliReturningToDeadAirfieldState)( this ), CIRCLING_DEAD_AIRFIELD, RETURN_TO_DEAD_AIRFIELD ); - defineState( CIRCLING_DEAD_AIRFIELD, newInstance(JetOrHeliCirclingDeadAirfieldState)( this ), AI_IDLE, AI_IDLE ); - defineState( TAXI_FROM_HANGAR, newInstance(JetOrHeliTaxiState)( this, FROM_HANGAR ), AI_IDLE, AI_IDLE ); -} - -//------------------------------------------------------------------------------------------------- -HeliAIStateMachine::~HeliAIStateMachine() -{ -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- -JetAIUpdateModuleData::JetAIUpdateModuleData() -{ - m_outOfAmmoDamagePerSecond = 0; - m_needsRunway = true; - m_keepsParkingSpaceWhenAirborne = true; - m_takeoffDistForMaxLift = 0.0f; - m_minHeight = 0.0f; - m_parkingOffset = 0.0f; - m_sneakyOffsetWhenAttacking = 0.0f; - m_takeoffPause = 0; - m_attackingLoco = LOCOMOTORSET_NORMAL; - m_returningLoco = LOCOMOTORSET_NORMAL; - m_attackLocoPersistTime = 0; - m_attackersMissPersistTime = 0; - m_lockonTime = 0; - m_lockonCursor.clear(); - m_lockonInitialDist = 100; - m_lockonFreq = 0.5; - m_lockonAngleSpin = 720; - m_returnToBaseIdleTime = 0; -} - -//------------------------------------------------------------------------------------------------- -/*static*/ void JetAIUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) -{ - AIUpdateModuleData::buildFieldParse(p); - - static const FieldParse dataFieldParse[] = - { - { "OutOfAmmoDamagePerSecond", INI::parsePercentToReal, NULL, offsetof( JetAIUpdateModuleData, m_outOfAmmoDamagePerSecond ) }, - { "NeedsRunway", INI::parseBool, NULL, offsetof( JetAIUpdateModuleData, m_needsRunway ) }, - { "KeepsParkingSpaceWhenAirborne",INI::parseBool, NULL, offsetof( JetAIUpdateModuleData, m_keepsParkingSpaceWhenAirborne ) }, - { "TakeoffDistForMaxLift", INI::parsePercentToReal, NULL, offsetof( JetAIUpdateModuleData, m_takeoffDistForMaxLift ) }, - { "TakeoffPause", INI::parseDurationUnsignedInt, NULL, offsetof( JetAIUpdateModuleData, m_takeoffPause ) }, - { "MinHeight", INI::parseReal, NULL, offsetof( JetAIUpdateModuleData, m_minHeight ) }, - { "ParkingOffset", INI::parseReal, NULL, offsetof( JetAIUpdateModuleData, m_parkingOffset ) }, - { "SneakyOffsetWhenAttacking", INI::parseReal, NULL, offsetof( JetAIUpdateModuleData, m_sneakyOffsetWhenAttacking ) }, - { "AttackLocomotorType", INI::parseIndexList, TheLocomotorSetNames, offsetof( JetAIUpdateModuleData, m_attackingLoco ) }, - { "AttackLocomotorPersistTime", INI::parseDurationUnsignedInt, NULL, offsetof( JetAIUpdateModuleData, m_attackLocoPersistTime ) }, - { "AttackersMissPersistTime", INI::parseDurationUnsignedInt, NULL, offsetof( JetAIUpdateModuleData, m_attackersMissPersistTime ) }, - { "ReturnForAmmoLocomotorType", INI::parseIndexList, TheLocomotorSetNames, offsetof( JetAIUpdateModuleData, m_returningLoco ) }, - { "LockonTime", INI::parseDurationUnsignedInt, NULL, offsetof( JetAIUpdateModuleData, m_lockonTime ) }, - { "LockonCursor", INI::parseAsciiString, NULL, offsetof( JetAIUpdateModuleData, m_lockonCursor ) }, - { "LockonInitialDist", INI::parseReal, NULL, offsetof( JetAIUpdateModuleData, m_lockonInitialDist ) }, - { "LockonFreq", INI::parseReal, NULL, offsetof( JetAIUpdateModuleData, m_lockonFreq ) }, - { "LockonAngleSpin", INI::parseAngleReal, NULL, offsetof( JetAIUpdateModuleData, m_lockonAngleSpin ) }, - { "LockonBlinky", INI::parseBool, NULL, offsetof( JetAIUpdateModuleData, m_lockonBlinky ) }, - { "ReturnToBaseIdleTime", INI::parseDurationUnsignedInt, NULL, offsetof( JetAIUpdateModuleData, m_returnToBaseIdleTime ) }, - { 0, 0, 0, 0 } - }; - p.add(dataFieldParse); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- -AIStateMachine* JetAIUpdate::makeStateMachine() -{ - if (getJetAIUpdateModuleData()->m_needsRunway) - return newInstance(JetAIStateMachine)( getObject(), "JetAIStateMachine"); - else - return newInstance(HeliAIStateMachine)( getObject(), "HeliAIStateMachine"); -} - -//------------------------------------------------------------------------------------------------- -JetAIUpdate::JetAIUpdate( Thing *thing, const ModuleData* moduleData ) : AIUpdateInterface( thing, moduleData ) -{ - m_flags = 0; - m_afterburnerSound = *(getObject()->getTemplate()->getPerUnitSound("Afterburner")); - m_afterburnerSound.setObjectID(getObject()->getID()); - m_attackLocoExpireFrame = 0; - m_attackersMissExpireFrame = 0; - m_untargetableExpireFrame = 0; - m_returnToBaseFrame = 0; - m_lockonDrawable = NULL; - m_landingPosForHelipadStuff.zero(); - - //Added By Sadullah Nader - //Initializations missing and needed - m_producerLocation.zero(); - // - m_enginesOn = TRUE; -} - -//------------------------------------------------------------------------------------------------- -JetAIUpdate::~JetAIUpdate() -{ - if (m_lockonDrawable) - { - TheGameClient->destroyDrawable(m_lockonDrawable); - m_lockonDrawable = NULL; - } -} - -//------------------------------------------------------------------------------------------------- -Bool JetAIUpdate::isIdle() const -{ - // we need to do this because we enter an idle state briefly between takeoff/landing in these cases, - // but scripting relies on us never claiming to be "idle"... - if (getFlag(HAS_PENDING_COMMAND)) - return false; - - return AIUpdateInterface::isIdle(); -} - -//------------------------------------------------------------------------------------------------- -Bool JetAIUpdate::isReloading() const -{ - StateID stateID = getStateMachine()->getCurrentStateID(); - if( stateID == RELOAD_AMMO ) - { - return TRUE; - } - return FALSE; -} - -//------------------------------------------------------------------------------------------------- -Bool JetAIUpdate::isTaxiingToParking() const -{ - StateID stateID = getStateMachine()->getCurrentStateID(); - switch( stateID ) - { - case TAXI_FROM_HANGAR: - case TAXI_FROM_LANDING: - case ORIENT_FOR_PARKING_PLACE: - case RELOAD_AMMO: - case TAKING_OFF_AWAIT_CLEARANCE: - case TAXI_TO_TAKEOFF: - case PAUSE_BEFORE_TAKEOFF: - case TAKING_OFF: - return TRUE; - } - return FALSE; -} - -//------------------------------------------------------------------------------------------------- -void JetAIUpdate::onObjectCreated() -{ - AIUpdateInterface::onObjectCreated(); - friend_setAllowAirLoco(false); - chooseLocomotorSet(LOCOMOTORSET_TAXIING); -} - -//------------------------------------------------------------------------------------------------- -void JetAIUpdate::onDelete() -{ - AIUpdateInterface::onDelete(); - ParkingPlaceBehaviorInterface* pp = getPP(getObject()->getProducerID()); - if (pp) - pp->releaseSpace(getObject()->getID()); -} - -//------------------------------------------------------------------------------------------------- -void JetAIUpdate::getProducerLocation() -{ - if (getFlag(HAS_PRODUCER_LOCATION)) - return; - - Object* jet = getObject(); - Object* airfield = TheGameLogic->findObjectByID( jet->getProducerID() ); - if (airfield == NULL) - m_producerLocation = *jet->getPosition(); - else - m_producerLocation = *airfield->getPosition(); - - /* - if we aren't allowed to fly, then we should be parked (or at least taxiing), - which implies we have a parking place reserved. If we don't, it's probably - because we were directly spawned via script (or directly placed on the map). - So, check to see if we have no parking place, and if not, quietly enable flight. - */ - ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); - if (!pp || !pp->hasReservedSpace(jet->getID())) - { - friend_setAllowAirLoco(true); - chooseLocomotorSet(LOCOMOTORSET_NORMAL); - } - else - { - friend_setAllowAirLoco(false); - chooseLocomotorSet(LOCOMOTORSET_TAXIING); - } - - setFlag(HAS_PRODUCER_LOCATION, true); - -} - -//------------------------------------------------------------------------------------------------- -UpdateSleepTime JetAIUpdate::update() -{ - const JetAIUpdateModuleData* d = getJetAIUpdateModuleData(); - - getProducerLocation(); - - Object* jet = getObject(); - - ParkingPlaceBehaviorInterface* pp = getPP(getObject()->getProducerID()); - - // If idle & out of ammo, return - // have to call our parent's isIdle, because we override it to never return true - // when we have a pending command... - UnsignedInt now = TheGameLogic->getFrame(); - - // srj sez: not 100% sure on this. calling RELOAD_AMMO "idle" allows us to get healed while reloading, - // but might have other side effects we didn't want. if this does prove to cause a bug, be sure - // that jets (and ESPECIALLY comanches) are still getting healed at airfields. - if (AIUpdateInterface::isIdle() || getStateMachine()->getCurrentStateID() == RELOAD_AMMO) - { - if (pp != NULL) - { - if (!getFlag(ALLOW_AIR_LOCO) && - !getFlag(HAS_PENDING_COMMAND) && - jet->isKindOf(KINDOF_PRODUCED_AT_HELIPAD) && - jet->getBodyModule()->getHealth() == jet->getBodyModule()->getMaxHealth()) - { - // we're completely healed, so take off again - pp->setHealee(jet, false); - friend_setAllowAirLoco(true); - getStateMachine()->clear(); - setLastCommandSource( CMD_FROM_AI ); - getStateMachine()->setState( TAKING_OFF_AWAIT_CLEARANCE ); - } - else - { - pp->setHealee(jet, !getFlag(ALLOW_AIR_LOCO)); - } - } - - // note that we might still have weapons with ammo, but still be forced to return to reload. - if (isOutOfSpecialReloadAmmo() && getFlag(ALLOW_AIR_LOCO)) - { - m_returnToBaseFrame = 0; - - // this is really a "just-in-case" to ensure the targeter list doesn't spin out of control (srj) - pruneDeadTargeters(); - - setFlag(USE_SPECIAL_RETURN_LOCO, true); - setLastCommandSource( CMD_FROM_AI ); - getStateMachine()->setState(RETURNING_FOR_LANDING); - } - else if (getFlag(HAS_PENDING_COMMAND) - // srj sez: if we are reloading ammo, wait will we are done before processing the pending command. - && getStateMachine()->getCurrentStateID() != RELOAD_AMMO) - { - m_returnToBaseFrame = 0; - - AICommandParms parms(AICMD_MOVE_TO_POSITION, CMD_FROM_AI); // values don't matter, will be wiped by next line - m_mostRecentCommand.reconstitute(parms); - setFlag(HAS_PENDING_COMMAND, false); - - aiDoCommand(&parms); - } - else if (m_returnToBaseFrame != 0 && now >= m_returnToBaseFrame && getFlag(ALLOW_AIR_LOCO)) - { - m_returnToBaseFrame = 0; - DEBUG_ASSERTCRASH(isOutOfSpecialReloadAmmo() == false, ("Hmm, this seems unlikely -- isOutOfSpecialReloadAmmo()==false")); - setFlag(USE_SPECIAL_RETURN_LOCO, false); - setLastCommandSource( CMD_FROM_AI ); - getStateMachine()->setState(RETURNING_FOR_LANDING); - } - else if (m_returnToBaseFrame == 0 && d->m_returnToBaseIdleTime > 0 && getFlag(ALLOW_AIR_LOCO)) - { - m_returnToBaseFrame = now + d->m_returnToBaseIdleTime; - } - } - else - { - if (pp != NULL) - { - pp->setHealee(getObject(), false); - } - m_returnToBaseFrame = 0; - if (getFlag(ALLOW_INTERRUPT_AND_RESUME_OF_CUR_STATE_FOR_RELOAD) && - isOutOfSpecialReloadAmmo() && getFlag(ALLOW_AIR_LOCO)) - { - setFlag(USE_SPECIAL_RETURN_LOCO, true); - setFlag(HAS_PENDING_COMMAND, true); - setFlag(ALLOW_INTERRUPT_AND_RESUME_OF_CUR_STATE_FOR_RELOAD, false); - setLastCommandSource( CMD_FROM_AI ); - getStateMachine()->setState(RETURNING_FOR_LANDING); - } - } - - Real minHeight = friend_getMinHeight(); - if( pp ) - { - minHeight += pp->getLandingDeckHeightOffset(); - } - - Drawable* draw = jet->getDrawable(); - if (draw != NULL) - { - StateID id = getStateMachine()->getCurrentStateID(); - Bool needToCheckMinHeight = (id >= JETAISTATETYPE_FIRST && id <= JETAISTATETYPE_LAST) || - !jet->isAboveTerrain() || - !getFlag(ALLOW_AIR_LOCO); - if( needToCheckMinHeight || jet->getStatusBits().test( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) ) - { - Real ht = jet->isAboveTerrain() ? jet->getHeightAboveTerrain() : 0; - if (ht < minHeight) - { - Matrix3D tmp(1); - tmp.Set_Z_Translation(minHeight - ht); - draw->setInstanceMatrix(&tmp); - } - else - { - draw->setInstanceMatrix(NULL); - } - } - else - { - draw->setInstanceMatrix(NULL); - } - } - - PhysicsBehavior* physics = jet->getPhysics(); - if (physics->getVelocityMagnitude() > 0 && getFlag(ALLOW_AIR_LOCO)) - jet->setModelConditionState(MODELCONDITION_JETEXHAUST); - else - jet->clearModelConditionState(MODELCONDITION_JETEXHAUST); - - if (jet->testStatus(OBJECT_STATUS_IS_ATTACKING)) - { - m_attackLocoExpireFrame = now + d->m_attackLocoPersistTime; - m_attackersMissExpireFrame = now + d->m_attackersMissPersistTime; - } - else - { - if (m_attackLocoExpireFrame != 0 && now >= m_attackLocoExpireFrame) - { - m_attackLocoExpireFrame = 0; - } - if (m_attackersMissExpireFrame != 0 && now >= m_attackersMissExpireFrame) - { - m_attackersMissExpireFrame = 0; - } - } - - if (m_untargetableExpireFrame != 0 && now >= m_untargetableExpireFrame) - { - m_untargetableExpireFrame = 0; - } - - positionLockon(); - - if (m_attackLocoExpireFrame != 0) - { - chooseLocomotorSet(d->m_attackingLoco); - } - else if (getFlag(USE_SPECIAL_RETURN_LOCO)) - { - chooseLocomotorSet(d->m_returningLoco); - } - - - if( !jet->isKindOf( KINDOF_PRODUCED_AT_HELIPAD ) ) - { - Drawable *draw = jet->getDrawable(); - if( draw ) - { - if( getFlag(TAKEOFF_IN_PROGRESS) - || getFlag(LANDING_IN_PROGRESS) - || getObject()->isSignificantlyAboveTerrain() - || isMoving() - || isWaitingForPath() ) - { - if( !m_enginesOn ) - { - //We just started moving, therefore turn on the engines! - draw->enableAmbientSound( TRUE ); - m_enginesOn = TRUE; - } - } - else if( m_enginesOn ) - { - //We're no longer moving, so turn off the engines! - draw->enableAmbientSound( FALSE ); - m_enginesOn = FALSE; - } - } - } - - - /*UpdateSleepTime ret =*/ AIUpdateInterface::update(); - //return (mine < ret) ? mine : ret; - /// @todo srj -- someday, make sleepy. for now, must not sleep. - return UPDATE_SLEEP_NONE; -} - -//------------------------------------------------------------------------------------------------- -Bool JetAIUpdate::chooseLocomotorSet(LocomotorSetType wst) -{ - const JetAIUpdateModuleData* d = getJetAIUpdateModuleData(); - if (!getFlag(ALLOW_AIR_LOCO)) - { - wst = LOCOMOTORSET_TAXIING; - } - else if (m_attackLocoExpireFrame != 0) - { - wst = d->m_attackingLoco; - } - else if (getFlag(USE_SPECIAL_RETURN_LOCO)) - { - wst = d->m_returningLoco; - } - return AIUpdateInterface::chooseLocomotorSet(wst); -} - -//------------------------------------------------------------------------------------------------- -void JetAIUpdate::setLocomotorGoalNone() -{ - if ((getFlag(TAKEOFF_IN_PROGRESS) || getFlag(LANDING_IN_PROGRESS)) - && getFlag(ALLOW_AIR_LOCO) && !getFlag(ALLOW_CIRCLING)) - { - Object* jet = getObject(); - Coord3D desiredPos = *jet->getPosition(); - const Coord3D* dir = jet->getUnitDirectionVector2D(); - desiredPos.x += dir->x * 1000.0f; - desiredPos.y += dir->y * 1000.0f; - setLocomotorGoalPositionExplicit(desiredPos); - } - else - { - AIUpdateInterface::setLocomotorGoalNone(); - } -} - -//---------------------------------------------------------------------------------------- -Bool JetAIUpdate::getSneakyTargetingOffset(Coord3D* offset) const -{ - if (m_attackersMissExpireFrame != 0 && TheGameLogic->getFrame() < m_attackersMissExpireFrame) - { - if (offset) - { - const JetAIUpdateModuleData* d = getJetAIUpdateModuleData(); - const Object* jet = getObject(); - const Coord3D* dir = jet->getUnitDirectionVector2D(); - offset->x = dir->x * d->m_sneakyOffsetWhenAttacking; - offset->y = dir->y * d->m_sneakyOffsetWhenAttacking; - offset->z = 0.0f; - } - return true; - } - else - { - return false; - } -} - -//---------------------------------------------------------------------------------------- -void JetAIUpdate::pruneDeadTargeters() -{ - if (!m_targetedBy.empty()) - { - for (std::list::iterator it = m_targetedBy.begin(); it != m_targetedBy.end(); /* empty */ ) - { - if (TheGameLogic->findObjectByID(*it) == NULL) - { - it = m_targetedBy.erase(it); - } - else - { - ++it; - } - } - } -} - -//---------------------------------------------------------------------------------------- -void JetAIUpdate::positionLockon() -{ - if (!m_lockonDrawable) - return; - - if (m_untargetableExpireFrame == 0) - { - TheGameClient->destroyDrawable(m_lockonDrawable); - m_lockonDrawable = NULL; - return; - } - - const JetAIUpdateModuleData* d = getJetAIUpdateModuleData(); - UnsignedInt now = TheGameLogic->getFrame(); - UnsignedInt remaining = m_untargetableExpireFrame - now; - UnsignedInt elapsed = d->m_lockonTime - remaining; - - Coord3D pos = *getObject()->getPosition(); - Real frac = (Real)remaining / (Real)d->m_lockonTime; - Real finalDist = getObject()->getGeometryInfo().getBoundingCircleRadius(); - Real dist = finalDist + (d->m_lockonInitialDist - finalDist) * frac; - Real angle = d->m_lockonAngleSpin * frac; - - pos.x += Cos(angle) * dist; - pos.y += Sin(angle) * dist; - // pos.z is untouched - - m_lockonDrawable->setPosition(&pos); - Real dx = getObject()->getPosition()->x - pos.x; - Real dy = getObject()->getPosition()->y - pos.y; - if (dx || dy) - m_lockonDrawable->setOrientation(atan2(dy, dx)); - - // the Gaussian sum, to avoid keeping a running total: - // - // 1+2+3+...n = n*(n+1)/2 - // - Real elapsedTimeSumPrev = 0.5f * (elapsed-1) * (elapsed); - Real elapsedTimeSumCurr = elapsedTimeSumPrev + elapsed; - Real factor = d->m_lockonFreq / d->m_lockonTime; - Bool lastPhase = ((Int)(factor * elapsedTimeSumPrev) & 1) != 0; - Bool thisPhase = ((Int)(factor * elapsedTimeSumCurr) & 1) != 0; - - if (lastPhase && (!thisPhase)) - { - AudioEventRTS lockonSound = TheAudio->getMiscAudio()->m_lockonTickSound; - lockonSound.setObjectID(getObject()->getID()); - TheAudio->addAudioEvent(&lockonSound); - if (d->m_lockonBlinky) - m_lockonDrawable->setDrawableHidden(false); - } - else - { - if (d->m_lockonBlinky) - m_lockonDrawable->setDrawableHidden(true); - } -} - -//---------------------------------------------------------------------------------------- -void JetAIUpdate::buildLockonDrawableIfNecessary() -{ - if (m_untargetableExpireFrame == 0) - return; - - const JetAIUpdateModuleData* d = getJetAIUpdateModuleData(); - if (d->m_lockonCursor.isNotEmpty() && m_lockonDrawable == NULL) - { - const ThingTemplate* tt = TheThingFactory->findTemplate(d->m_lockonCursor); - if (tt) - { - m_lockonDrawable = TheThingFactory->newDrawable(tt); - } - } - positionLockon(); -} - -//---------------------------------------------------------------------------------------- -void JetAIUpdate::addTargeter(ObjectID id, Bool add) -{ - const JetAIUpdateModuleData* d = getJetAIUpdateModuleData(); - UnsignedInt lockonTime = d->m_lockonTime; - if (lockonTime != 0) - { - std::list::iterator it = std::find(m_targetedBy.begin(), m_targetedBy.end(), id); - if (add) - { - if (it == m_targetedBy.end()) - { - m_targetedBy.push_back(id); - if (m_untargetableExpireFrame == 0 && m_targetedBy.size() == 1) - { - m_untargetableExpireFrame = TheGameLogic->getFrame() + lockonTime; - buildLockonDrawableIfNecessary(); - } - } - } - else - { - if (it != m_targetedBy.end()) - { - m_targetedBy.erase(it); - if (m_targetedBy.empty()) - { - m_untargetableExpireFrame = 0; - } - } - } - } -} - -//---------------------------------------------------------------------------------------- -Bool JetAIUpdate::isTemporarilyPreventingAimSuccess() const -{ - return m_untargetableExpireFrame != 0 && (TheGameLogic->getFrame() < m_untargetableExpireFrame); -} - -//---------------------------------------------------------------------------------------- -Bool JetAIUpdate::isAllowedToMoveAwayFromUnit() const -{ - // parked (or landing) units don't get to do this. - if (!getFlag(ALLOW_AIR_LOCO) || getFlag(TAKEOFF_IN_PROGRESS) || getFlag(LANDING_IN_PROGRESS)) - return false; - - return AIUpdateInterface::isAllowedToMoveAwayFromUnit(); -} - -//------------------------------------------------------------------------------------------------- -Bool JetAIUpdate::isDoingGroundMovement(void) const -{ - // srj per jba: Air units should never be doing ground movement, even when taxiing... - // (exception: see getTreatAsAircraftForLocoDistToGoal) - return false; -} - -//------------------------------------------------------------------------------------------------- -Bool JetAIUpdate::getTreatAsAircraftForLocoDistToGoal() const -{ - // exception to isDoingGroundMovement: should never treat as aircraft for dist-to-goal when taxiing. - if (getFlag(TAXI_IN_PROGRESS)) - { - return false; - } - else - { - return AIUpdateInterface::getTreatAsAircraftForLocoDistToGoal(); - } -} - -//------------------------------------------------------------------------------------------------- -Bool JetAIUpdate::isParkedInHangar() const -{ - // We do not check if the Aircraft actually needs a runway/hangar here, - // so we can ignore those cases earlier already - return isReloading() || !(getFlag(TAKEOFF_IN_PROGRESS) - || getFlag(LANDING_IN_PROGRESS) - || getObject()->isSignificantlyAboveTerrain() - || isMoving() - || isWaitingForPath()); -} - -//---------------------------------------------------------------------------------------- -/** - * Follow the path defined by the given array of points - */ -void JetAIUpdate::privateFollowPath( const std::vector* path, Object *ignoreObject, CommandSourceType cmdSource, Bool exitProduction ) -{ - if (exitProduction) - { - getStateMachine()->clear(); - if( ignoreObject ) - ignoreObstacle( ignoreObject ); - setLastCommandSource( cmdSource ); - if (getObject()->isKindOf(KINDOF_PRODUCED_AT_HELIPAD)) - getStateMachine()->setState( TAKING_OFF_AWAIT_CLEARANCE ); - else - getStateMachine()->setState( TAXI_FROM_HANGAR ); - } - else - { - AIUpdateInterface::privateFollowPath(path, ignoreObject, cmdSource, exitProduction); - } -} - -//---------------------------------------------------------------------------------------- -void JetAIUpdate::privateFollowPathAppend( const Coord3D *pos, CommandSourceType cmdSource ) -{ - // nothing yet... might need to override. not sure. (srj) - AIUpdateInterface::privateFollowPathAppend(pos, cmdSource); -} - -//---------------------------------------------------------------------------------------- -void JetAIUpdate::doLandingCommand(Object *airfield, CommandSourceType cmdSource) -{ - if (getObject()->isKindOf(KINDOF_PRODUCED_AT_HELIPAD)) - { - m_landingPosForHelipadStuff = *airfield->getPosition(); - - Coord3D tmp; - FindPositionOptions options; - options.maxRadius = airfield->getGeometryInfo().getBoundingCircleRadius() * 10.0f; - if (ThePartitionManager->findPositionAround(&m_landingPosForHelipadStuff, &options, &tmp)) - m_landingPosForHelipadStuff = tmp; - } - - for (BehaviorModule** i = airfield->getBehaviorModules(); *i; ++i) - { - ParkingPlaceBehaviorInterface* pp = (*i)->getParkingPlaceBehaviorInterface(); - if (pp == NULL) - continue; - - if (getObject()->isKindOf(KINDOF_PRODUCED_AT_HELIPAD) || - pp->reserveSpace(getObject()->getID(), friend_getParkingOffset(), NULL)) - { - // if we had a space at another airfield, release it - ParkingPlaceBehaviorInterface* oldPP = getPP(getObject()->getProducerID()); - if (oldPP != NULL && oldPP != pp) - { - oldPP->releaseSpace(getObject()->getID()); - } - - getObject()->setProducer(airfield); - DEBUG_ASSERTCRASH(isOutOfSpecialReloadAmmo() == false, ("Hmm, this seems unlikely -- isOutOfSpecialReloadAmmo()==false")); - setFlag(USE_SPECIAL_RETURN_LOCO, false); - setFlag(ALLOW_INTERRUPT_AND_RESUME_OF_CUR_STATE_FOR_RELOAD, false); - setLastCommandSource( cmdSource ); - getStateMachine()->setState(RETURNING_FOR_LANDING); - return; - } - } -} - -//---------------------------------------------------------------------------------------- -void JetAIUpdate::notifyVictimIsDead() -{ - if (getJetAIUpdateModuleData()->m_needsRunway) - m_returnToBaseFrame = TheGameLogic->getFrame(); -} - -//---------------------------------------------------------------------------------------- -/** - * Enter the given object - */ -void JetAIUpdate::privateEnter( Object *objectToEnter, CommandSourceType cmdSource ) -{ - // we are already landing. just ignore it. - if (getFlag(LANDING_IN_PROGRESS)) - return; - - if( !TheActionManager->canEnterObject( getObject(), objectToEnter, cmdSource, DONT_CHECK_CAPACITY ) ) - return; - - doLandingCommand(objectToEnter, cmdSource); -} - -//---------------------------------------------------------------------------------------- -/** - * Get repaired at the repair depot - */ -void JetAIUpdate::privateGetRepaired( Object *repairDepot, CommandSourceType cmdSource ) -{ - // we are already landing. just ignore it. - if (getFlag(LANDING_IN_PROGRESS)) - return; - - // sanity, if we can't get repaired from here get out of here - if( TheActionManager->canGetRepairedAt( getObject(), repairDepot, cmdSource ) == FALSE ) - return; - - // dock with the repair depot - doLandingCommand( repairDepot, cmdSource ); - -} - -//------------------------------------------------------------------------------------------------- -Bool JetAIUpdate::isParkedAt(const Object* obj) const -{ - if (!getFlag(ALLOW_AIR_LOCO) && - !getObject()->isKindOf(KINDOF_PRODUCED_AT_HELIPAD) && - obj != NULL) - { - Object* airfield; - ParkingPlaceBehaviorInterface* pp = getPP(getObject()->getProducerID(), &airfield); - if (pp != NULL && airfield != NULL && airfield == obj) - { - return true; - } - } - - return false; -} - -//------------------------------------------------------------------------------------------------- -void JetAIUpdate::aiDoCommand(const AICommandParms* parms) -{ - // call this from aiDoCommand as well as update, because this can - // be called before update ever is... if the unit is placed on a map, - // and a script tells it to do something with a condition of TRUE! - getProducerLocation(); - - if (!isAllowedToRespondToAiCommands(parms)) - return; - - // note that we always store this, even if nothing will be "pending". - m_mostRecentCommand.store(*parms); - - if (getFlag(TAKEOFF_IN_PROGRESS) || getFlag(LANDING_IN_PROGRESS)) - { - // have to wait for takeoff or landing to complete, just store the sucker - setFlag(HAS_PENDING_COMMAND, true); - return; - } - else if (parms->m_cmd == AICMD_IDLE && getStateMachine()->getCurrentStateID() == RELOAD_AMMO) - { - // uber-special-case... if we are told to idle, but are reloading ammo, ignore it for now, - // since we're already doing "nothing" and responding to this will cease our reload... - // don't just return, tho, in case we were (say) reloading during a guard stint. - setFlag(HAS_PENDING_COMMAND, true); - return; - } - else if( parms->m_cmd == AICMD_IDLE && getObject()->isAirborneTarget() && !getObject()->isKindOf( KINDOF_PRODUCED_AT_HELIPAD ) ) - { - getStateMachine()->clear(); - setLastCommandSource( CMD_FROM_AI ); - getStateMachine()->setState( RETURNING_FOR_LANDING ); - return; - } - else if (!getFlag(ALLOW_AIR_LOCO)) - { - switch (parms->m_cmd) - { - case AICMD_IDLE: - case AICMD_BUSY: - case AICMD_FOLLOW_EXITPRODUCTION_PATH: - // don't need (or want) to take off for these - break; - - case AICMD_ENTER: - case AICMD_GET_REPAIRED: - - // if we're already parked at the airfield in question, just ignore. - if (isParkedAt(parms->m_obj)) - return; - - // else fall thru to the default case! - - default: - { - // nuke any existing pending cmd - m_mostRecentCommand.store(*parms); - setFlag(HAS_PENDING_COMMAND, true); - - getStateMachine()->clear(); - setLastCommandSource( CMD_FROM_AI ); - getStateMachine()->setState( TAKING_OFF_AWAIT_CLEARANCE ); - - return; - } - } - } - - switch (parms->m_cmd) - { - case AICMD_GUARD_POSITION: - case AICMD_GUARD_OBJECT: - case AICMD_GUARD_AREA: - case AICMD_HUNT: - case AICMD_GUARD_RETALIATE: - setFlag(ALLOW_INTERRUPT_AND_RESUME_OF_CUR_STATE_FOR_RELOAD, true); - break; - default: - setFlag(ALLOW_INTERRUPT_AND_RESUME_OF_CUR_STATE_FOR_RELOAD, false); - break; - } - - setFlag(HAS_PENDING_COMMAND, false); - AIUpdateInterface::aiDoCommand(parms); -} - -//------------------------------------------------------------------------------------------------- -void JetAIUpdate::friend_setAllowAirLoco(Bool allowAirLoco) -{ - setFlag(ALLOW_AIR_LOCO, allowAirLoco); -} - -//------------------------------------------------------------------------------------------------- -void JetAIUpdate::friend_enableAfterburners(Bool v) -{ - Object* jet = getObject(); - if (v) - { - jet->setModelConditionState(MODELCONDITION_JETAFTERBURNER); - if (!m_afterburnerSound.isCurrentlyPlaying()) - { - m_afterburnerSound.setObjectID(jet->getID()); - m_afterburnerSound.setPlayingHandle(TheAudio->addAudioEvent(&m_afterburnerSound)); - } - } - else - { - jet->clearModelConditionState(MODELCONDITION_JETAFTERBURNER); - if (m_afterburnerSound.isCurrentlyPlaying()) - { - TheAudio->removeAudioEvent(m_afterburnerSound.getPlayingHandle()); - } - } -} - -//------------------------------------------------------------------------------------------------- -void JetAIUpdate::friend_addWaypointToGoalPath( const Coord3D &bestPos ) -{ - privateFollowPathAppend( &bestPos, CMD_FROM_AI ); -} - -//------------------------------------------------------------------------------------------------- -AICommandType JetAIUpdate::friend_getPendingCommandType() const -{ - if( getFlag( HAS_PENDING_COMMAND ) ) - { - return m_mostRecentCommand.getCommandType(); - } - return AICMD_NO_COMMAND; -} - -//------------------------------------------------------------------------------------------------- -void JetAIUpdate::friend_purgePendingCommand() -{ - setFlag(HAS_PENDING_COMMAND, false); -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void JetAIUpdate::crc( Xfer *xfer ) -{ - // extend base class - AIUpdateInterface::crc(xfer); -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void JetAIUpdate::xfer( Xfer *xfer ) -{ - - // version - XferVersion currentVersion = 2; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // extend base class - AIUpdateInterface::xfer(xfer); - - - xfer->xferCoord3D(&m_producerLocation); - m_mostRecentCommand.doXfer(xfer); - xfer->xferUnsignedInt(&m_attackLocoExpireFrame); - xfer->xferUnsignedInt(&m_attackersMissExpireFrame); - xfer->xferUnsignedInt(&m_returnToBaseFrame); - xfer->xferSTLObjectIDList(&m_targetedBy); - - xfer->xferUnsignedInt(&m_untargetableExpireFrame); - - // Set on create. - //AudioEventRTS m_afterburnerSound; ///< Sound when afterburners on - - AsciiString drawName; - if (m_lockonDrawable) { - drawName = m_lockonDrawable->getTemplate()->getName(); - } - xfer->xferAsciiString(&drawName); - if (drawName.isNotEmpty() && m_lockonDrawable==NULL) - { - const ThingTemplate* tt = TheThingFactory->findTemplate(drawName); - if (tt) - { - m_lockonDrawable = TheThingFactory->newDrawable(tt); - } - } - xfer->xferInt(&m_flags); - - if( version >= 2 ) - { - xfer->xferBool( &m_enginesOn ); - } - else - { - //We don't have to be accurate -- this is a patch. - if( getFlag(TAKEOFF_IN_PROGRESS) || getFlag(LANDING_IN_PROGRESS) || getObject()->isSignificantlyAboveTerrain() || getObject()->isKindOf( KINDOF_PRODUCED_AT_HELIPAD ) ) - { - m_enginesOn = TRUE; - } - else - { - m_enginesOn = FALSE; - } - } - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void JetAIUpdate::loadPostProcess( void ) -{ - //When drawables are created, so are their ambient sounds. After loading, only turn off the - //ambient sound if the engine is off. - if( !m_enginesOn ) - { - Drawable *draw = getObject()->getDrawable(); - if( draw ) - { - draw->stopAmbientSound(); - } - } - - // extend base class - AIUpdateInterface::loadPostProcess(); -} // end loadPostProcess + virtual const char* debugGetName() { return "PartitionFilterHasParkingPlace"; } +#endif + virtual Bool allow(Object *objOther) + { + ParkingPlaceBehaviorInterface* pp = getPP(objOther->getID()); + if (pp != NULL && pp->reserveSpace(m_id, 0.0f, NULL)) + return true; + return false; + } +}; + +//------------------------------------------------------------------------------------------------- +static Object* findSuitableAirfield(Object* jet) +{ + PartitionFilterAcceptByKindOf filterKind(MAKE_KINDOF_MASK(KINDOF_FS_AIRFIELD), KINDOFMASK_NONE); + PartitionFilterRejectByObjectStatus filterStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_UNDER_CONSTRUCTION ), OBJECT_STATUS_MASK_NONE ); + PartitionFilterRejectByObjectStatus filterStatusTwo( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_SOLD ), OBJECT_STATUS_MASK_NONE ); // Independent to make it an OR + PartitionFilterRelationship filterTeam(jet, PartitionFilterRelationship::ALLOW_ALLIES); + PartitionFilterAlive filterAlive; + PartitionFilterSameMapStatus filterMapStatus(jet); + PartitionFilterHasParkingPlace filterPP(jet->getID()); + + PartitionFilter *filters[16]; + Int numFilters = 0; + filters[numFilters++] = &filterKind; + filters[numFilters++] = &filterStatus; + filters[numFilters++] = &filterStatusTwo; + filters[numFilters++] = &filterTeam; + filters[numFilters++] = &filterAlive; + filters[numFilters++] = &filterPP; + filters[numFilters++] = &filterMapStatus; + filters[numFilters] = NULL; + + return ThePartitionManager->getClosestObject( jet, HUGE_DIST, FROM_CENTER_2D, filters ); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- +/* + Success: we have runway clearance + Failure: no runway clearance +*/ +class JetAwaitingRunwayState : public State +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetAwaitingRunwayState, "JetAwaitingRunwayState") +protected: + // snapshot interface STUBBED. + virtual void crc( Xfer *xfer ){}; + virtual void xfer( Xfer *xfer ){XferVersion cv = 1; XferVersion v = cv; xfer->xferVersion( &v, cv );} + virtual void loadPostProcess(){}; +private: + const Bool m_landing; + +public: + JetAwaitingRunwayState( StateMachine *machine, Bool landing ) : m_landing(landing), State( machine, "JetAwaitingRunwayState") { } + + virtual StateReturnType onEnter() + { + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if( !jetAI ) + return STATE_FAILURE; + + jetAI->friend_setTakeoffInProgress(!m_landing); + jetAI->friend_setLandingInProgress(m_landing); + jetAI->friend_setAllowCircling(true); + return STATE_CONTINUE; + } + + virtual StateReturnType update() + { + Object* jet = getMachineOwner(); + if (jet->isEffectivelyDead()) + return STATE_FAILURE; + + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if( !jetAI ) + return STATE_FAILURE; + + ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); + if (pp == NULL) + { + // no producer? just skip this step. + return STATE_SUCCESS; + } + + // gotta reserve a space in order to reserve a runway + if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), NULL)) + { + DEBUG_ASSERTCRASH(m_landing, ("hmm, this should never happen for taking-off things")); + return STATE_FAILURE; + } + + if (pp->reserveRunway(jet->getID(), m_landing)) + { + return STATE_SUCCESS; + } + else if( jet->testStatus( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) && !m_landing ) + { + //If we're trying to take off an aircraft carrier and fail to reserve a + //runway, it's because we need to be at the front of the carrier queue. + //Therefore, we need to move forward whenever possible until we are in + //the front. + Coord3D bestPos; + if( pp->calcBestParkingAssignment( jet->getID(), &bestPos ) ) + { + jetAI->friend_setTaxiInProgress(true); + jetAI->friend_setAllowAirLoco(false); + jetAI->chooseLocomotorSet(LOCOMOTORSET_TAXIING); + + jetAI->destroyPath(); + Path *movePath; + movePath = newInstance(Path); + Coord3D pos = *jet->getPosition(); + movePath->prependNode( &pos, LAYER_GROUND ); + movePath->markOptimized(); + movePath->appendNode( &bestPos, LAYER_GROUND ); + + TheAI->pathfinder()->setDebugPath(movePath); + + jetAI->friend_setPath( movePath ); + DEBUG_ASSERTCRASH(jetAI->getCurLocomotor(), ("no loco")); + jetAI->getCurLocomotor()->setUsePreciseZPos(true); + jetAI->getCurLocomotor()->setUltraAccurate(true); + jetAI->getCurLocomotor()->setAllowInvalidPosition(true); + jetAI->ignoreObstacleID(jet->getProducerID()); + } + } + + // can't get a runway? gotta wait. + jetAI->setLocomotorGoalNone(); + return STATE_CONTINUE; + } + + virtual void onExit(StateExitType status) + { + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if (jetAI) + { + jetAI->friend_setTakeoffInProgress(false); + jetAI->friend_setLandingInProgress(false); + jetAI->friend_setAllowCircling(false); + } + } + +}; +EMPTY_DTOR(JetAwaitingRunwayState) + +//------------------------------------------------------------------------------------------------- +/* + Success: a new suitable airfield has appeared + Failure: shouldn't normally happen +*/ +class JetOrHeliCirclingDeadAirfieldState : public State +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetOrHeliCirclingDeadAirfieldState, "JetOrHeliCirclingDeadAirfieldState") +protected: + // snapshot interface STUBBED. + // The state will check immediately after a load game, but I think that's ok. jba. + virtual void crc( Xfer *xfer ){}; + virtual void xfer( Xfer *xfer ){XferVersion cv = 1; XferVersion v = cv; xfer->xferVersion( &v, cv );} + virtual void loadPostProcess(){}; + +private: + Int m_checkAirfield; + + enum + { + // only recheck for new airfields every second or so + HOW_OFTEN_TO_CHECK = LOGICFRAMES_PER_SECOND + }; + +public: + JetOrHeliCirclingDeadAirfieldState( StateMachine *machine ) : + State( machine, "JetOrHeliCirclingDeadAirfieldState"), + m_checkAirfield(0) { } + + virtual StateReturnType onEnter() + { + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if( !jetAI ) + { + return STATE_FAILURE; + } + + // obscure case: if the jet wasn't spawned, but just placed directly on the map, + // it might not have an owning airfield, and it might be trying to return + // simply due to being idle, not out of ammo. so check and don't die in that + // case, but just punt back out to idle. + if (!jetAI->isOutOfSpecialReloadAmmo() && jet->getProducerID() == INVALID_ID) + { + return STATE_FAILURE; + } + + // just stay where we are. + jetAI->setLocomotorGoalNone(); + + m_checkAirfield = HOW_OFTEN_TO_CHECK; + + //Play the "low fuel" voice whenever the craft is circling above the airfield. + AudioEventRTS soundToPlay = *jet->getTemplate()->getPerUnitSound( "VoiceLowFuel" ); + soundToPlay.setObjectID( jet->getID() ); + TheAudio->addAudioEvent( &soundToPlay ); + + return STATE_CONTINUE; + } + + virtual StateReturnType update() + { + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if( !jetAI ) + { + return STATE_FAILURE; + } + + // just stay where we are. + jetAI->setLocomotorGoalNone(); + + Real damageRate = jetAI->friend_getOutOfAmmoDamagePerSecond(); + if (damageRate > 0) + { + // convert to damage/sec to damage/frame + damageRate *= SECONDS_PER_LOGICFRAME_REAL; + // since it's a percentage, multiply times the max health + damageRate *= jet->getBodyModule()->getMaxHealth(); + + DamageInfo damageInfo; + damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE; + damageInfo.in.m_deathType = DEATH_NORMAL; + damageInfo.in.m_sourceID = INVALID_ID; + damageInfo.in.m_amount = damageRate; + jet->attemptDamage( &damageInfo ); + } + + if (--m_checkAirfield <= 0) + { + m_checkAirfield = HOW_OFTEN_TO_CHECK; + Object* airfield = findSuitableAirfield( jet ); + if (airfield) + { + jet->setProducer(airfield); + return STATE_SUCCESS; + } + } + + return STATE_CONTINUE; + } + +}; +EMPTY_DTOR(JetOrHeliCirclingDeadAirfieldState) + +//------------------------------------------------------------------------------------------------- +/* + Success: we returned to the dead-airfield location + Failure: shouldn't normally happen +*/ +class JetOrHeliReturningToDeadAirfieldState : public AIInternalMoveToState +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetOrHeliReturningToDeadAirfieldState, "JetOrHeliReturningToDeadAirfieldState") +public: + JetOrHeliReturningToDeadAirfieldState( StateMachine *machine ) : AIInternalMoveToState( machine, "JetOrHeliReturningToDeadAirfieldState") { } + + virtual StateReturnType onEnter() + { + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if( !jetAI ) + { + return STATE_FAILURE; + } + + setAdjustsDestination(true); + m_goalPosition = *jetAI->friend_getProducerLocation(); + + return AIInternalMoveToState::onEnter(); + } + +}; +EMPTY_DTOR(JetOrHeliReturningToDeadAirfieldState) + +//------------------------------------------------------------------------------------------------- +// This solution uses the +// http://www.faqs.org/faqs/graphics/algorithms-faq/ +// Subject 1.03 +static Bool intersectInfiniteLine2D +( + Real ax, Real ay, Real ao, + Real cx, Real cy, Real co, + Real& ix, Real& iy +) +{ + Real bx = ax + Cos(ao); + Real by = ay + Sin(ao); + Real dx = cx + Cos(co); + Real dy = cy + Sin(co); + + Real denom = ((bx - ax) * (dy - cy) - (by - ay) * (dx - cx)); + if (denom == 0.0f) + { + // the lines are parallel. + return false; + } + + // The lines intersect. + Real r = ((ay - cy) * (dx - cx) - (ax - cx) * (dy - cy) ) / denom; + ix = ax + r * (bx - ax); + iy = ay + r * (by - ay); + return true; +} + +//------------------------------------------------------------------------------------------------- +/* + Success: we are on the ground at the runway start + Failure: we are unable to get on the ground +*/ +class JetOrHeliTaxiState : public AIMoveOutOfTheWayState +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetOrHeliTaxiState, "JetOrHeliTaxiState") +private: + TaxiType m_taxiMode; +public: + JetOrHeliTaxiState( StateMachine *machine, TaxiType m ) : m_taxiMode(m), AIMoveOutOfTheWayState( machine ) { } + + virtual StateReturnType onEnter() + { + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if( !jetAI ) + return STATE_FAILURE; + + jetAI->setCanPathThroughUnits(true); + jetAI->friend_setTakeoffInProgress(m_taxiMode != TO_PARKING); + jetAI->friend_setLandingInProgress(m_taxiMode == TO_PARKING); + jetAI->friend_setTaxiInProgress(true); + + if( m_taxiMode == TO_PARKING ) + { + //Instantly reload flares. + CountermeasuresBehaviorInterface *cbi = jet->getCountermeasuresBehaviorInterface(); + if( cbi ) + { + cbi->reloadCountermeasures(); + } + } + + jetAI->friend_setAllowAirLoco(false); + jetAI->chooseLocomotorSet(LOCOMOTORSET_TAXIING); + DEBUG_ASSERTCRASH(jetAI->getCurLocomotor(), ("no loco")); + + Object* airfield; + ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID(), &airfield); + if (pp == NULL) + return STATE_SUCCESS; // no airfield? just skip this step. + + ParkingPlaceBehaviorInterface::PPInfo ppinfo; + if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo)) + return STATE_FAILURE; // full? + + Coord3D intermedPt; + Bool intermed = false; + Real orient = atan2(ppinfo.runwayPrep.y - ppinfo.parkingSpace.y, ppinfo.runwayPrep.x - ppinfo.parkingSpace.x); + + + if (fabs(stdAngleDiff(orient, ppinfo.parkingOrientation)) > PI/128) + { + intermedPt.z = (ppinfo.parkingSpace.z + ppinfo.runwayPrep.z) * 0.5f; + intermed = intersectInfiniteLine2D( + ppinfo.parkingSpace.x, ppinfo.parkingSpace.y, ppinfo.parkingOrientation, + ppinfo.runwayPrep.x, ppinfo.runwayPrep.y, ppinfo.parkingOrientation + PI/2, + intermedPt.x, intermedPt.y); + } + + jetAI->destroyPath(); + Path *movePath; + movePath = newInstance(Path); + Coord3D pos = *jet->getPosition(); + movePath->prependNode( &pos, LAYER_GROUND ); + movePath->markOptimized(); + + if (m_taxiMode == TO_PARKING) + { + if( jet->testStatus( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) ) + { + //We're on an aircraft carrier. + const std::vector *pTaxiLocations = pp->getTaxiLocations( jet->getID() ); + if( pTaxiLocations ) + { + std::vector::const_iterator it; + for( it = pTaxiLocations->begin(); it != pTaxiLocations->end(); it++ ) + { + movePath->appendNode( &(*it), LAYER_GROUND ); + } + } + + //We just landed... see if we can get a better space forward so we don't stop and pause + //at our initially assigned spot. + Coord3D pos; + pp->calcBestParkingAssignment( jet->getID(), &pos ); + + movePath->appendNode( &pos, LAYER_GROUND ); + } + else + { + //We're on a normal airfield + movePath->appendNode( &ppinfo.runwayPrep, LAYER_GROUND ); + if (intermed) + movePath->appendNode( &intermedPt, LAYER_GROUND ); + movePath->appendNode( &ppinfo.parkingSpace, LAYER_GROUND ); + } + } + else if (m_taxiMode == FROM_PARKING) + { + if( jet->testStatus( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) ) + { + if( !(ppinfo.runwayStart == ppinfo.runwayPrep) ) + { + movePath->appendNode( &ppinfo.runwayStart, LAYER_GROUND ); + } + } + else + { + if (intermed) + movePath->appendNode( &intermedPt, LAYER_GROUND ); + movePath->appendNode( &ppinfo.runwayPrep, LAYER_GROUND ); + movePath->appendNode( &ppinfo.runwayStart, LAYER_GROUND ); + } + } + else if (m_taxiMode == FROM_HANGAR) + { + if( jet->testStatus( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) ) + { + //Aircraft carrier + if( jet->testStatus( OBJECT_STATUS_REASSIGN_PARKING ) ) + { + //This status means we are being reassigned a parking space. We're not actually moving from the + //hangar. So simply move to the new parking spot which was just switched from under us in + //FlightDeckBehavior::update() + jet->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_REASSIGN_PARKING ) ); + movePath->appendNode( &ppinfo.runwayPrep, LAYER_GROUND ); + } + else + { + const std::vector *pCreationLocations = pp->getCreationLocations( jet->getID() ); + if( !pCreationLocations ) + { + DEBUG_CRASH( ("No creation locations specified for runway for JetAIBehavior -- taxiing from hanger (Kris).") ); + return STATE_FAILURE; + } + std::vector::const_iterator it; + Bool firstNode = TRUE; + for( it = pCreationLocations->begin(); it != pCreationLocations->end(); it++ ) + { + if( firstNode ) + { + //Skip the first node because it's the creation location. + firstNode = FALSE; + continue; + } + movePath->appendNode( &(*it), LAYER_GROUND ); + } + movePath->appendNode( &ppinfo.runwayPrep, LAYER_GROUND ); + } + } + else + { + //Airfield + movePath->appendNode( &ppinfo.parkingSpace, LAYER_GROUND ); + } + } + + m_waitingForPath = FALSE; + TheAI->pathfinder()->setDebugPath(movePath); + + setAdjustsDestination(false); // precision is necessary + + jetAI->friend_setPath( movePath ); + DEBUG_ASSERTCRASH(jetAI->getCurLocomotor(), ("no loco")); + jetAI->getCurLocomotor()->setUsePreciseZPos(true); + jetAI->getCurLocomotor()->setUltraAccurate(true); + jetAI->getCurLocomotor()->setAllowInvalidPosition(true); + jetAI->ignoreObstacleID(jet->getProducerID()); + + StateReturnType ret = AIMoveOutOfTheWayState::onEnter(); + return ret; + } + + virtual StateReturnType update() + { + Object* jet = getMachineOwner(); + if (jet->isEffectivelyDead()) + return STATE_FAILURE; + + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if( !jetAI ) + return STATE_FAILURE; + + if( m_taxiMode == TO_PARKING || m_taxiMode == FROM_HANGAR ) + { + //Keep checking to see if there is a better spot as it moves forward. If we find a better spot, then + //append the position to our move. + ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); + Coord3D bestPos; + Int oldIndex, newIndex; + // Check pp for null, as it is possible for your airfield to get destroyed while taxiing.jba [8/27/2003] + if( pp!=NULL && pp->calcBestParkingAssignment( jet->getID(), &bestPos, &oldIndex, &newIndex ) ) + { + Path *path = jetAI->friend_getPath(); + if( path ) + { + path->appendNode( &bestPos, LAYER_GROUND ); + } + } + } + + return AIMoveOutOfTheWayState::update(); + } + + virtual void onExit( StateExitType status ) + { + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if (jetAI) + { + jetAI->getCurLocomotor()->setUsePreciseZPos(false); + jetAI->getCurLocomotor()->setUltraAccurate(false); + jetAI->getCurLocomotor()->setAllowInvalidPosition(false); + jetAI->friend_setTakeoffInProgress(false); + jetAI->friend_setLandingInProgress(false); + jetAI->friend_setTaxiInProgress(false); + jetAI->setCanPathThroughUnits(false); + } + + AIMoveOutOfTheWayState::onExit(status); + } + +}; +EMPTY_DTOR(JetOrHeliTaxiState) + +//------------------------------------------------------------------------------------------------- +/* + Success: we are on the ground at the runway start + Failure: we are unable to get on the ground +*/ +class JetTakeoffOrLandingState : public AIFollowPathState +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetTakeoffOrLandingState, "JetTakeoffOrLandingState") +private: + Real m_maxLift; + Real m_maxSpeed; +#ifdef CIRCLE_FOR_LANDING + Coord3D m_circleForLandingPos; +#endif + Bool m_landing; + Bool m_landingSoundPlayed; + +public: + JetTakeoffOrLandingState( StateMachine *machine, Bool landing ) : m_landing(landing), AIFollowPathState( machine, "JetTakeoffOrLandingState" ) { } + + virtual StateReturnType onEnter() + { + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if (!jetAI) + return STATE_FAILURE; + + if (jet->isEffectivelyDead()) + return STATE_FAILURE; + + jetAI->friend_setTakeoffInProgress(!m_landing); + jetAI->friend_setLandingInProgress(m_landing); + jetAI->friend_setAllowAirLoco(true); + jetAI->chooseLocomotorSet(LOCOMOTORSET_NORMAL); + Locomotor* loco = jetAI->getCurLocomotor(); + DEBUG_ASSERTCRASH(loco, ("no loco")); + loco->setMaxLift(BIGNUM); + BodyDamageType bdt = jet->getBodyModule()->getDamageState(); + m_maxLift = loco->getMaxLift(bdt); + m_maxSpeed = loco->getMaxSpeedForCondition(bdt); + m_landingSoundPlayed = FALSE; + if (m_landing) + { + loco->setMaxSpeed(loco->getMinSpeed()); + } + else + { + loco->setMaxLift(0); + } + loco->setUsePreciseZPos(true); + loco->setUltraAccurate(true); + jetAI->ignoreObstacleID(jet->getProducerID()); + + ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); + if (pp == NULL) + return STATE_SUCCESS; // no airfield? just skip this step + + ParkingPlaceBehaviorInterface::PPInfo ppinfo; + if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo)) + { + // it's full. + return STATE_FAILURE; + } + + // only check this for landing; we might have already given up the reservation to the guy behind us for takeoff + if (m_landing) + { + if (!pp->reserveRunway(jet->getID(), m_landing)) + { + DEBUG_CRASH(("we should never get to this state unless we have a runway available")); + return STATE_FAILURE; + } + } + + std::vector path; + if (m_landing) + { +#ifdef CIRCLE_FOR_LANDING + m_circleForLandingPos = ppinfo.runwayApproach; + m_circleForLandingPos.z = (ppinfo.runwayEnd.z + ppinfo.runwayApproach.z)*0.5f; +#else + path.push_back(ppinfo.runwayApproach); +#endif + if( jet->testStatus( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) ) + { + //Assigned to an aircraft carrier which has separate landing strips. + path.push_back( ppinfo.runwayLandingStart ); + path.push_back( ppinfo.runwayLandingEnd ); + } + else + { + //Assigned to an airstrip -- land the same way we took off but in reverse. + path.push_back(ppinfo.runwayEnd); + path.push_back(ppinfo.runwayStart); + } + } + else + { + ppinfo.runwayEnd.z = ppinfo.runwayApproach.z; + path.push_back(ppinfo.runwayEnd); + path.push_back(ppinfo.runwayExit); + } + + setAdjustsDestination(false); // precision is necessary + setAdjustFinalDestination(false); // especially at the endpoint! + + jetAI->friend_setGoalPath( &path ); + + StateReturnType ret = AIFollowPathState::onEnter(); + + return ret; + } + + virtual StateReturnType update() + { + Object* jet = getMachineOwner(); + if (jet->isEffectivelyDead()) + return STATE_FAILURE; + + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if( !jetAI ) + return STATE_FAILURE; + + if (m_landing) + { +#ifdef CIRCLE_FOR_LANDING + if (jet->getPosition()->z > m_circleForLandingPos.z) + { + const Real THRESH = 4.0f; + jetAI->getCurLocomotor()->setAltitudeChangeThresholdForCircling(THRESH); + jetAI->setLocomotorGoalPositionExplicit(m_circleForLandingPos); + return STATE_CONTINUE; + } + else +#endif + { + jetAI->getCurLocomotor()->setMaxLift(BIGNUM); +#ifdef CIRCLE_FOR_LANDING + jetAI->getCurLocomotor()->setAltitudeChangeThresholdForCircling(0); +#endif + } + + if( !m_landingSoundPlayed ) + { + ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); + Real zPos = jet->getPosition()->z; + Real zSlop = 0.25f; + PathfindLayerEnum layer = TheTerrainLogic->getHighestLayerForDestination( jet->getPosition() ); + Real groundZ = TheTerrainLogic->getLayerHeight( jet->getPosition()->x, jet->getPosition()->y, layer ); + if( pp ) + { + groundZ += pp->getLandingDeckHeightOffset(); + } + + if( zPos - zSlop <= groundZ ) + { + m_landingSoundPlayed = TRUE; + AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_aircraftWheelScreech; + soundToPlay.setPosition( jet->getPosition() ); + TheAudio->addAudioEvent( &soundToPlay ); + } + } + } + else + { + ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); + if (pp) + pp->transferRunwayReservationToNextInLineForTakeoff(jet->getID()); + + //Calculate the distance of the jet from the end of the runway as a ratio from the start. + //As it approaches the end of the runway, the plane will gain more lift, even if it's already + //going quickly. Using speed for lift is bad in the case of the aircraft carrier, because + //we don't want it to take off quickly. + ParkingPlaceBehaviorInterface::PPInfo ppinfo; + pp->calcPPInfo( jet->getID(), &ppinfo ); + Coord3D vector = ppinfo.runwayEnd; + vector.sub( jet->getPosition() ); + Real dist = vector.length(); + + Real ratio = 1.0f - (dist / ppinfo.runwayTakeoffDist); + ratio *= ratio; //dampen it.... + if (ratio < 0.0f) ratio = 0.0f; + if (ratio > 1.0f) ratio = 1.0f; + jetAI->getCurLocomotor()->setMaxLift(m_maxLift * ratio); + } + + StateReturnType ret = AIFollowPathState::update(); + return ret; + } + + virtual void onExit( StateExitType status ) + { + AIFollowPathState::onExit(status); + + // just in case. + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if( !jetAI ) + return; + + jetAI->friend_setTakeoffInProgress(false); + jetAI->friend_setLandingInProgress(false); + jetAI->friend_enableAfterburners(false); + + // Paranoia checks - sometimes onExit is called when we are + // shutting down, and not all pieces are valid. CurLocomotor + // is definitely null in some cases. jba. + Locomotor* loco = jetAI->getCurLocomotor(); + if (loco) + { + loco->setUsePreciseZPos(false); + loco->setUltraAccurate(false); + // don't restore lift if dead -- this may fight with JetSlowDeathBehavior! + if (!jet->isEffectivelyDead()) + loco->setMaxLift(BIGNUM); +#ifdef CIRCLE_FOR_LANDING + loco->setAltitudeChangeThresholdForCircling(0); +#endif + } + jetAI->ignoreObstacleID(INVALID_ID); + ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); + if (!m_landing) + { + if (pp && !jetAI->friend_keepsParkingSpaceWhenAirborne()) + pp->releaseSpace(jet->getID()); + } + if (pp) + pp->releaseRunway(jet->getID()); + } +}; +EMPTY_DTOR(JetTakeoffOrLandingState) + +//------------------------------------------------------------------------------------------------- +static Real calcDistSqr(const Coord3D& a, const Coord3D& b) +{ + return sqr(a.x-b.x) + sqr(a.y-b.y) + sqr(a.z-b.z); +} + +//------------------------------------------------------------------------------------------------- +/* + Success: we are on the ground at the runway start + Failure: we are unable to get on the ground +*/ +class HeliTakeoffOrLandingState : public State +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(HeliTakeoffOrLandingState, "HeliTakeoffOrLandingState") +protected: + // snapshot interface + virtual void crc( Xfer *xfer ) + { + // empty. jba. + } + + virtual void xfer( Xfer *xfer ) + { + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // set on create. xfer->xferBool(&m_landing); + xfer->xferCoord3D(&m_path[0]); + xfer->xferCoord3D(&m_path[1]); + xfer->xferInt(&m_index); + xfer->xferCoord3D(&m_parkingLoc); + xfer->xferReal(&m_parkingOrientation); + } + virtual void loadPostProcess() + { + // empty. jba. + } + +private: + Coord3D m_path[2]; + Int m_index; + Coord3D m_parkingLoc; + Real m_parkingOrientation; + Bool m_landing; +public: + HeliTakeoffOrLandingState( StateMachine *machine, Bool landing ) : m_landing(landing), + State( machine, "HeliTakeoffOrLandingState" ), m_index(0) + { + m_parkingLoc.zero(); + } + + virtual StateReturnType onEnter() + { + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if( !jetAI ) + return STATE_FAILURE; + + jetAI->friend_setTakeoffInProgress(!m_landing); + jetAI->friend_setLandingInProgress(m_landing); + jetAI->friend_setAllowAirLoco(true); + jetAI->chooseLocomotorSet(LOCOMOTORSET_NORMAL); + + Locomotor* loco = jetAI->getCurLocomotor(); + DEBUG_ASSERTCRASH(loco, ("no loco")); + loco->setUsePreciseZPos(true); + loco->setUltraAccurate(true); + jetAI->ignoreObstacleID(jet->getProducerID()); + + Object* airfield; + ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID(), &airfield); + if (pp == NULL) + return STATE_SUCCESS; // no airfield? just skip this step + + Coord3D landingApproach; + if (jet->isKindOf(KINDOF_PRODUCED_AT_HELIPAD)) + { + if (m_landing) + { + m_parkingLoc = jetAI->friend_getLandingPosForHelipadStuff(); + m_parkingOrientation = jet->getOrientation(); + } + else + { + m_parkingOrientation = jet->getOrientation(); + m_parkingLoc = *jet->getPosition(); + } + landingApproach = m_parkingLoc; + landingApproach.z += pp->getApproachHeight() + pp->getLandingDeckHeightOffset(); + } + else + { + ParkingPlaceBehaviorInterface::PPInfo ppinfo; + + if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo)) + return STATE_FAILURE; + m_parkingLoc = ppinfo.parkingSpace; + m_parkingOrientation = ppinfo.parkingOrientation; + landingApproach = m_parkingLoc; + landingApproach.z += (ppinfo.runwayApproach.z - ppinfo.runwayEnd.z); + } + + if (m_landing) + { + m_path[0] = landingApproach; + m_path[1] = m_parkingLoc; + } + else + { + m_path[0] = m_parkingLoc; + m_path[1] = landingApproach; + m_path[1].z = landingApproach.z; + } + m_index = 0; + + return STATE_CONTINUE; + } + + virtual StateReturnType update() + { + Object* jet = getMachineOwner(); + if (jet->isEffectivelyDead()) + return STATE_FAILURE; + + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if( !jetAI ) + return STATE_FAILURE; + +// I have disabled this because it is no longer necessary and is a bit funky lookin' (srj) +#ifdef NOT_IN_USE + // magically position it correctly. + jet->getPhysics()->scrubVelocity2D(0); + Coord3D hoverloc = m_path[m_index]; + hoverloc.z = jet->getPosition()->z; +#if 1 + Coord3D pos = *jet->getPosition(); + Real dx = hoverloc.x - pos.x; + Real dy = hoverloc.y - pos.y; + Real dSqr = dx*dx+dy*dy; + const Real DARN_CLOSE = 0.25f; + if (dSqr < DARN_CLOSE) + { + jet->setPosition(&hoverloc); + } + else + { + Real dist = sqrtf(dSqr); + if (dist<1) dist = 1; + pos.x += PATHFIND_CELL_SIZE_F*dx/(dist*LOGICFRAMES_PER_SECOND); + pos.y += PATHFIND_CELL_SIZE_F*dy/(dist*LOGICFRAMES_PER_SECOND); + jet->setPosition(&pos); + } +#else + jet->setPosition(&hoverloc); +#endif + jet->setOrientation(m_parkingOrientation); +#endif + + if (jet->isKindOf(KINDOF_PRODUCED_AT_HELIPAD) || !m_landing) + { + TheAI->pathfinder()->adjustDestination(jet, jetAI->getLocomotorSet(), &m_path[m_index]); + TheAI->pathfinder()->updateGoal(jet, &m_path[m_index], LAYER_GROUND); + } + + jetAI->setLocomotorGoalPositionExplicit(m_path[m_index]); + + const Real THRESH = 3.0f; + const Real THRESH_SQR = THRESH*THRESH; + const Coord3D* a = jet->getPosition(); + const Coord3D* b = &m_path[m_index]; + Real distSqr = calcDistSqr(*a, *b); + if (distSqr <= THRESH_SQR) + ++m_index; + + if (m_index >= 2) + return STATE_SUCCESS; + + return STATE_CONTINUE; + } + + virtual void onExit( StateExitType status ) + { + // just in case. + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if( !jetAI ) + return; + + jetAI->friend_setTakeoffInProgress(false); + jetAI->friend_setLandingInProgress(false); + + // Paranoia checks - sometimes onExit is called when we are + // shutting down, and not all pieces are valid. CurLocomotor + // is definitely null in some cases. jba. + Locomotor* loco = jetAI->getCurLocomotor(); + if (loco) + { + loco->setUsePreciseZPos(false); + loco->setUltraAccurate(false); + // don't restore lift if dead -- this may fight with JetSlowDeathBehavior! + if (!jet->isEffectivelyDead()) + loco->setMaxLift(BIGNUM); + } + + jetAI->ignoreObstacleID(INVALID_ID); + ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); + if (m_landing) + { + jetAI->friend_setAllowAirLoco(false); + jetAI->chooseLocomotorSet(LOCOMOTORSET_TAXIING); + } + else + { + if (pp && !jetAI->friend_keepsParkingSpaceWhenAirborne()) + pp->releaseSpace(jet->getID()); + } + } + +}; +EMPTY_DTOR(HeliTakeoffOrLandingState) + +//------------------------------------------------------------------------------------------------- +class JetOrHeliParkOrientState : public State +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetOrHeliParkOrientState, "JetOrHeliParkOrientState") +protected: + // snapshot interface STUBBED. + virtual void crc( Xfer *xfer ){}; + virtual void xfer( Xfer *xfer ){XferVersion cv = 1; XferVersion v = cv; xfer->xferVersion( &v, cv );} + virtual void loadPostProcess(){}; + +public: + JetOrHeliParkOrientState( StateMachine *machine ) : State( machine, "JetOrHeliParkOrientState") { } + + virtual StateReturnType onEnter() + { + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if( !jetAI ) + return STATE_FAILURE; + + if (jet->isKindOf(KINDOF_PRODUCED_AT_HELIPAD)) + { + return STATE_SUCCESS; + } + + jetAI->friend_setTakeoffInProgress(false); + jetAI->friend_setLandingInProgress(true); + + jetAI->ignoreObstacleID(jet->getProducerID()); + return STATE_CONTINUE; + } + + virtual StateReturnType update() + { + Object* jet = getMachineOwner(); + if (jet->isEffectivelyDead()) + return STATE_FAILURE; + + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if( !jetAI ) + { + return STATE_FAILURE; + } + + ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); + if (pp == NULL) + return STATE_FAILURE; + + ParkingPlaceBehaviorInterface::PPInfo ppinfo; + if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo)) + return STATE_FAILURE; + + const Real THRESH = 0.001f; + if (fabs(stdAngleDiff(jet->getOrientation(), ppinfo.parkingOrientation)) <= THRESH) + return STATE_SUCCESS; + + // magically position it correctly. + jet->getPhysics()->scrubVelocity2D(0); + Coord3D hoverloc = ppinfo.parkingSpace; + if( jet->testStatus( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) ) + { + hoverloc = ppinfo.runwayPrep; + } + + hoverloc.z = jet->getPosition()->z; + jet->setPosition(&hoverloc); + + jetAI->setLocomotorGoalOrientation(ppinfo.parkingOrientation); + + return STATE_CONTINUE; + } + + virtual void onExit( StateExitType status ) + { + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if( !jetAI ) + return; + + jetAI->friend_setTakeoffInProgress(false); + jetAI->friend_setLandingInProgress(false); + jetAI->ignoreObstacleID(INVALID_ID); + } +}; +EMPTY_DTOR(JetOrHeliParkOrientState) + +//------------------------------------------------------------------------------------------------- +class JetPauseBeforeTakeoffState : public AIFaceState +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetPauseBeforeTakeoffState, "JetPauseBeforeTakeoffState") +protected: + // snapshot interface + virtual void crc( Xfer *xfer ) + { + // empty. jba. + } + + virtual void xfer( Xfer *xfer ) + { + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // set on create. xfer->xferBool(&m_landing); + xfer->xferUnsignedInt(&m_when); + xfer->xferUnsignedInt(&m_whenTransfer); + xfer->xferBool(&m_afterburners); + xfer->xferBool(&m_resetTimer); + xfer->xferObjectID(&m_waitedForTaxiID); + } + virtual void loadPostProcess() + { + // empty. jba. + } + +private: + UnsignedInt m_when; + UnsignedInt m_whenTransfer; + ObjectID m_waitedForTaxiID; + Bool m_resetTimer; + Bool m_afterburners; + + Bool findWaiter() + { + Object* jet = getMachineOwner(); + ParkingPlaceBehaviorInterface* pp = getPP(getMachineOwner()->getProducerID()); + if (pp) + { + Int count = pp->getRunwayCount(); + for (Int i = 0; i < count; ++i) + { + Object* otherJet = TheGameLogic->findObjectByID( pp->getRunwayReservation( i, RESERVATION_TAKEOFF ) ); + if (otherJet == NULL || otherJet == jet) + continue; + + AIUpdateInterface* ai = otherJet->getAIUpdateInterface(); + if (ai == NULL) + continue; + + if (ai->getCurrentStateID() == TAXI_TO_TAKEOFF) + { + if (m_waitedForTaxiID == INVALID_ID) + { + m_waitedForTaxiID = otherJet->getID(); + } + return true; + } + } + } + return false; + } + +public: + JetPauseBeforeTakeoffState( StateMachine *machine ) : + AIFaceState(machine, false), + m_when(0), + m_whenTransfer(0), + m_waitedForTaxiID(INVALID_ID), + m_resetTimer(false), + m_afterburners(false) + { + // nothing + } + + virtual StateReturnType onEnter() + { + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if( !jetAI ) + return STATE_FAILURE; + + jetAI->friend_setTakeoffInProgress(true); + jetAI->friend_setLandingInProgress(false); + + m_when = 0; + m_whenTransfer = 0; + m_waitedForTaxiID = INVALID_ID; + m_resetTimer = false; + m_afterburners = false; + + ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); + if (pp == NULL) + return STATE_SUCCESS; // no airfield? just skip this step. + + ParkingPlaceBehaviorInterface::PPInfo ppinfo; + if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo)) + return STATE_SUCCESS; // full? + + getMachine()->setGoalPosition(&ppinfo.runwayEnd); + + return AIFaceState::onEnter(); + } + + virtual StateReturnType update() + { + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if (jet->isEffectivelyDead()) + return STATE_FAILURE; + + // always call this. + StateReturnType superStatus = AIFaceState::update(); + + if (findWaiter()) + return STATE_CONTINUE; + + UnsignedInt now = TheGameLogic->getFrame(); + if (!m_resetTimer) + { + // we had to wait, but now everyone else is ready, so restart our countdown. + m_when = now + jetAI->friend_getTakeoffPause(); + if (m_waitedForTaxiID == INVALID_ID) + { + m_waitedForTaxiID = jet->getID(); // just so we don't pick up anyone else + m_whenTransfer = now + 1; + } + else + { + m_whenTransfer = now + 2; // 2 seems odd, but is correct + } + m_resetTimer = true; + } + + if (!m_afterburners) + { + jetAI->friend_enableAfterburners(true); + m_afterburners = true; + } + + DEBUG_ASSERTCRASH(m_when != 0, ("hmm")); + DEBUG_ASSERTCRASH(m_whenTransfer != 0, ("hmm")); + + // once we start the final wait, release the runways for guys behind us, so they can start taxiing + ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); + if (pp && now >= m_whenTransfer) + { + pp->transferRunwayReservationToNextInLineForTakeoff(jet->getID()); + } + + if (now >= m_when) + return superStatus; + + return STATE_CONTINUE; + } + + virtual void onExit(StateExitType status) + { + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + jetAI->friend_setTakeoffInProgress(false); + jetAI->friend_setLandingInProgress(false); + AIFaceState::onExit(status); + } + +}; +EMPTY_DTOR(JetPauseBeforeTakeoffState) + +//------------------------------------------------------------------------------------------------- +class JetOrHeliReloadAmmoState : public State +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetOrHeliReloadAmmoState, "JetOrHeliReloadAmmoState") +private: + UnsignedInt m_reloadTime; + UnsignedInt m_reloadDoneFrame; + +protected: + + // snapshot interface + virtual void crc( Xfer *xfer ) + { + // empty. jba. + } + + virtual void xfer( Xfer *xfer ) + { + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // set on create. xfer->xferBool(&m_landing); + xfer->xferUnsignedInt(&m_reloadTime); + xfer->xferUnsignedInt(&m_reloadDoneFrame); + } + virtual void loadPostProcess() + { + // empty. jba. + } + +public: + JetOrHeliReloadAmmoState( StateMachine *machine ) : State( machine, "JetOrHeliReloadAmmoState") { } + + virtual StateReturnType onEnter() + { + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if( !jetAI ) + return STATE_FAILURE; + + jetAI->friend_setTakeoffInProgress(false); + jetAI->friend_setLandingInProgress(false); + jetAI->friend_setUseSpecialReturnLoco(false); + + // AW: Workaround for VTOL aircraft rotating towards 0 degrees on reloading. + if (!jetAI->friend_needsRunway()) { + ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); + ParkingPlaceBehaviorInterface::PPInfo ppinfo; + if ((pp) && pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo)) { + // DEBUG_LOG((">> JetOrHeliReloadAmmoState - onEnter - parkingOrientation = %f.\n", ppinfo.parkingOrientation)); + jetAI->setLocomotorGoalOrientation(ppinfo.parkingOrientation); + } + } + + m_reloadTime = 0; + for (Int i = 0; i < WEAPONSLOT_COUNT; ++i) + { + const Weapon* w = jet->getWeaponInWeaponSlot((WeaponSlotType)i); + if (w == NULL) + continue; + + Int remaining = w->getRemainingAmmo(); + Int clipSize = w->getClipSize(); + Int rt = w->getClipReloadTime(jet); + if (clipSize > 0) + { + // bias by amount empty. + Int needed = clipSize - remaining; + rt = (rt * needed) / clipSize; + } + if (rt > m_reloadTime) + m_reloadTime = rt; + } + + if (m_reloadTime < 1) + m_reloadTime = 1; + m_reloadDoneFrame = m_reloadTime + TheGameLogic->getFrame(); + return STATE_CONTINUE; + } + + virtual StateReturnType update() + { + Object* jet = getMachineOwner(); + + UnsignedInt now = TheGameLogic->getFrame(); + Bool allDone = true; + for (Int i = 0; i < WEAPONSLOT_COUNT; ++i) + { + Weapon* w = jet->getWeaponInWeaponSlot((WeaponSlotType)i); + if (w == NULL) + continue; + + if (now >= m_reloadDoneFrame) + w->setClipPercentFull(1.0f, false); + else + w->setClipPercentFull((Real)(m_reloadTime - (m_reloadDoneFrame - now)) / m_reloadTime, false); + + if (w->getRemainingAmmo() != w->getClipSize()) + allDone = false; + } + + if (allDone) + return STATE_SUCCESS; + + return STATE_CONTINUE; + } + + virtual void onExit(StateExitType status) + { + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + jetAI->friend_setTakeoffInProgress(false); + jetAI->friend_setLandingInProgress(false); + } + +}; +EMPTY_DTOR(JetOrHeliReloadAmmoState) + +//------------------------------------------------------------------------------------------------- +/* + Success: we are close enough to a friendly airfield to land + Failure: we are unable to get close enough to a friendly airfield to land +*/ +class JetOrHeliReturnForLandingState : public AIInternalMoveToState +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(JetOrHeliReturnForLandingState, "JetOrHeliReturnForLandingState") +public: + JetOrHeliReturnForLandingState( StateMachine *machine ) : AIInternalMoveToState( machine, "JetOrHeliReturnForLandingState") { } + + virtual StateReturnType onEnter() + { + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + + ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); + if (pp == NULL) + { + // nuke the producer id, since it's dead + jet->setProducer(NULL); + + Object* airfield = findSuitableAirfield( jet ); + pp = airfield ? getPP(airfield->getID()) : NULL; + if (airfield && pp) + { + jet->setProducer(airfield); + } + else + { + return STATE_FAILURE; + } + } + + if (jet->isKindOf(KINDOF_PRODUCED_AT_HELIPAD)) + { + m_goalPosition = jetAI->friend_getLandingPosForHelipadStuff(); + } + else + { + ParkingPlaceBehaviorInterface::PPInfo ppinfo; + if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo)) + return STATE_FAILURE; + + m_goalPosition = jetAI->friend_needsRunway() ? ppinfo.runwayApproach : ppinfo.parkingSpace; + } + setAdjustsDestination(false); // precision is necessary + + return AIInternalMoveToState::onEnter(); + } +}; +EMPTY_DTOR(JetOrHeliReturnForLandingState) + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- +class JetAIStateMachine : public AIStateMachine +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( JetAIStateMachine, "JetAIStateMachine" ); + +public: + JetAIStateMachine( Object *owner, AsciiString name ); + +}; + +//------------------------------------------------------------------------------------------------- +JetAIStateMachine::JetAIStateMachine(Object *owner, AsciiString name) : AIStateMachine(owner, name) +{ + defineState( RETURNING_FOR_LANDING, newInstance(JetOrHeliReturnForLandingState)( this ), LANDING_AWAIT_CLEARANCE, RETURN_TO_DEAD_AIRFIELD ); + defineState( TAKING_OFF_AWAIT_CLEARANCE, newInstance(JetAwaitingRunwayState)( this, false ), TAXI_TO_TAKEOFF, AI_IDLE ); + defineState( TAXI_TO_TAKEOFF, newInstance(JetOrHeliTaxiState)( this, FROM_PARKING ), PAUSE_BEFORE_TAKEOFF, AI_IDLE ); + defineState( PAUSE_BEFORE_TAKEOFF, newInstance(JetPauseBeforeTakeoffState)( this ), TAKING_OFF, AI_IDLE ); + defineState( TAKING_OFF, newInstance(JetTakeoffOrLandingState)( this, false ), AI_IDLE, AI_IDLE ); + defineState( LANDING_AWAIT_CLEARANCE, newInstance(JetAwaitingRunwayState)( this, true ), LANDING, AI_IDLE ); + defineState( LANDING, newInstance(JetTakeoffOrLandingState)( this, true ), TAXI_FROM_LANDING, AI_IDLE ); + defineState( TAXI_FROM_LANDING, newInstance(JetOrHeliTaxiState)( this, TO_PARKING ), ORIENT_FOR_PARKING_PLACE, AI_IDLE ); + defineState( TAXI_FROM_HANGAR, newInstance(JetOrHeliTaxiState)( this, FROM_HANGAR ), ORIENT_FOR_PARKING_PLACE, AI_IDLE ); + defineState( ORIENT_FOR_PARKING_PLACE, newInstance(JetOrHeliParkOrientState)( this ), RELOAD_AMMO, AI_IDLE ); + defineState( RELOAD_AMMO, newInstance(JetOrHeliReloadAmmoState)( this ), AI_IDLE, AI_IDLE ); + defineState( RETURN_TO_DEAD_AIRFIELD, newInstance(JetOrHeliReturningToDeadAirfieldState)( this ), CIRCLING_DEAD_AIRFIELD, RETURN_TO_DEAD_AIRFIELD ); + defineState( CIRCLING_DEAD_AIRFIELD, newInstance(JetOrHeliCirclingDeadAirfieldState)( this ), AI_IDLE, AI_IDLE ); +} + +//------------------------------------------------------------------------------------------------- +JetAIStateMachine::~JetAIStateMachine() +{ +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- +class HeliAIStateMachine : public AIStateMachine +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( HeliAIStateMachine, "HeliAIStateMachine" ); + +public: + HeliAIStateMachine( Object *owner, AsciiString name ); + +}; + +//------------------------------------------------------------------------------------------------- +HeliAIStateMachine::HeliAIStateMachine(Object *owner, AsciiString name) : AIStateMachine(owner, name) +{ + defineState( RETURNING_FOR_LANDING, newInstance(JetOrHeliReturnForLandingState)( this ), LANDING_AWAIT_CLEARANCE, RETURN_TO_DEAD_AIRFIELD ); + defineState( TAKING_OFF_AWAIT_CLEARANCE, newInstance(SuccessState)( this ), TAKING_OFF, AI_IDLE ); + defineState( TAKING_OFF, newInstance(HeliTakeoffOrLandingState)( this, false ), AI_IDLE, AI_IDLE ); + defineState( LANDING_AWAIT_CLEARANCE, newInstance(SuccessState)( this ), ORIENT_FOR_PARKING_PLACE, AI_IDLE ); + defineState( ORIENT_FOR_PARKING_PLACE, newInstance(JetOrHeliParkOrientState)( this ), LANDING, AI_IDLE ); + defineState( LANDING, newInstance(HeliTakeoffOrLandingState)( this, true ), RELOAD_AMMO, AI_IDLE ); + defineState( RELOAD_AMMO, newInstance(JetOrHeliReloadAmmoState)( this ), AI_IDLE, AI_IDLE ); + defineState( RETURN_TO_DEAD_AIRFIELD, newInstance(JetOrHeliReturningToDeadAirfieldState)( this ), CIRCLING_DEAD_AIRFIELD, RETURN_TO_DEAD_AIRFIELD ); + defineState( CIRCLING_DEAD_AIRFIELD, newInstance(JetOrHeliCirclingDeadAirfieldState)( this ), AI_IDLE, AI_IDLE ); + defineState( TAXI_FROM_HANGAR, newInstance(JetOrHeliTaxiState)( this, FROM_HANGAR ), AI_IDLE, AI_IDLE ); +} + +//------------------------------------------------------------------------------------------------- +HeliAIStateMachine::~HeliAIStateMachine() +{ +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- +JetAIUpdateModuleData::JetAIUpdateModuleData() +{ + m_outOfAmmoDamagePerSecond = 0; + m_needsRunway = true; + m_keepsParkingSpaceWhenAirborne = true; + m_takeoffDistForMaxLift = 0.0f; + m_minHeight = 0.0f; + m_parkingOffset = 0.0f; + m_sneakyOffsetWhenAttacking = 0.0f; + m_takeoffPause = 0; + m_attackingLoco = LOCOMOTORSET_NORMAL; + m_returningLoco = LOCOMOTORSET_NORMAL; + m_attackLocoPersistTime = 0; + m_attackersMissPersistTime = 0; + m_lockonTime = 0; + m_lockonCursor.clear(); + m_lockonInitialDist = 100; + m_lockonFreq = 0.5; + m_lockonAngleSpin = 720; + m_returnToBaseIdleTime = 0; +} + +//------------------------------------------------------------------------------------------------- +/*static*/ void JetAIUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + AIUpdateModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + { "OutOfAmmoDamagePerSecond", INI::parsePercentToReal, NULL, offsetof( JetAIUpdateModuleData, m_outOfAmmoDamagePerSecond ) }, + { "NeedsRunway", INI::parseBool, NULL, offsetof( JetAIUpdateModuleData, m_needsRunway ) }, + { "KeepsParkingSpaceWhenAirborne",INI::parseBool, NULL, offsetof( JetAIUpdateModuleData, m_keepsParkingSpaceWhenAirborne ) }, + { "TakeoffDistForMaxLift", INI::parsePercentToReal, NULL, offsetof( JetAIUpdateModuleData, m_takeoffDistForMaxLift ) }, + { "TakeoffPause", INI::parseDurationUnsignedInt, NULL, offsetof( JetAIUpdateModuleData, m_takeoffPause ) }, + { "MinHeight", INI::parseReal, NULL, offsetof( JetAIUpdateModuleData, m_minHeight ) }, + { "ParkingOffset", INI::parseReal, NULL, offsetof( JetAIUpdateModuleData, m_parkingOffset ) }, + { "SneakyOffsetWhenAttacking", INI::parseReal, NULL, offsetof( JetAIUpdateModuleData, m_sneakyOffsetWhenAttacking ) }, + { "AttackLocomotorType", INI::parseIndexList, TheLocomotorSetNames, offsetof( JetAIUpdateModuleData, m_attackingLoco ) }, + { "AttackLocomotorPersistTime", INI::parseDurationUnsignedInt, NULL, offsetof( JetAIUpdateModuleData, m_attackLocoPersistTime ) }, + { "AttackersMissPersistTime", INI::parseDurationUnsignedInt, NULL, offsetof( JetAIUpdateModuleData, m_attackersMissPersistTime ) }, + { "ReturnForAmmoLocomotorType", INI::parseIndexList, TheLocomotorSetNames, offsetof( JetAIUpdateModuleData, m_returningLoco ) }, + { "LockonTime", INI::parseDurationUnsignedInt, NULL, offsetof( JetAIUpdateModuleData, m_lockonTime ) }, + { "LockonCursor", INI::parseAsciiString, NULL, offsetof( JetAIUpdateModuleData, m_lockonCursor ) }, + { "LockonInitialDist", INI::parseReal, NULL, offsetof( JetAIUpdateModuleData, m_lockonInitialDist ) }, + { "LockonFreq", INI::parseReal, NULL, offsetof( JetAIUpdateModuleData, m_lockonFreq ) }, + { "LockonAngleSpin", INI::parseAngleReal, NULL, offsetof( JetAIUpdateModuleData, m_lockonAngleSpin ) }, + { "LockonBlinky", INI::parseBool, NULL, offsetof( JetAIUpdateModuleData, m_lockonBlinky ) }, + { "ReturnToBaseIdleTime", INI::parseDurationUnsignedInt, NULL, offsetof( JetAIUpdateModuleData, m_returnToBaseIdleTime ) }, + { 0, 0, 0, 0 } + }; + p.add(dataFieldParse); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- +AIStateMachine* JetAIUpdate::makeStateMachine() +{ + if (getJetAIUpdateModuleData()->m_needsRunway) + return newInstance(JetAIStateMachine)( getObject(), "JetAIStateMachine"); + else + return newInstance(HeliAIStateMachine)( getObject(), "HeliAIStateMachine"); +} + +//------------------------------------------------------------------------------------------------- +JetAIUpdate::JetAIUpdate( Thing *thing, const ModuleData* moduleData ) : AIUpdateInterface( thing, moduleData ) +{ + m_flags = 0; + m_afterburnerSound = *(getObject()->getTemplate()->getPerUnitSound("Afterburner")); + m_afterburnerSound.setObjectID(getObject()->getID()); + m_attackLocoExpireFrame = 0; + m_attackersMissExpireFrame = 0; + m_untargetableExpireFrame = 0; + m_returnToBaseFrame = 0; + m_lockonDrawable = NULL; + m_landingPosForHelipadStuff.zero(); + + //Added By Sadullah Nader + //Initializations missing and needed + m_producerLocation.zero(); + // + m_enginesOn = TRUE; +} + +//------------------------------------------------------------------------------------------------- +JetAIUpdate::~JetAIUpdate() +{ + if (m_lockonDrawable) + { + TheGameClient->destroyDrawable(m_lockonDrawable); + m_lockonDrawable = NULL; + } +} + +//------------------------------------------------------------------------------------------------- +Bool JetAIUpdate::isIdle() const +{ + // we need to do this because we enter an idle state briefly between takeoff/landing in these cases, + // but scripting relies on us never claiming to be "idle"... + if (getFlag(HAS_PENDING_COMMAND)) + return false; + + return AIUpdateInterface::isIdle(); +} + +//------------------------------------------------------------------------------------------------- +Bool JetAIUpdate::isReloading() const +{ + StateID stateID = getStateMachine()->getCurrentStateID(); + if( stateID == RELOAD_AMMO ) + { + return TRUE; + } + return FALSE; +} + +//------------------------------------------------------------------------------------------------- +Bool JetAIUpdate::isTaxiingToParking() const +{ + StateID stateID = getStateMachine()->getCurrentStateID(); + switch( stateID ) + { + case TAXI_FROM_HANGAR: + case TAXI_FROM_LANDING: + case ORIENT_FOR_PARKING_PLACE: + case RELOAD_AMMO: + case TAKING_OFF_AWAIT_CLEARANCE: + case TAXI_TO_TAKEOFF: + case PAUSE_BEFORE_TAKEOFF: + case TAKING_OFF: + return TRUE; + } + return FALSE; +} + +//------------------------------------------------------------------------------------------------- +void JetAIUpdate::onObjectCreated() +{ + AIUpdateInterface::onObjectCreated(); + friend_setAllowAirLoco(false); + chooseLocomotorSet(LOCOMOTORSET_TAXIING); +} + +//------------------------------------------------------------------------------------------------- +void JetAIUpdate::onDelete() +{ + AIUpdateInterface::onDelete(); + ParkingPlaceBehaviorInterface* pp = getPP(getObject()->getProducerID()); + if (pp) + pp->releaseSpace(getObject()->getID()); +} + +//------------------------------------------------------------------------------------------------- +void JetAIUpdate::getProducerLocation() +{ + if (getFlag(HAS_PRODUCER_LOCATION)) + return; + + Object* jet = getObject(); + Object* airfield = TheGameLogic->findObjectByID( jet->getProducerID() ); + if (airfield == NULL) + m_producerLocation = *jet->getPosition(); + else + m_producerLocation = *airfield->getPosition(); + + /* + if we aren't allowed to fly, then we should be parked (or at least taxiing), + which implies we have a parking place reserved. If we don't, it's probably + because we were directly spawned via script (or directly placed on the map). + So, check to see if we have no parking place, and if not, quietly enable flight. + */ + ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); + if (!pp || !pp->hasReservedSpace(jet->getID())) + { + friend_setAllowAirLoco(true); + chooseLocomotorSet(LOCOMOTORSET_NORMAL); + } + else + { + friend_setAllowAirLoco(false); + chooseLocomotorSet(LOCOMOTORSET_TAXIING); + } + + setFlag(HAS_PRODUCER_LOCATION, true); + +} + +//------------------------------------------------------------------------------------------------- +UpdateSleepTime JetAIUpdate::update() +{ + const JetAIUpdateModuleData* d = getJetAIUpdateModuleData(); + + getProducerLocation(); + + Object* jet = getObject(); + + ParkingPlaceBehaviorInterface* pp = getPP(getObject()->getProducerID()); + + // If idle & out of ammo, return + // have to call our parent's isIdle, because we override it to never return true + // when we have a pending command... + UnsignedInt now = TheGameLogic->getFrame(); + + // srj sez: not 100% sure on this. calling RELOAD_AMMO "idle" allows us to get healed while reloading, + // but might have other side effects we didn't want. if this does prove to cause a bug, be sure + // that jets (and ESPECIALLY comanches) are still getting healed at airfields. + if (AIUpdateInterface::isIdle() || getStateMachine()->getCurrentStateID() == RELOAD_AMMO) + { + if (pp != NULL) + { + if (!getFlag(ALLOW_AIR_LOCO) && + !getFlag(HAS_PENDING_COMMAND) && + jet->isKindOf(KINDOF_PRODUCED_AT_HELIPAD) && + jet->getBodyModule()->getHealth() == jet->getBodyModule()->getMaxHealth()) + { + // we're completely healed, so take off again + pp->setHealee(jet, false); + friend_setAllowAirLoco(true); + getStateMachine()->clear(); + setLastCommandSource( CMD_FROM_AI ); + getStateMachine()->setState( TAKING_OFF_AWAIT_CLEARANCE ); + } + else + { + pp->setHealee(jet, !getFlag(ALLOW_AIR_LOCO)); + } + } + + // note that we might still have weapons with ammo, but still be forced to return to reload. + if (isOutOfSpecialReloadAmmo() && getFlag(ALLOW_AIR_LOCO)) + { + m_returnToBaseFrame = 0; + + // this is really a "just-in-case" to ensure the targeter list doesn't spin out of control (srj) + pruneDeadTargeters(); + + setFlag(USE_SPECIAL_RETURN_LOCO, true); + setLastCommandSource( CMD_FROM_AI ); + getStateMachine()->setState(RETURNING_FOR_LANDING); + } + else if (getFlag(HAS_PENDING_COMMAND) + // srj sez: if we are reloading ammo, wait will we are done before processing the pending command. + && getStateMachine()->getCurrentStateID() != RELOAD_AMMO) + { + m_returnToBaseFrame = 0; + + AICommandParms parms(AICMD_MOVE_TO_POSITION, CMD_FROM_AI); // values don't matter, will be wiped by next line + m_mostRecentCommand.reconstitute(parms); + setFlag(HAS_PENDING_COMMAND, false); + + aiDoCommand(&parms); + } + else if (m_returnToBaseFrame != 0 && now >= m_returnToBaseFrame && getFlag(ALLOW_AIR_LOCO)) + { + m_returnToBaseFrame = 0; + DEBUG_ASSERTCRASH(isOutOfSpecialReloadAmmo() == false, ("Hmm, this seems unlikely -- isOutOfSpecialReloadAmmo()==false")); + setFlag(USE_SPECIAL_RETURN_LOCO, false); + setLastCommandSource( CMD_FROM_AI ); + getStateMachine()->setState(RETURNING_FOR_LANDING); + } + else if (m_returnToBaseFrame == 0 && d->m_returnToBaseIdleTime > 0 && getFlag(ALLOW_AIR_LOCO)) + { + m_returnToBaseFrame = now + d->m_returnToBaseIdleTime; + } + } + else + { + if (pp != NULL) + { + pp->setHealee(getObject(), false); + } + m_returnToBaseFrame = 0; + if (getFlag(ALLOW_INTERRUPT_AND_RESUME_OF_CUR_STATE_FOR_RELOAD) && + isOutOfSpecialReloadAmmo() && getFlag(ALLOW_AIR_LOCO)) + { + setFlag(USE_SPECIAL_RETURN_LOCO, true); + setFlag(HAS_PENDING_COMMAND, true); + setFlag(ALLOW_INTERRUPT_AND_RESUME_OF_CUR_STATE_FOR_RELOAD, false); + setLastCommandSource( CMD_FROM_AI ); + getStateMachine()->setState(RETURNING_FOR_LANDING); + } + } + + Real minHeight = friend_getMinHeight(); + if( pp ) + { + minHeight += pp->getLandingDeckHeightOffset(); + } + + Drawable* draw = jet->getDrawable(); + if (draw != NULL) + { + StateID id = getStateMachine()->getCurrentStateID(); + Bool needToCheckMinHeight = (id >= JETAISTATETYPE_FIRST && id <= JETAISTATETYPE_LAST) || + !jet->isAboveTerrain() || + !getFlag(ALLOW_AIR_LOCO); + if( needToCheckMinHeight || jet->getStatusBits().test( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) ) + { + Real ht = jet->isAboveTerrain() ? jet->getHeightAboveTerrain() : 0; + if (ht < minHeight) + { + Matrix3D tmp(1); + tmp.Set_Z_Translation(minHeight - ht); + draw->setInstanceMatrix(&tmp); + } + else + { + draw->setInstanceMatrix(NULL); + } + } + else + { + draw->setInstanceMatrix(NULL); + } + } + + PhysicsBehavior* physics = jet->getPhysics(); + if (physics->getVelocityMagnitude() > 0 && getFlag(ALLOW_AIR_LOCO)) + jet->setModelConditionState(MODELCONDITION_JETEXHAUST); + else + jet->clearModelConditionState(MODELCONDITION_JETEXHAUST); + + if (jet->testStatus(OBJECT_STATUS_IS_ATTACKING)) + { + m_attackLocoExpireFrame = now + d->m_attackLocoPersistTime; + m_attackersMissExpireFrame = now + d->m_attackersMissPersistTime; + } + else + { + if (m_attackLocoExpireFrame != 0 && now >= m_attackLocoExpireFrame) + { + m_attackLocoExpireFrame = 0; + } + if (m_attackersMissExpireFrame != 0 && now >= m_attackersMissExpireFrame) + { + m_attackersMissExpireFrame = 0; + } + } + + if (m_untargetableExpireFrame != 0 && now >= m_untargetableExpireFrame) + { + m_untargetableExpireFrame = 0; + } + + positionLockon(); + + if (m_attackLocoExpireFrame != 0) + { + chooseLocomotorSet(d->m_attackingLoco); + } + else if (getFlag(USE_SPECIAL_RETURN_LOCO)) + { + chooseLocomotorSet(d->m_returningLoco); + } + + + if( !jet->isKindOf( KINDOF_PRODUCED_AT_HELIPAD ) ) + { + Drawable *draw = jet->getDrawable(); + if( draw ) + { + if( getFlag(TAKEOFF_IN_PROGRESS) + || getFlag(LANDING_IN_PROGRESS) + || getObject()->isSignificantlyAboveTerrain() + || isMoving() + || isWaitingForPath() ) + { + if( !m_enginesOn ) + { + //We just started moving, therefore turn on the engines! + draw->enableAmbientSound( TRUE ); + m_enginesOn = TRUE; + } + } + else if( m_enginesOn ) + { + //We're no longer moving, so turn off the engines! + draw->enableAmbientSound( FALSE ); + m_enginesOn = FALSE; + } + } + } + + + /*UpdateSleepTime ret =*/ AIUpdateInterface::update(); + //return (mine < ret) ? mine : ret; + /// @todo srj -- someday, make sleepy. for now, must not sleep. + return UPDATE_SLEEP_NONE; +} + +//------------------------------------------------------------------------------------------------- +Bool JetAIUpdate::chooseLocomotorSet(LocomotorSetType wst) +{ + const JetAIUpdateModuleData* d = getJetAIUpdateModuleData(); + if (!getFlag(ALLOW_AIR_LOCO)) + { + wst = LOCOMOTORSET_TAXIING; + } + else if (m_attackLocoExpireFrame != 0) + { + wst = d->m_attackingLoco; + } + else if (getFlag(USE_SPECIAL_RETURN_LOCO)) + { + wst = d->m_returningLoco; + } + return AIUpdateInterface::chooseLocomotorSet(wst); +} + +//------------------------------------------------------------------------------------------------- +void JetAIUpdate::setLocomotorGoalNone() +{ + if ((getFlag(TAKEOFF_IN_PROGRESS) || getFlag(LANDING_IN_PROGRESS)) + && getFlag(ALLOW_AIR_LOCO) && !getFlag(ALLOW_CIRCLING)) + { + Object* jet = getObject(); + Coord3D desiredPos = *jet->getPosition(); + const Coord3D* dir = jet->getUnitDirectionVector2D(); + desiredPos.x += dir->x * 1000.0f; + desiredPos.y += dir->y * 1000.0f; + setLocomotorGoalPositionExplicit(desiredPos); + } + else + { + AIUpdateInterface::setLocomotorGoalNone(); + } +} + +//---------------------------------------------------------------------------------------- +Bool JetAIUpdate::getSneakyTargetingOffset(Coord3D* offset) const +{ + if (m_attackersMissExpireFrame != 0 && TheGameLogic->getFrame() < m_attackersMissExpireFrame) + { + if (offset) + { + const JetAIUpdateModuleData* d = getJetAIUpdateModuleData(); + const Object* jet = getObject(); + const Coord3D* dir = jet->getUnitDirectionVector2D(); + offset->x = dir->x * d->m_sneakyOffsetWhenAttacking; + offset->y = dir->y * d->m_sneakyOffsetWhenAttacking; + offset->z = 0.0f; + } + return true; + } + else + { + return false; + } +} + +//---------------------------------------------------------------------------------------- +void JetAIUpdate::pruneDeadTargeters() +{ + if (!m_targetedBy.empty()) + { + for (std::list::iterator it = m_targetedBy.begin(); it != m_targetedBy.end(); /* empty */ ) + { + if (TheGameLogic->findObjectByID(*it) == NULL) + { + it = m_targetedBy.erase(it); + } + else + { + ++it; + } + } + } +} + +//---------------------------------------------------------------------------------------- +void JetAIUpdate::positionLockon() +{ + if (!m_lockonDrawable) + return; + + if (m_untargetableExpireFrame == 0) + { + TheGameClient->destroyDrawable(m_lockonDrawable); + m_lockonDrawable = NULL; + return; + } + + const JetAIUpdateModuleData* d = getJetAIUpdateModuleData(); + UnsignedInt now = TheGameLogic->getFrame(); + UnsignedInt remaining = m_untargetableExpireFrame - now; + UnsignedInt elapsed = d->m_lockonTime - remaining; + + Coord3D pos = *getObject()->getPosition(); + Real frac = (Real)remaining / (Real)d->m_lockonTime; + Real finalDist = getObject()->getGeometryInfo().getBoundingCircleRadius(); + Real dist = finalDist + (d->m_lockonInitialDist - finalDist) * frac; + Real angle = d->m_lockonAngleSpin * frac; + + pos.x += Cos(angle) * dist; + pos.y += Sin(angle) * dist; + // pos.z is untouched + + m_lockonDrawable->setPosition(&pos); + Real dx = getObject()->getPosition()->x - pos.x; + Real dy = getObject()->getPosition()->y - pos.y; + if (dx || dy) + m_lockonDrawable->setOrientation(atan2(dy, dx)); + + // the Gaussian sum, to avoid keeping a running total: + // + // 1+2+3+...n = n*(n+1)/2 + // + Real elapsedTimeSumPrev = 0.5f * (elapsed-1) * (elapsed); + Real elapsedTimeSumCurr = elapsedTimeSumPrev + elapsed; + Real factor = d->m_lockonFreq / d->m_lockonTime; + Bool lastPhase = ((Int)(factor * elapsedTimeSumPrev) & 1) != 0; + Bool thisPhase = ((Int)(factor * elapsedTimeSumCurr) & 1) != 0; + + if (lastPhase && (!thisPhase)) + { + AudioEventRTS lockonSound = TheAudio->getMiscAudio()->m_lockonTickSound; + lockonSound.setObjectID(getObject()->getID()); + TheAudio->addAudioEvent(&lockonSound); + if (d->m_lockonBlinky) + m_lockonDrawable->setDrawableHidden(false); + } + else + { + if (d->m_lockonBlinky) + m_lockonDrawable->setDrawableHidden(true); + } +} + +//---------------------------------------------------------------------------------------- +void JetAIUpdate::buildLockonDrawableIfNecessary() +{ + if (m_untargetableExpireFrame == 0) + return; + + const JetAIUpdateModuleData* d = getJetAIUpdateModuleData(); + if (d->m_lockonCursor.isNotEmpty() && m_lockonDrawable == NULL) + { + const ThingTemplate* tt = TheThingFactory->findTemplate(d->m_lockonCursor); + if (tt) + { + m_lockonDrawable = TheThingFactory->newDrawable(tt); + } + } + positionLockon(); +} + +//---------------------------------------------------------------------------------------- +void JetAIUpdate::addTargeter(ObjectID id, Bool add) +{ + const JetAIUpdateModuleData* d = getJetAIUpdateModuleData(); + UnsignedInt lockonTime = d->m_lockonTime; + if (lockonTime != 0) + { + std::list::iterator it = std::find(m_targetedBy.begin(), m_targetedBy.end(), id); + if (add) + { + if (it == m_targetedBy.end()) + { + m_targetedBy.push_back(id); + if (m_untargetableExpireFrame == 0 && m_targetedBy.size() == 1) + { + m_untargetableExpireFrame = TheGameLogic->getFrame() + lockonTime; + buildLockonDrawableIfNecessary(); + } + } + } + else + { + if (it != m_targetedBy.end()) + { + m_targetedBy.erase(it); + if (m_targetedBy.empty()) + { + m_untargetableExpireFrame = 0; + } + } + } + } +} + +//---------------------------------------------------------------------------------------- +Bool JetAIUpdate::isTemporarilyPreventingAimSuccess() const +{ + return m_untargetableExpireFrame != 0 && (TheGameLogic->getFrame() < m_untargetableExpireFrame); +} + +//---------------------------------------------------------------------------------------- +Bool JetAIUpdate::isAllowedToMoveAwayFromUnit() const +{ + // parked (or landing) units don't get to do this. + if (!getFlag(ALLOW_AIR_LOCO) || getFlag(TAKEOFF_IN_PROGRESS) || getFlag(LANDING_IN_PROGRESS)) + return false; + + return AIUpdateInterface::isAllowedToMoveAwayFromUnit(); +} + +//------------------------------------------------------------------------------------------------- +Bool JetAIUpdate::isDoingGroundMovement(void) const +{ + // srj per jba: Air units should never be doing ground movement, even when taxiing... + // (exception: see getTreatAsAircraftForLocoDistToGoal) + return false; +} + +//------------------------------------------------------------------------------------------------- +Bool JetAIUpdate::getTreatAsAircraftForLocoDistToGoal() const +{ + // exception to isDoingGroundMovement: should never treat as aircraft for dist-to-goal when taxiing. + if (getFlag(TAXI_IN_PROGRESS)) + { + return false; + } + else + { + return AIUpdateInterface::getTreatAsAircraftForLocoDistToGoal(); + } +} + +//------------------------------------------------------------------------------------------------- +Bool JetAIUpdate::isParkedInHangar() const +{ + // We do not check if the Aircraft actually needs a runway/hangar here, + // so we can ignore those cases earlier already + return isReloading() || !(getFlag(TAKEOFF_IN_PROGRESS) + || getFlag(LANDING_IN_PROGRESS) + || getObject()->isSignificantlyAboveTerrain() + || isMoving() + || isWaitingForPath()); +} + +//---------------------------------------------------------------------------------------- +/** + * Follow the path defined by the given array of points + */ +void JetAIUpdate::privateFollowPath( const std::vector* path, Object *ignoreObject, CommandSourceType cmdSource, Bool exitProduction ) +{ + if (exitProduction) + { + getStateMachine()->clear(); + if( ignoreObject ) + ignoreObstacle( ignoreObject ); + setLastCommandSource( cmdSource ); + if (getObject()->isKindOf(KINDOF_PRODUCED_AT_HELIPAD)) + getStateMachine()->setState( TAKING_OFF_AWAIT_CLEARANCE ); + else + getStateMachine()->setState( TAXI_FROM_HANGAR ); + } + else + { + AIUpdateInterface::privateFollowPath(path, ignoreObject, cmdSource, exitProduction); + } +} + +//---------------------------------------------------------------------------------------- +void JetAIUpdate::privateFollowPathAppend( const Coord3D *pos, CommandSourceType cmdSource ) +{ + // nothing yet... might need to override. not sure. (srj) + AIUpdateInterface::privateFollowPathAppend(pos, cmdSource); +} + +//---------------------------------------------------------------------------------------- +void JetAIUpdate::doLandingCommand(Object *airfield, CommandSourceType cmdSource) +{ + if (getObject()->isKindOf(KINDOF_PRODUCED_AT_HELIPAD)) + { + m_landingPosForHelipadStuff = *airfield->getPosition(); + + Coord3D tmp; + FindPositionOptions options; + options.maxRadius = airfield->getGeometryInfo().getBoundingCircleRadius() * 10.0f; + if (ThePartitionManager->findPositionAround(&m_landingPosForHelipadStuff, &options, &tmp)) + m_landingPosForHelipadStuff = tmp; + } + + for (BehaviorModule** i = airfield->getBehaviorModules(); *i; ++i) + { + ParkingPlaceBehaviorInterface* pp = (*i)->getParkingPlaceBehaviorInterface(); + if (pp == NULL) + continue; + + if (getObject()->isKindOf(KINDOF_PRODUCED_AT_HELIPAD) || + pp->reserveSpace(getObject()->getID(), friend_getParkingOffset(), NULL)) + { + // if we had a space at another airfield, release it + ParkingPlaceBehaviorInterface* oldPP = getPP(getObject()->getProducerID()); + if (oldPP != NULL && oldPP != pp) + { + oldPP->releaseSpace(getObject()->getID()); + } + + getObject()->setProducer(airfield); + DEBUG_ASSERTCRASH(isOutOfSpecialReloadAmmo() == false, ("Hmm, this seems unlikely -- isOutOfSpecialReloadAmmo()==false")); + setFlag(USE_SPECIAL_RETURN_LOCO, false); + setFlag(ALLOW_INTERRUPT_AND_RESUME_OF_CUR_STATE_FOR_RELOAD, false); + setLastCommandSource( cmdSource ); + getStateMachine()->setState(RETURNING_FOR_LANDING); + return; + } + } +} + +//---------------------------------------------------------------------------------------- +void JetAIUpdate::notifyVictimIsDead() +{ + if (getJetAIUpdateModuleData()->m_needsRunway) + m_returnToBaseFrame = TheGameLogic->getFrame(); +} + +//---------------------------------------------------------------------------------------- +/** + * Enter the given object + */ +void JetAIUpdate::privateEnter( Object *objectToEnter, CommandSourceType cmdSource ) +{ + // we are already landing. just ignore it. + if (getFlag(LANDING_IN_PROGRESS)) + return; + + if( !TheActionManager->canEnterObject( getObject(), objectToEnter, cmdSource, DONT_CHECK_CAPACITY ) ) + return; + + doLandingCommand(objectToEnter, cmdSource); +} + +//---------------------------------------------------------------------------------------- +/** + * Get repaired at the repair depot + */ +void JetAIUpdate::privateGetRepaired( Object *repairDepot, CommandSourceType cmdSource ) +{ + // we are already landing. just ignore it. + if (getFlag(LANDING_IN_PROGRESS)) + return; + + // sanity, if we can't get repaired from here get out of here + if( TheActionManager->canGetRepairedAt( getObject(), repairDepot, cmdSource ) == FALSE ) + return; + + // dock with the repair depot + doLandingCommand( repairDepot, cmdSource ); + +} + +//------------------------------------------------------------------------------------------------- +Bool JetAIUpdate::isParkedAt(const Object* obj) const +{ + if (!getFlag(ALLOW_AIR_LOCO) && + !getObject()->isKindOf(KINDOF_PRODUCED_AT_HELIPAD) && + obj != NULL) + { + Object* airfield; + ParkingPlaceBehaviorInterface* pp = getPP(getObject()->getProducerID(), &airfield); + if (pp != NULL && airfield != NULL && airfield == obj) + { + return true; + } + } + + return false; +} + +//------------------------------------------------------------------------------------------------- +void JetAIUpdate::aiDoCommand(const AICommandParms* parms) +{ + // call this from aiDoCommand as well as update, because this can + // be called before update ever is... if the unit is placed on a map, + // and a script tells it to do something with a condition of TRUE! + getProducerLocation(); + + if (!isAllowedToRespondToAiCommands(parms)) + return; + + // note that we always store this, even if nothing will be "pending". + m_mostRecentCommand.store(*parms); + + if (getFlag(TAKEOFF_IN_PROGRESS) || getFlag(LANDING_IN_PROGRESS)) + { + // have to wait for takeoff or landing to complete, just store the sucker + setFlag(HAS_PENDING_COMMAND, true); + return; + } + else if (parms->m_cmd == AICMD_IDLE && getStateMachine()->getCurrentStateID() == RELOAD_AMMO) + { + // uber-special-case... if we are told to idle, but are reloading ammo, ignore it for now, + // since we're already doing "nothing" and responding to this will cease our reload... + // don't just return, tho, in case we were (say) reloading during a guard stint. + setFlag(HAS_PENDING_COMMAND, true); + return; + } + else if( parms->m_cmd == AICMD_IDLE && getObject()->isAirborneTarget() && !getObject()->isKindOf( KINDOF_PRODUCED_AT_HELIPAD ) ) + { + getStateMachine()->clear(); + setLastCommandSource( CMD_FROM_AI ); + getStateMachine()->setState( RETURNING_FOR_LANDING ); + return; + } + else if (!getFlag(ALLOW_AIR_LOCO)) + { + switch (parms->m_cmd) + { + case AICMD_IDLE: + case AICMD_BUSY: + case AICMD_FOLLOW_EXITPRODUCTION_PATH: + // don't need (or want) to take off for these + break; + + case AICMD_ENTER: + case AICMD_GET_REPAIRED: + + // if we're already parked at the airfield in question, just ignore. + if (isParkedAt(parms->m_obj)) + return; + + // else fall thru to the default case! + + default: + { + // nuke any existing pending cmd + m_mostRecentCommand.store(*parms); + setFlag(HAS_PENDING_COMMAND, true); + + getStateMachine()->clear(); + setLastCommandSource( CMD_FROM_AI ); + getStateMachine()->setState( TAKING_OFF_AWAIT_CLEARANCE ); + + return; + } + } + } + + switch (parms->m_cmd) + { + case AICMD_GUARD_POSITION: + case AICMD_GUARD_OBJECT: + case AICMD_GUARD_AREA: + case AICMD_HUNT: + case AICMD_GUARD_RETALIATE: + setFlag(ALLOW_INTERRUPT_AND_RESUME_OF_CUR_STATE_FOR_RELOAD, true); + break; + default: + setFlag(ALLOW_INTERRUPT_AND_RESUME_OF_CUR_STATE_FOR_RELOAD, false); + break; + } + + setFlag(HAS_PENDING_COMMAND, false); + AIUpdateInterface::aiDoCommand(parms); +} + +//------------------------------------------------------------------------------------------------- +void JetAIUpdate::friend_setAllowAirLoco(Bool allowAirLoco) +{ + setFlag(ALLOW_AIR_LOCO, allowAirLoco); +} + +//------------------------------------------------------------------------------------------------- +void JetAIUpdate::friend_enableAfterburners(Bool v) +{ + Object* jet = getObject(); + if (v) + { + jet->setModelConditionState(MODELCONDITION_JETAFTERBURNER); + if (!m_afterburnerSound.isCurrentlyPlaying()) + { + m_afterburnerSound.setObjectID(jet->getID()); + m_afterburnerSound.setPlayingHandle(TheAudio->addAudioEvent(&m_afterburnerSound)); + } + } + else + { + jet->clearModelConditionState(MODELCONDITION_JETAFTERBURNER); + if (m_afterburnerSound.isCurrentlyPlaying()) + { + TheAudio->removeAudioEvent(m_afterburnerSound.getPlayingHandle()); + } + } +} + +//------------------------------------------------------------------------------------------------- +void JetAIUpdate::friend_addWaypointToGoalPath( const Coord3D &bestPos ) +{ + privateFollowPathAppend( &bestPos, CMD_FROM_AI ); +} + +//------------------------------------------------------------------------------------------------- +AICommandType JetAIUpdate::friend_getPendingCommandType() const +{ + if( getFlag( HAS_PENDING_COMMAND ) ) + { + return m_mostRecentCommand.getCommandType(); + } + return AICMD_NO_COMMAND; +} + +//------------------------------------------------------------------------------------------------- +void JetAIUpdate::friend_purgePendingCommand() +{ + setFlag(HAS_PENDING_COMMAND, false); +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void JetAIUpdate::crc( Xfer *xfer ) +{ + // extend base class + AIUpdateInterface::crc(xfer); +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void JetAIUpdate::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 2; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // extend base class + AIUpdateInterface::xfer(xfer); + + + xfer->xferCoord3D(&m_producerLocation); + m_mostRecentCommand.doXfer(xfer); + xfer->xferUnsignedInt(&m_attackLocoExpireFrame); + xfer->xferUnsignedInt(&m_attackersMissExpireFrame); + xfer->xferUnsignedInt(&m_returnToBaseFrame); + xfer->xferSTLObjectIDList(&m_targetedBy); + + xfer->xferUnsignedInt(&m_untargetableExpireFrame); + + // Set on create. + //AudioEventRTS m_afterburnerSound; ///< Sound when afterburners on + + AsciiString drawName; + if (m_lockonDrawable) { + drawName = m_lockonDrawable->getTemplate()->getName(); + } + xfer->xferAsciiString(&drawName); + if (drawName.isNotEmpty() && m_lockonDrawable==NULL) + { + const ThingTemplate* tt = TheThingFactory->findTemplate(drawName); + if (tt) + { + m_lockonDrawable = TheThingFactory->newDrawable(tt); + } + } + xfer->xferInt(&m_flags); + + if( version >= 2 ) + { + xfer->xferBool( &m_enginesOn ); + } + else + { + //We don't have to be accurate -- this is a patch. + if( getFlag(TAKEOFF_IN_PROGRESS) || getFlag(LANDING_IN_PROGRESS) || getObject()->isSignificantlyAboveTerrain() || getObject()->isKindOf( KINDOF_PRODUCED_AT_HELIPAD ) ) + { + m_enginesOn = TRUE; + } + else + { + m_enginesOn = FALSE; + } + } + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void JetAIUpdate::loadPostProcess( void ) +{ + //When drawables are created, so are their ambient sounds. After loading, only turn off the + //ambient sound if the engine is off. + if( !m_enginesOn ) + { + Drawable *draw = getObject()->getDrawable(); + if( draw ) + { + draw->stopAmbientSound(); + } + } + + // extend base class + AIUpdateInterface::loadPostProcess(); +} // end loadPostProcess From 7710d11ce39a12bac0da957c74a1f4c2dd52cf82 Mon Sep 17 00:00:00 2001 From: Andi Date: Sun, 8 Jun 2025 11:18:14 +0200 Subject: [PATCH 19/42] Added weaponslots 4-8 --- .../GameEngine/Include/Common/GameCommon.h | 5 + .../Code/GameEngine/Include/Common/GameType.h | 397 +++++++++--------- .../GameEngine/Include/Common/ModelState.h | 31 ++ .../Code/GameEngine/Include/GameLogic/AI.h | 5 + .../Include/GameLogic/Module/StealthUpdate.h | 380 +++++++++-------- .../GameEngine/Include/GameLogic/WeaponSet.h | 10 + .../GameEngine/Source/Common/BitFlags.cpp | 32 ++ .../GameClient/MessageStream/CommandXlat.cpp | 15 + .../Source/GameLogic/AI/AIStates.cpp | 25 ++ .../Source/GameLogic/Object/Object.cpp | 42 +- .../Source/GameLogic/Object/WeaponSet.cpp | 12 +- .../Tools/WorldBuilder/src/DrawObject.cpp | 12 +- 12 files changed, 577 insertions(+), 389 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GameCommon.h b/GeneralsMD/Code/GameEngine/Include/Common/GameCommon.h index dbbad0424ca..0937e76d419 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GameCommon.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GameCommon.h @@ -233,6 +233,11 @@ enum CommandSourceType CPP_11(: Int) CMD_SYNC_TO_PRIMARY, // This weapon can only be used when PRIMARY is fired CMD_SYNC_TO_SECONDARY, // This weapon can only be used when SECONDARY is fired CMD_SYNC_TO_TERTIARY, // This weapon can only be used when TERTIARY is fired + CMD_SYNC_TO_FOUR, // This weapon can only be used when WEAPON_FOUR is fired + CMD_SYNC_TO_FIVE, // ... + CMD_SYNC_TO_SIX, // ... + CMD_SYNC_TO_SEVEN, // ... + CMD_SYNC_TO_EIGHT, // ... }; ///< the source of a command diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GameType.h b/GeneralsMD/Code/GameEngine/Include/Common/GameType.h index c969afaaa3f..baba14a1466 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GameType.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GameType.h @@ -1,196 +1,201 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// GameType.h -// Basic data types needed for the game engine. This is an extension of BaseType.h. -// Author: Michael S. Booth, April 2001 - -#pragma once - -#ifndef _GAME_TYPE_H_ -#define _GAME_TYPE_H_ - -#include "Lib/BaseType.h" - -// the default size of the world map -#define DEFAULT_WORLD_WIDTH 64 -#define DEFAULT_WORLD_HEIGHT 64 - -/// A unique, generic "identifier" used to access Objects. -enum ObjectID CPP_11(: Int) -{ - INVALID_ID = 0, - FORCE_OBJECTID_TO_LONG_SIZE = 0x7ffffff -}; - -/// A unique, generic "identifier" used to access Drawables. -enum DrawableID CPP_11(: Int) -{ - INVALID_DRAWABLE_ID = 0, - FORCE_DRAWABLEID_TO_LONG_SIZE = 0x7ffffff -}; - -/// A unique, generic "identifier" used to identify player specified formations. -enum FormationID CPP_11(: Int) -{ - NO_FORMATION_ID = 0, // Unit is not a member of any formation - FORCE_FORMATIONID_TO_LONG_SIZE = 0x7ffffff -}; - -#define INVALID_ANGLE -100.0f - -class INI; - -//------------------------------------------------------------------------------------------------- -/** The time of day enumeration, keep in sync with TimeOfDayNames[] */ -//------------------------------------------------------------------------------------------------- -enum TimeOfDay CPP_11(: Int) -{ - TIME_OF_DAY_INVALID = 0, - TIME_OF_DAY_FIRST = 1, - TIME_OF_DAY_MORNING = TIME_OF_DAY_FIRST, - TIME_OF_DAY_AFTERNOON, - TIME_OF_DAY_EVENING, - TIME_OF_DAY_NIGHT, - - TIME_OF_DAY_COUNT // keep this last -}; - -extern const char *TimeOfDayNames[]; -// defined in Common/GameType.cpp - -//------------------------------------------------------------------------------------------------- -enum Weather CPP_11(: Int) -{ - WEATHER_NORMAL = 0, - WEATHER_SNOWY = 1, - - WEATHER_COUNT // keep this last -}; - -extern const char *WeatherNames[]; - -enum Scorches CPP_11(: Int) -{ - SCORCH_1 = 0, - SCORCH_2 = 1, - SCORCH_3 = 2, - SCORCH_4 = 3, - SHADOW_SCORCH = 4, -/* SCORCH_6 = 5, - SCORCH_7 = 6, - SCORCH_8 = 7, - - CRATER_1 = 8, - CRATER_2 = 9, - CRATER_3 = 10, - CRATER_4 = 11, - CRATER_5 = 12, - CRATER_6 = 13, - CRATER_7 = 14, - CRATER_8 = 15, - - - MISC_DECAL_1 = 16, - MISC_DECAL_2 = 17, - MISC_DECAL_3 = 18, - MISC_DECAL_4 = 19, - MISC_DECAL_5 = 20, - MISC_DECAL_6 = 21, - MISC_DECAL_7 = 22, - MISC_DECAL_8 = 23, - - MISC_DECAL_9 = 24, - MISC_DECAL_10 = 25, - MISC_DECAL_11 = 26, - MISC_DECAL_12 = 27, - MISC_DECAL_13 = 28, - MISC_DECAL_14 = 29, - MISC_DECAL_15 = 30, - MISC_DECAL_16 = 31, - - MISC_DECAL_17 = 32, - MISC_DECAL_18 = 33, - MISC_DECAL_19 = 34, - MISC_DECAL_20 = 35, - MISC_DECAL_21 = 36, - MISC_DECAL_22 = 37, - MISC_DECAL_23 = 38, - MISC_DECAL_24 = 39, - - MISC_DECAL_25 = 40, - MISC_DECAL_26 = 41, - MISC_DECAL_27 = 42, - MISC_DECAL_28 = 43, - MISC_DECAL_29 = 44, - MISC_DECAL_30 = 45, - MISC_DECAL_31 = 46, - MISC_DECAL_32 = 47, - - MISC_DECAL_33 = 48, - MISC_DECAL_34 = 49, - MISC_DECAL_35 = 50, - MISC_DECAL_36 = 51, - MISC_DECAL_37 = 52, - MISC_DECAL_38 = 53, - MISC_DECAL_39 = 54, - MISC_DECAL_40 = 55, - - MISC_DECAL_41 = 56, - MISC_DECAL_42 = 57, - MISC_DECAL_43 = 58, - MISC_DECAL_44 = 59, - MISC_DECAL_45 = 60, - MISC_DECAL_46 = 61, - MISC_DECAL_47 = 62, - MISC_DECAL_48 = 63, -*/ - SCORCH_COUNT -}; - -//------------------------------------------------------------------------------------------------- -enum WeaponSlotType CPP_11(: Int) -{ - PRIMARY_WEAPON = 0, - SECONDARY_WEAPON, - TERTIARY_WEAPON, - - WEAPONSLOT_COUNT // keep last -}; - -//------------------------------------------------------------------------------------------------- -// Pathfind layers - ground is the first layer, each bridge is another. jba. -// Layer 1 is the ground. -// Layer 2 is the top layer - bridge if one is present, ground otherwise. -// Layer 2 - LAYER_LAST -1 are bridges. -// Layer_WALL is a special "wall" layer for letting units run aroound on top of a wall -// made of structures. -// Note that the bridges just index in the pathfinder, so you don't actually -// have a LAYER_BRIDGE_1 enum value. -enum PathfindLayerEnum CPP_11(: Int) {LAYER_INVALID = 0, LAYER_GROUND = 1, LAYER_WALL = 15, LAYER_LAST=15}; - -//------------------------------------------------------------------------------------------------- - -#endif // _GAME_TYPE_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// GameType.h +// Basic data types needed for the game engine. This is an extension of BaseType.h. +// Author: Michael S. Booth, April 2001 + +#pragma once + +#ifndef _GAME_TYPE_H_ +#define _GAME_TYPE_H_ + +#include "Lib/BaseType.h" + +// the default size of the world map +#define DEFAULT_WORLD_WIDTH 64 +#define DEFAULT_WORLD_HEIGHT 64 + +/// A unique, generic "identifier" used to access Objects. +enum ObjectID CPP_11(: Int) +{ + INVALID_ID = 0, + FORCE_OBJECTID_TO_LONG_SIZE = 0x7ffffff +}; + +/// A unique, generic "identifier" used to access Drawables. +enum DrawableID CPP_11(: Int) +{ + INVALID_DRAWABLE_ID = 0, + FORCE_DRAWABLEID_TO_LONG_SIZE = 0x7ffffff +}; + +/// A unique, generic "identifier" used to identify player specified formations. +enum FormationID CPP_11(: Int) +{ + NO_FORMATION_ID = 0, // Unit is not a member of any formation + FORCE_FORMATIONID_TO_LONG_SIZE = 0x7ffffff +}; + +#define INVALID_ANGLE -100.0f + +class INI; + +//------------------------------------------------------------------------------------------------- +/** The time of day enumeration, keep in sync with TimeOfDayNames[] */ +//------------------------------------------------------------------------------------------------- +enum TimeOfDay CPP_11(: Int) +{ + TIME_OF_DAY_INVALID = 0, + TIME_OF_DAY_FIRST = 1, + TIME_OF_DAY_MORNING = TIME_OF_DAY_FIRST, + TIME_OF_DAY_AFTERNOON, + TIME_OF_DAY_EVENING, + TIME_OF_DAY_NIGHT, + + TIME_OF_DAY_COUNT // keep this last +}; + +extern const char *TimeOfDayNames[]; +// defined in Common/GameType.cpp + +//------------------------------------------------------------------------------------------------- +enum Weather CPP_11(: Int) +{ + WEATHER_NORMAL = 0, + WEATHER_SNOWY = 1, + + WEATHER_COUNT // keep this last +}; + +extern const char *WeatherNames[]; + +enum Scorches CPP_11(: Int) +{ + SCORCH_1 = 0, + SCORCH_2 = 1, + SCORCH_3 = 2, + SCORCH_4 = 3, + SHADOW_SCORCH = 4, +/* SCORCH_6 = 5, + SCORCH_7 = 6, + SCORCH_8 = 7, + + CRATER_1 = 8, + CRATER_2 = 9, + CRATER_3 = 10, + CRATER_4 = 11, + CRATER_5 = 12, + CRATER_6 = 13, + CRATER_7 = 14, + CRATER_8 = 15, + + + MISC_DECAL_1 = 16, + MISC_DECAL_2 = 17, + MISC_DECAL_3 = 18, + MISC_DECAL_4 = 19, + MISC_DECAL_5 = 20, + MISC_DECAL_6 = 21, + MISC_DECAL_7 = 22, + MISC_DECAL_8 = 23, + + MISC_DECAL_9 = 24, + MISC_DECAL_10 = 25, + MISC_DECAL_11 = 26, + MISC_DECAL_12 = 27, + MISC_DECAL_13 = 28, + MISC_DECAL_14 = 29, + MISC_DECAL_15 = 30, + MISC_DECAL_16 = 31, + + MISC_DECAL_17 = 32, + MISC_DECAL_18 = 33, + MISC_DECAL_19 = 34, + MISC_DECAL_20 = 35, + MISC_DECAL_21 = 36, + MISC_DECAL_22 = 37, + MISC_DECAL_23 = 38, + MISC_DECAL_24 = 39, + + MISC_DECAL_25 = 40, + MISC_DECAL_26 = 41, + MISC_DECAL_27 = 42, + MISC_DECAL_28 = 43, + MISC_DECAL_29 = 44, + MISC_DECAL_30 = 45, + MISC_DECAL_31 = 46, + MISC_DECAL_32 = 47, + + MISC_DECAL_33 = 48, + MISC_DECAL_34 = 49, + MISC_DECAL_35 = 50, + MISC_DECAL_36 = 51, + MISC_DECAL_37 = 52, + MISC_DECAL_38 = 53, + MISC_DECAL_39 = 54, + MISC_DECAL_40 = 55, + + MISC_DECAL_41 = 56, + MISC_DECAL_42 = 57, + MISC_DECAL_43 = 58, + MISC_DECAL_44 = 59, + MISC_DECAL_45 = 60, + MISC_DECAL_46 = 61, + MISC_DECAL_47 = 62, + MISC_DECAL_48 = 63, +*/ + SCORCH_COUNT +}; + +//------------------------------------------------------------------------------------------------- +enum WeaponSlotType CPP_11(: Int) +{ + PRIMARY_WEAPON = 0, + SECONDARY_WEAPON, + TERTIARY_WEAPON, + WEAPON_FOUR, + WEAPON_FIVE, + WEAPON_SIX, + WEAPON_SEVEN, + WEAPON_EIGHT, + + WEAPONSLOT_COUNT // keep last +}; + +//------------------------------------------------------------------------------------------------- +// Pathfind layers - ground is the first layer, each bridge is another. jba. +// Layer 1 is the ground. +// Layer 2 is the top layer - bridge if one is present, ground otherwise. +// Layer 2 - LAYER_LAST -1 are bridges. +// Layer_WALL is a special "wall" layer for letting units run aroound on top of a wall +// made of structures. +// Note that the bridges just index in the pathfinder, so you don't actually +// have a LAYER_BRIDGE_1 enum value. +enum PathfindLayerEnum CPP_11(: Int) {LAYER_INVALID = 0, LAYER_GROUND = 1, LAYER_WALL = 15, LAYER_LAST=15}; + +//------------------------------------------------------------------------------------------------- + +#endif // _GAME_TYPE_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h b/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h index 6749326ed7f..a683e17985d 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h @@ -248,6 +248,37 @@ enum ModelConditionFlagType CPP_11(: Int) // MODELCONDITION_WEAPONSET_GARRISONED, // somewhat obsolote since we are usually not visible when contained. + // New Weaponslots (4 to 8 -- D to H) + MODELCONDITION_PREATTACK_D, + MODELCONDITION_FIRING_D, + MODELCONDITION_BETWEEN_FIRING_SHOTS_D, + MODELCONDITION_RELOADING_D, + MODELCONDITION_USING_WEAPON_D, + + MODELCONDITION_PREATTACK_E, + MODELCONDITION_FIRING_E, + MODELCONDITION_BETWEEN_FIRING_SHOTS_E, + MODELCONDITION_RELOADING_E, + MODELCONDITION_USING_WEAPON_E, + + MODELCONDITION_PREATTACK_F, + MODELCONDITION_FIRING_F, + MODELCONDITION_BETWEEN_FIRING_SHOTS_F, + MODELCONDITION_RELOADING_F, + MODELCONDITION_USING_WEAPON_F, + + MODELCONDITION_PREATTACK_G, + MODELCONDITION_FIRING_G, + MODELCONDITION_BETWEEN_FIRING_SHOTS_G, + MODELCONDITION_RELOADING_G, + MODELCONDITION_USING_WEAPON_G, + + MODELCONDITION_PREATTACK_H, + MODELCONDITION_FIRING_H, + MODELCONDITION_BETWEEN_FIRING_SHOTS_H, + MODELCONDITION_RELOADING_H, + MODELCONDITION_USING_WEAPON_H, + // // Note: these values are saved in save files, so you MUST NOT REMOVE OR CHANGE // existing values! diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h index d16d6b0019b..82fae990e8a 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h @@ -339,6 +339,11 @@ static const char *TheCommandSourceMaskNames[] = "SYNC_TO_PRIMARY", //This weapon will be fired whenever PRIMARY is fired "SYNC_TO_SECONDARY", //This weapon will be fired whenever SECONDARY is fired "SYNC_TO_TERTIARY", //This weapon will be fired whenever TERTIARY is fired + "SYNC_TO_WEAPON_FOUR", //This weapon will be fired whenever WEAPON_FOUR is fired + "SYNC_TO_WEAPON_FIVE", //... + "SYNC_TO_WEAPON_SIX", //... + "SYNC_TO_WEAPON_SEVEN", //... + "SYNC_TO_WEAPON_EIGHT", //... NULL }; #endif diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StealthUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StealthUpdate.h index 351e2b01413..9469253882e 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StealthUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StealthUpdate.h @@ -1,185 +1,195 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: StealthUpdate.h ////////////////////////////////////////////////////////////////////////// -// Author: Kris Morness, May 2002 -// Desc: An update that checks for a status bit to stealth the owning object -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __STEALTH_UPDATE_H_ -#define __STEALTH_UPDATE_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "GameLogic/Module/UpdateModule.h" - -// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// -class Thing; -enum StealthLookType CPP_11(: Int); -enum EvaMessage CPP_11(: Int); -class FXList; - -enum -{ - STEALTH_NOT_WHILE_ATTACKING = 0x00000001, - STEALTH_NOT_WHILE_MOVING = 0x00000002, - STEALTH_NOT_WHILE_USING_ABILITY = 0x00000004, - STEALTH_NOT_WHILE_FIRING_PRIMARY = 0x00000008, - STEALTH_NOT_WHILE_FIRING_SECONDARY = 0x00000010, - STEALTH_NOT_WHILE_FIRING_TERTIARY = 0x00000020, - STEALTH_ONLY_WITH_BLACK_MARKET = 0x00000040, - STEALTH_NOT_WHILE_TAKING_DAMAGE = 0x00000080, - STEALTH_NOT_WHILE_FIRING_WEAPON = (STEALTH_NOT_WHILE_FIRING_PRIMARY | STEALTH_NOT_WHILE_FIRING_SECONDARY | STEALTH_NOT_WHILE_FIRING_TERTIARY), - STEALTH_NOT_WHILE_RIDERS_ATTACKING = 0x00000100, -}; - -#ifdef DEFINE_STEALTHLEVEL_NAMES -static const char *TheStealthLevelNames[] = -{ - "ATTACKING", - "MOVING", - "USING_ABILITY", - "FIRING_PRIMARY", - "FIRING_SECONDARY", - "FIRING_TERTIARY", - "NO_BLACK_MARKET", - "TAKING_DAMAGE", - "RIDERS_ATTACKING", - NULL -}; -#endif - -#define INVALID_OPACITY -1.0f - -//------------------------------------------------------------------------------------------------- -class StealthUpdateModuleData : public UpdateModuleData -{ -public: - ObjectStatusMaskType m_hintDetectableStates; - ObjectStatusMaskType m_requiredStatus; - ObjectStatusMaskType m_forbiddenStatus; - FXList *m_disguiseRevealFX; - FXList *m_disguiseFX; - Real m_stealthSpeed; - Real m_friendlyOpacityMin; - Real m_friendlyOpacityMax; - Real m_revealDistanceFromTarget; - UnsignedInt m_disguiseTransitionFrames; - UnsignedInt m_disguiseRevealTransitionFrames; - UnsignedInt m_pulseFrames; - UnsignedInt m_stealthDelay; - UnsignedInt m_stealthLevel; - UnsignedInt m_blackMarketCheckFrames; - EvaMessage m_enemyDetectionEvaEvent; - EvaMessage m_ownDetectionEvaEvent; - Bool m_innateStealth; - Bool m_orderIdleEnemiesToAttackMeUponReveal; - Bool m_teamDisguised; - Bool m_useRiderStealth; - Bool m_grantedBySpecialPower; - - StealthUpdateModuleData(); - static void buildFieldParse(MultiIniFieldParse& p); - -}; - -//------------------------------------------------------------------------------------------------- -class StealthUpdate : public UpdateModule -{ - - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( StealthUpdate, "StealthUpdate" ) - MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( StealthUpdate, StealthUpdateModuleData ); - -public: - - StealthUpdate( Thing *thing, const ModuleData* moduleData ); - // virtual destructor prototype provided by memory pool declaration - - - virtual StealthUpdate* getStealth() { return this; } - - - virtual UpdateSleepTime update(); - - //Still gets called, even if held -ML - virtual DisabledMaskType getDisabledTypesToProcess() const { return MAKE_DISABLED_MASK( DISABLED_HELD ); } - - // ??? ugh - Bool isDisguised() const { return m_disguiseAsTemplate != NULL; } - Int getDisguisedPlayerIndex() const { return m_disguiseAsPlayerIndex; } - const ThingTemplate *getDisguisedTemplate() { return m_disguiseAsTemplate; } - void markAsDetected( UnsignedInt numFrames = 0 ); - void disguiseAsObject( const Object *target ); //wrapper function for ease. - Real getFriendlyOpacity() const; - UnsignedInt getStealthDelay() const { return getStealthUpdateModuleData()->m_stealthDelay; } - UnsignedInt getStealthLevel() const { return getStealthUpdateModuleData()->m_stealthLevel; } - EvaMessage getEnemyDetectionEvaEvent() const { return getStealthUpdateModuleData()->m_enemyDetectionEvaEvent; } - EvaMessage getOwnDetectionEvaEvent() const { return getStealthUpdateModuleData()->m_ownDetectionEvaEvent; } - Bool getOrderIdleEnemiesToAttackMeUponReveal() const { return getStealthUpdateModuleData()->m_orderIdleEnemiesToAttackMeUponReveal; } - Object* calcStealthOwner(); //Is it me that can stealth or is it my rider? - Bool allowedToStealth( Object *stealthOwner ) const; - void receiveGrant( Bool active = TRUE, UnsignedInt frames = 0 ); - - Bool isGrantedBySpecialPower( void ) { return getStealthUpdateModuleData()->m_grantedBySpecialPower; } - Bool isTemporaryGrant() { return m_framesGranted > 0; } - -protected: - - StealthLookType calcStealthedStatusForPlayer(const Object* obj, const Player* player); - Bool canDisguise() const { return getStealthUpdateModuleData()->m_teamDisguised; } - Real getRevealDistanceFromTarget() const { return getStealthUpdateModuleData()->m_revealDistanceFromTarget; } - void hintDetectableWhileUnstealthed( void ) ; - - void changeVisualDisguise(); - - UpdateSleepTime calcSleepTime() const; - -private: - UnsignedInt m_stealthAllowedFrame; - UnsignedInt m_detectionExpiresFrame; - mutable UnsignedInt m_nextBlackMarketCheckFrame; - Bool m_enabled; - - Real m_pulsePhaseRate; - Real m_pulsePhase; - - //Disguise only members - Int m_disguiseAsPlayerIndex; //The player team we are wanting to disguise as (might not actually be disguised yet). - const ThingTemplate *m_disguiseAsTemplate; //The disguise template (might not actually be using it yet) - UnsignedInt m_disguiseTransitionFrames; //How many frames are left before transition is complete. - Bool m_disguiseHalfpointReached; //In the middle of the transition, we will switch drawables! - Bool m_transitioningToDisguise; //Set when we are disguising -- clear when we're transitioning out of. - Bool m_disguised; //We're disguised as far as other players are concerned. - UnsignedInt m_framesGranted; //0 means forever... everything else is number of frames before stealth lost. - - // runtime xfer members (does not need saving) - Bool m_xferRestoreDisguise; //Tells us we need to restore our disguise - WeaponSetType m_requiresWeaponSetType; - -}; - - -#endif - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: StealthUpdate.h ////////////////////////////////////////////////////////////////////////// +// Author: Kris Morness, May 2002 +// Desc: An update that checks for a status bit to stealth the owning object +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __STEALTH_UPDATE_H_ +#define __STEALTH_UPDATE_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "GameLogic/Module/UpdateModule.h" + +// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// +class Thing; +enum StealthLookType CPP_11(: Int); +enum EvaMessage CPP_11(: Int); +class FXList; + +enum +{ + STEALTH_NOT_WHILE_ATTACKING = 0x00000001, + STEALTH_NOT_WHILE_MOVING = 0x00000002, + STEALTH_NOT_WHILE_USING_ABILITY = 0x00000004, + STEALTH_NOT_WHILE_FIRING_PRIMARY = 0x00000008, + STEALTH_NOT_WHILE_FIRING_SECONDARY = 0x00000010, + STEALTH_NOT_WHILE_FIRING_TERTIARY = 0x00000020, + STEALTH_ONLY_WITH_BLACK_MARKET = 0x00000040, + STEALTH_NOT_WHILE_TAKING_DAMAGE = 0x00000080, + STEALTH_NOT_WHILE_RIDERS_ATTACKING = 0x00000100, + STEALTH_NOT_WHILE_FIRING_FOUR = 0x00000200, + STEALTH_NOT_WHILE_FIRING_FIVE = 0x00000400, + STEALTH_NOT_WHILE_FIRING_SIX = 0x00000800, + STEALTH_NOT_WHILE_FIRING_SEVEN = 0x00001000, + STEALTH_NOT_WHILE_FIRING_EIGHT = 0x00002000, + STEALTH_NOT_WHILE_FIRING_WEAPON = (STEALTH_NOT_WHILE_FIRING_PRIMARY | STEALTH_NOT_WHILE_FIRING_SECONDARY | STEALTH_NOT_WHILE_FIRING_TERTIARY | STEALTH_NOT_WHILE_FIRING_FOUR | STEALTH_NOT_WHILE_FIRING_FIVE | STEALTH_NOT_WHILE_FIRING_SIX | STEALTH_NOT_WHILE_FIRING_SEVEN | STEALTH_NOT_WHILE_FIRING_EIGHT), +}; + +#ifdef DEFINE_STEALTHLEVEL_NAMES +static const char *TheStealthLevelNames[] = +{ + "ATTACKING", + "MOVING", + "USING_ABILITY", + "FIRING_PRIMARY", + "FIRING_SECONDARY", + "FIRING_TERTIARY", + "NO_BLACK_MARKET", + "TAKING_DAMAGE", + "RIDERS_ATTACKING", + "FIRING_WEAPON_FOUR", + "FIRING_WEAPON_FIVE", + "FIRING_WEAPON_SIX", + "FIRING_WEAPON_SEVEN", + "FIRING_WEAPON_EIGHT", + NULL +}; +#endif + +#define INVALID_OPACITY -1.0f + +//------------------------------------------------------------------------------------------------- +class StealthUpdateModuleData : public UpdateModuleData +{ +public: + ObjectStatusMaskType m_hintDetectableStates; + ObjectStatusMaskType m_requiredStatus; + ObjectStatusMaskType m_forbiddenStatus; + FXList *m_disguiseRevealFX; + FXList *m_disguiseFX; + Real m_stealthSpeed; + Real m_friendlyOpacityMin; + Real m_friendlyOpacityMax; + Real m_revealDistanceFromTarget; + UnsignedInt m_disguiseTransitionFrames; + UnsignedInt m_disguiseRevealTransitionFrames; + UnsignedInt m_pulseFrames; + UnsignedInt m_stealthDelay; + UnsignedInt m_stealthLevel; + UnsignedInt m_blackMarketCheckFrames; + EvaMessage m_enemyDetectionEvaEvent; + EvaMessage m_ownDetectionEvaEvent; + Bool m_innateStealth; + Bool m_orderIdleEnemiesToAttackMeUponReveal; + Bool m_teamDisguised; + Bool m_useRiderStealth; + Bool m_grantedBySpecialPower; + + StealthUpdateModuleData(); + static void buildFieldParse(MultiIniFieldParse& p); + +}; + +//------------------------------------------------------------------------------------------------- +class StealthUpdate : public UpdateModule +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( StealthUpdate, "StealthUpdate" ) + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( StealthUpdate, StealthUpdateModuleData ); + +public: + + StealthUpdate( Thing *thing, const ModuleData* moduleData ); + // virtual destructor prototype provided by memory pool declaration + + + virtual StealthUpdate* getStealth() { return this; } + + + virtual UpdateSleepTime update(); + + //Still gets called, even if held -ML + virtual DisabledMaskType getDisabledTypesToProcess() const { return MAKE_DISABLED_MASK( DISABLED_HELD ); } + + // ??? ugh + Bool isDisguised() const { return m_disguiseAsTemplate != NULL; } + Int getDisguisedPlayerIndex() const { return m_disguiseAsPlayerIndex; } + const ThingTemplate *getDisguisedTemplate() { return m_disguiseAsTemplate; } + void markAsDetected( UnsignedInt numFrames = 0 ); + void disguiseAsObject( const Object *target ); //wrapper function for ease. + Real getFriendlyOpacity() const; + UnsignedInt getStealthDelay() const { return getStealthUpdateModuleData()->m_stealthDelay; } + UnsignedInt getStealthLevel() const { return getStealthUpdateModuleData()->m_stealthLevel; } + EvaMessage getEnemyDetectionEvaEvent() const { return getStealthUpdateModuleData()->m_enemyDetectionEvaEvent; } + EvaMessage getOwnDetectionEvaEvent() const { return getStealthUpdateModuleData()->m_ownDetectionEvaEvent; } + Bool getOrderIdleEnemiesToAttackMeUponReveal() const { return getStealthUpdateModuleData()->m_orderIdleEnemiesToAttackMeUponReveal; } + Object* calcStealthOwner(); //Is it me that can stealth or is it my rider? + Bool allowedToStealth( Object *stealthOwner ) const; + void receiveGrant( Bool active = TRUE, UnsignedInt frames = 0 ); + + Bool isGrantedBySpecialPower( void ) { return getStealthUpdateModuleData()->m_grantedBySpecialPower; } + Bool isTemporaryGrant() { return m_framesGranted > 0; } + +protected: + + StealthLookType calcStealthedStatusForPlayer(const Object* obj, const Player* player); + Bool canDisguise() const { return getStealthUpdateModuleData()->m_teamDisguised; } + Real getRevealDistanceFromTarget() const { return getStealthUpdateModuleData()->m_revealDistanceFromTarget; } + void hintDetectableWhileUnstealthed( void ) ; + + void changeVisualDisguise(); + + UpdateSleepTime calcSleepTime() const; + +private: + UnsignedInt m_stealthAllowedFrame; + UnsignedInt m_detectionExpiresFrame; + mutable UnsignedInt m_nextBlackMarketCheckFrame; + Bool m_enabled; + + Real m_pulsePhaseRate; + Real m_pulsePhase; + + //Disguise only members + Int m_disguiseAsPlayerIndex; //The player team we are wanting to disguise as (might not actually be disguised yet). + const ThingTemplate *m_disguiseAsTemplate; //The disguise template (might not actually be using it yet) + UnsignedInt m_disguiseTransitionFrames; //How many frames are left before transition is complete. + Bool m_disguiseHalfpointReached; //In the middle of the transition, we will switch drawables! + Bool m_transitioningToDisguise; //Set when we are disguising -- clear when we're transitioning out of. + Bool m_disguised; //We're disguised as far as other players are concerned. + UnsignedInt m_framesGranted; //0 means forever... everything else is number of frames before stealth lost. + + // runtime xfer members (does not need saving) + Bool m_xferRestoreDisguise; //Tells us we need to restore our disguise + WeaponSetType m_requiresWeaponSetType; + +}; + + +#endif + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/WeaponSet.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/WeaponSet.h index 6b6fc75a88d..639ef8ca4ae 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/WeaponSet.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/WeaponSet.h @@ -59,6 +59,11 @@ static const char *TheWeaponSlotTypeNames[] = "PRIMARY", "SECONDARY", "TERTIARY", + "WEAPON_FOUR", + "WEAPON_FIVE", + "WEAPON_SIX", + "WEAPON_SEVEN", + "WEAPON_EIGHT", NULL }; @@ -68,6 +73,11 @@ static const LookupListRec TheWeaponSlotTypeNamesLookupList[] = { "PRIMARY", PRIMARY_WEAPON }, { "SECONDARY", SECONDARY_WEAPON }, { "TERTIARY", TERTIARY_WEAPON }, + { "WEAPON_FOUR", WEAPON_FOUR }, + { "WEAPON_FIVE", WEAPON_FIVE }, + { "WEAPON_SIX", WEAPON_SIX }, + { "WEAPON_SEVEN", WEAPON_SEVEN }, + { "WEAPON_EIGHT", WEAPON_EIGHT }, { NULL, 0 }// keep this last! }; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp b/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp index ef8b2a3ffec..40b1f6b59ea 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp @@ -175,6 +175,38 @@ const char* ModelConditionFlags::s_bitNameList[] = "WEAPONSET_PLAYER_UPGRADE2", "WEAPONSET_PLAYER_UPGRADE3", "WEAPONSET_PLAYER_UPGRADE4", + + // New Weaponslots (D-H) + + "PREATTACK_D", + "FIRING_D", + "BETWEEN_FIRING_SHOTS_D", + "RELOADING_D", + "USING_WEAPON_D", + + "PREATTACK_E", + "FIRING_E", + "BETWEEN_FIRING_SHOTS_E", + "RELOADING_E", + "USING_WEAPON_E", + + "PREATTACK_F", + "FIRING_F", + "BETWEEN_FIRING_SHOTS_F", + "RELOADING_F", + "USING_WEAPON_F", + + "PREATTACK_G", + "FIRING_G", + "BETWEEN_FIRING_SHOTS_G", + "RELOADING_G", + "USING_WEAPON_G", + + "PREATTACK_H", + "FIRING_H", + "BETWEEN_FIRING_SHOTS_H", + "RELOADING_H", + "USING_WEAPON_H", NULL }; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 149c1e31108..a41e48f9e5c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -510,6 +510,21 @@ void pickAndPlayUnitVoiceResponse( const DrawableList *list, GameMessage::Type m case TERTIARY_WEAPON: soundToPlayPtr = templ->getPerUnitSound( "VoiceTertiaryWeaponMode" ); break; + case WEAPON_FOUR: + soundToPlayPtr = templ->getPerUnitSound( "VoicerWeaponModeFour" ); + break; + case WEAPON_FIVE: + soundToPlayPtr = templ->getPerUnitSound( "VoicerWeaponModeFive" ); + break; + case WEAPON_SIX: + soundToPlayPtr = templ->getPerUnitSound( "VoicerWeaponModeSix" ); + break; + case WEAPON_SEVEN: + soundToPlayPtr = templ->getPerUnitSound( "VoicerWeaponModeSeven" ); + break; + case WEAPON_EIGHT: + soundToPlayPtr = templ->getPerUnitSound( "VoicerWeaponModeEight" ); + break; } objectWithSound = obj; skip = true; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp index eceebc1324e..ace990a8d5b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp @@ -1486,18 +1486,43 @@ StateReturnType AIDeadState::onEnter() nonDyingStuff.set(MODELCONDITION_USING_WEAPON_A); nonDyingStuff.set(MODELCONDITION_USING_WEAPON_B); nonDyingStuff.set(MODELCONDITION_USING_WEAPON_C); + nonDyingStuff.set(MODELCONDITION_USING_WEAPON_D); + nonDyingStuff.set(MODELCONDITION_USING_WEAPON_E); + nonDyingStuff.set(MODELCONDITION_USING_WEAPON_F); + nonDyingStuff.set(MODELCONDITION_USING_WEAPON_G); + nonDyingStuff.set(MODELCONDITION_USING_WEAPON_H); nonDyingStuff.set(MODELCONDITION_FIRING_A); nonDyingStuff.set(MODELCONDITION_FIRING_B); nonDyingStuff.set(MODELCONDITION_FIRING_C); + nonDyingStuff.set(MODELCONDITION_FIRING_D); + nonDyingStuff.set(MODELCONDITION_FIRING_E); + nonDyingStuff.set(MODELCONDITION_FIRING_F); + nonDyingStuff.set(MODELCONDITION_FIRING_G); + nonDyingStuff.set(MODELCONDITION_FIRING_H); nonDyingStuff.set(MODELCONDITION_BETWEEN_FIRING_SHOTS_A); nonDyingStuff.set(MODELCONDITION_BETWEEN_FIRING_SHOTS_B); nonDyingStuff.set(MODELCONDITION_BETWEEN_FIRING_SHOTS_C); + nonDyingStuff.set(MODELCONDITION_BETWEEN_FIRING_SHOTS_D); + nonDyingStuff.set(MODELCONDITION_BETWEEN_FIRING_SHOTS_E); + nonDyingStuff.set(MODELCONDITION_BETWEEN_FIRING_SHOTS_F); + nonDyingStuff.set(MODELCONDITION_BETWEEN_FIRING_SHOTS_G); + nonDyingStuff.set(MODELCONDITION_BETWEEN_FIRING_SHOTS_H); nonDyingStuff.set(MODELCONDITION_RELOADING_A); nonDyingStuff.set(MODELCONDITION_RELOADING_B); nonDyingStuff.set(MODELCONDITION_RELOADING_C); + nonDyingStuff.set(MODELCONDITION_RELOADING_D); + nonDyingStuff.set(MODELCONDITION_RELOADING_E); + nonDyingStuff.set(MODELCONDITION_RELOADING_F); + nonDyingStuff.set(MODELCONDITION_RELOADING_G); + nonDyingStuff.set(MODELCONDITION_RELOADING_H); nonDyingStuff.set(MODELCONDITION_PREATTACK_A); nonDyingStuff.set(MODELCONDITION_PREATTACK_B); nonDyingStuff.set(MODELCONDITION_PREATTACK_C); + nonDyingStuff.set(MODELCONDITION_PREATTACK_D); + nonDyingStuff.set(MODELCONDITION_PREATTACK_E); + nonDyingStuff.set(MODELCONDITION_PREATTACK_F); + nonDyingStuff.set(MODELCONDITION_PREATTACK_G); + nonDyingStuff.set(MODELCONDITION_PREATTACK_H); #ifdef ALLOW_SURRENDER nonDyingStuff.set(MODELCONDITION_SURRENDER); #endif diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 853f82ffd92..ab6137f8e36 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -145,6 +145,41 @@ static const ModelConditionFlags s_allWeaponFireFlags[WEAPONSLOT_COUNT] = MODELCONDITION_RELOADING_C, MODELCONDITION_PREATTACK_C, MODELCONDITION_USING_WEAPON_C + ), + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_D, + MODELCONDITION_BETWEEN_FIRING_SHOTS_D, + MODELCONDITION_RELOADING_D, + MODELCONDITION_PREATTACK_D, + MODELCONDITION_USING_WEAPON_D + ), + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_E, + MODELCONDITION_BETWEEN_FIRING_SHOTS_E, + MODELCONDITION_RELOADING_E, + MODELCONDITION_PREATTACK_E, + MODELCONDITION_USING_WEAPON_E + ), + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_F, + MODELCONDITION_BETWEEN_FIRING_SHOTS_F, + MODELCONDITION_RELOADING_F, + MODELCONDITION_PREATTACK_F, + MODELCONDITION_USING_WEAPON_F + ), + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_G, + MODELCONDITION_BETWEEN_FIRING_SHOTS_G, + MODELCONDITION_RELOADING_G, + MODELCONDITION_PREATTACK_G, + MODELCONDITION_USING_WEAPON_G + ), + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_H, + MODELCONDITION_BETWEEN_FIRING_SHOTS_H, + MODELCONDITION_RELOADING_H, + MODELCONDITION_PREATTACK_H, + MODELCONDITION_USING_WEAPON_H ) }; @@ -1277,7 +1312,12 @@ Bool Object::getWeaponInWeaponSlotSyncedToSlot(WeaponSlotType thisSlot, WeaponSl return ((Int)mask >= 0) && ((mask & (1 << CMD_SYNC_TO_PRIMARY) && otherSlot == PRIMARY_WEAPON) || (mask & (1 << CMD_SYNC_TO_SECONDARY) && otherSlot == SECONDARY_WEAPON) || - (mask & (1 << CMD_SYNC_TO_TERTIARY) && otherSlot == TERTIARY_WEAPON)); + (mask & (1 << CMD_SYNC_TO_TERTIARY) && otherSlot == TERTIARY_WEAPON) || + (mask & (1 << CMD_SYNC_TO_FOUR) && otherSlot == WEAPON_FOUR) || + (mask & (1 << CMD_SYNC_TO_FIVE) && otherSlot == WEAPON_FIVE) || + (mask & (1 << CMD_SYNC_TO_SIX) && otherSlot == WEAPON_SIX) || + (mask & (1 << CMD_SYNC_TO_SEVEN) && otherSlot == WEAPON_SEVEN) || + (mask & (1 << CMD_SYNC_TO_EIGHT) && otherSlot == WEAPON_EIGHT)); } diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp index 5fe77f7e2dd..8505f25a857 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp @@ -389,11 +389,11 @@ void WeaponSet::updateWeaponSet(const Object* obj) //------------------------------------------------------------------------------------------------- /*static*/ ModelConditionFlags WeaponSet::getModelConditionForWeaponSlot(WeaponSlotType wslot, WeaponSetConditionType a) { - static const ModelConditionFlagType Nothing[WEAPONSLOT_COUNT] = { MODELCONDITION_INVALID, MODELCONDITION_INVALID, MODELCONDITION_INVALID }; - static const ModelConditionFlagType Firing[WEAPONSLOT_COUNT] = { MODELCONDITION_FIRING_A, MODELCONDITION_FIRING_B, MODELCONDITION_FIRING_C }; - static const ModelConditionFlagType Betweening[WEAPONSLOT_COUNT] = { MODELCONDITION_BETWEEN_FIRING_SHOTS_A, MODELCONDITION_BETWEEN_FIRING_SHOTS_B, MODELCONDITION_BETWEEN_FIRING_SHOTS_C }; - static const ModelConditionFlagType Reloading[WEAPONSLOT_COUNT] = { MODELCONDITION_RELOADING_A, MODELCONDITION_RELOADING_B, MODELCONDITION_RELOADING_C }; - static const ModelConditionFlagType PreAttack[WEAPONSLOT_COUNT] = { MODELCONDITION_PREATTACK_A, MODELCONDITION_PREATTACK_B, MODELCONDITION_PREATTACK_C }; + static const ModelConditionFlagType Nothing[WEAPONSLOT_COUNT] = { MODELCONDITION_INVALID, MODELCONDITION_INVALID, MODELCONDITION_INVALID, MODELCONDITION_INVALID, MODELCONDITION_INVALID, MODELCONDITION_INVALID, MODELCONDITION_INVALID, MODELCONDITION_INVALID }; + static const ModelConditionFlagType Firing[WEAPONSLOT_COUNT] = { MODELCONDITION_FIRING_A, MODELCONDITION_FIRING_B, MODELCONDITION_FIRING_C, MODELCONDITION_FIRING_D, MODELCONDITION_FIRING_E, MODELCONDITION_FIRING_F, MODELCONDITION_FIRING_G, MODELCONDITION_FIRING_H }; + static const ModelConditionFlagType Betweening[WEAPONSLOT_COUNT] = { MODELCONDITION_BETWEEN_FIRING_SHOTS_A, MODELCONDITION_BETWEEN_FIRING_SHOTS_B, MODELCONDITION_BETWEEN_FIRING_SHOTS_C, MODELCONDITION_BETWEEN_FIRING_SHOTS_D, MODELCONDITION_BETWEEN_FIRING_SHOTS_E, MODELCONDITION_BETWEEN_FIRING_SHOTS_F, MODELCONDITION_BETWEEN_FIRING_SHOTS_G, MODELCONDITION_BETWEEN_FIRING_SHOTS_H }; + static const ModelConditionFlagType Reloading[WEAPONSLOT_COUNT] = { MODELCONDITION_RELOADING_A, MODELCONDITION_RELOADING_B, MODELCONDITION_RELOADING_C, MODELCONDITION_RELOADING_D, MODELCONDITION_RELOADING_E, MODELCONDITION_RELOADING_F, MODELCONDITION_RELOADING_G, MODELCONDITION_RELOADING_H }; + static const ModelConditionFlagType PreAttack[WEAPONSLOT_COUNT] = { MODELCONDITION_PREATTACK_A, MODELCONDITION_PREATTACK_B, MODELCONDITION_PREATTACK_C, MODELCONDITION_PREATTACK_D, MODELCONDITION_PREATTACK_E, MODELCONDITION_PREATTACK_F, MODELCONDITION_PREATTACK_G, MODELCONDITION_PREATTACK_H }; static const ModelConditionFlagType* Lookup[WSF_COUNT] = { Nothing, Firing, Betweening, Reloading, PreAttack }; ModelConditionFlags flags; // defaults to all clear @@ -402,7 +402,7 @@ void WeaponSet::updateWeaponSet(const Object* obj) if (f != MODELCONDITION_INVALID) flags.set(f); - static const ModelConditionFlagType Using[WEAPONSLOT_COUNT] = { MODELCONDITION_USING_WEAPON_A, MODELCONDITION_USING_WEAPON_B, MODELCONDITION_USING_WEAPON_C }; + static const ModelConditionFlagType Using[WEAPONSLOT_COUNT] = { MODELCONDITION_USING_WEAPON_A, MODELCONDITION_USING_WEAPON_B, MODELCONDITION_USING_WEAPON_C, MODELCONDITION_USING_WEAPON_D, MODELCONDITION_USING_WEAPON_E, MODELCONDITION_USING_WEAPON_F, MODELCONDITION_USING_WEAPON_G, MODELCONDITION_USING_WEAPON_H }; if (a != WSF_NONE) flags.set(Using[wslot]); diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/DrawObject.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/DrawObject.cpp index d835153f7f5..a30053fafc5 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/DrawObject.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/DrawObject.cpp @@ -1783,7 +1783,17 @@ void DrawObject::updateVBWithWeaponRange(MapObject *pMapObj, CameraClass* camera return; } - const unsigned long colors[WEAPONSLOT_COUNT] = {0xFF00FF00, 0xFFE0F00A, 0xFFFF0000}; // Green, Yellow, Red + // const unsigned long colors[WEAPONSLOT_COUNT] = {0xFF00FF00, 0xFFE0F00A, 0xFFFF0000}; // Green, Yellow, Red + const unsigned long colors[WEAPONSLOT_COUNT] = { + 0xFF00FF00, // Green + 0xFFFFFF00, // Yellow + 0xFFFF0000, // Red + 0xFF0000FF, // Blue + 0xFF00FFFF, // Cyan + 0xFFFF00FF, // Magenta + 0xFF000000, // Black + 0xFFFFFFFF // White + }; Coord3D pos = *pMapObj->getLocation(); From ac5bcd1677d2d5c617d30dfcf46f496ff5021eee Mon Sep 17 00:00:00 2001 From: Andi Date: Sun, 8 Jun 2025 14:20:24 +0200 Subject: [PATCH 20/42] line endings --- .../Code/GameEngine/Include/Common/GameType.h | 402 +++++++++--------- .../Include/GameLogic/Module/StealthUpdate.h | 390 ++++++++--------- 2 files changed, 396 insertions(+), 396 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GameType.h b/GeneralsMD/Code/GameEngine/Include/Common/GameType.h index baba14a1466..f11ee558a08 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GameType.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GameType.h @@ -1,201 +1,201 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// GameType.h -// Basic data types needed for the game engine. This is an extension of BaseType.h. -// Author: Michael S. Booth, April 2001 - -#pragma once - -#ifndef _GAME_TYPE_H_ -#define _GAME_TYPE_H_ - -#include "Lib/BaseType.h" - -// the default size of the world map -#define DEFAULT_WORLD_WIDTH 64 -#define DEFAULT_WORLD_HEIGHT 64 - -/// A unique, generic "identifier" used to access Objects. -enum ObjectID CPP_11(: Int) -{ - INVALID_ID = 0, - FORCE_OBJECTID_TO_LONG_SIZE = 0x7ffffff -}; - -/// A unique, generic "identifier" used to access Drawables. -enum DrawableID CPP_11(: Int) -{ - INVALID_DRAWABLE_ID = 0, - FORCE_DRAWABLEID_TO_LONG_SIZE = 0x7ffffff -}; - -/// A unique, generic "identifier" used to identify player specified formations. -enum FormationID CPP_11(: Int) -{ - NO_FORMATION_ID = 0, // Unit is not a member of any formation - FORCE_FORMATIONID_TO_LONG_SIZE = 0x7ffffff -}; - -#define INVALID_ANGLE -100.0f - -class INI; - -//------------------------------------------------------------------------------------------------- -/** The time of day enumeration, keep in sync with TimeOfDayNames[] */ -//------------------------------------------------------------------------------------------------- -enum TimeOfDay CPP_11(: Int) -{ - TIME_OF_DAY_INVALID = 0, - TIME_OF_DAY_FIRST = 1, - TIME_OF_DAY_MORNING = TIME_OF_DAY_FIRST, - TIME_OF_DAY_AFTERNOON, - TIME_OF_DAY_EVENING, - TIME_OF_DAY_NIGHT, - - TIME_OF_DAY_COUNT // keep this last -}; - -extern const char *TimeOfDayNames[]; -// defined in Common/GameType.cpp - -//------------------------------------------------------------------------------------------------- -enum Weather CPP_11(: Int) -{ - WEATHER_NORMAL = 0, - WEATHER_SNOWY = 1, - - WEATHER_COUNT // keep this last -}; - -extern const char *WeatherNames[]; - -enum Scorches CPP_11(: Int) -{ - SCORCH_1 = 0, - SCORCH_2 = 1, - SCORCH_3 = 2, - SCORCH_4 = 3, - SHADOW_SCORCH = 4, -/* SCORCH_6 = 5, - SCORCH_7 = 6, - SCORCH_8 = 7, - - CRATER_1 = 8, - CRATER_2 = 9, - CRATER_3 = 10, - CRATER_4 = 11, - CRATER_5 = 12, - CRATER_6 = 13, - CRATER_7 = 14, - CRATER_8 = 15, - - - MISC_DECAL_1 = 16, - MISC_DECAL_2 = 17, - MISC_DECAL_3 = 18, - MISC_DECAL_4 = 19, - MISC_DECAL_5 = 20, - MISC_DECAL_6 = 21, - MISC_DECAL_7 = 22, - MISC_DECAL_8 = 23, - - MISC_DECAL_9 = 24, - MISC_DECAL_10 = 25, - MISC_DECAL_11 = 26, - MISC_DECAL_12 = 27, - MISC_DECAL_13 = 28, - MISC_DECAL_14 = 29, - MISC_DECAL_15 = 30, - MISC_DECAL_16 = 31, - - MISC_DECAL_17 = 32, - MISC_DECAL_18 = 33, - MISC_DECAL_19 = 34, - MISC_DECAL_20 = 35, - MISC_DECAL_21 = 36, - MISC_DECAL_22 = 37, - MISC_DECAL_23 = 38, - MISC_DECAL_24 = 39, - - MISC_DECAL_25 = 40, - MISC_DECAL_26 = 41, - MISC_DECAL_27 = 42, - MISC_DECAL_28 = 43, - MISC_DECAL_29 = 44, - MISC_DECAL_30 = 45, - MISC_DECAL_31 = 46, - MISC_DECAL_32 = 47, - - MISC_DECAL_33 = 48, - MISC_DECAL_34 = 49, - MISC_DECAL_35 = 50, - MISC_DECAL_36 = 51, - MISC_DECAL_37 = 52, - MISC_DECAL_38 = 53, - MISC_DECAL_39 = 54, - MISC_DECAL_40 = 55, - - MISC_DECAL_41 = 56, - MISC_DECAL_42 = 57, - MISC_DECAL_43 = 58, - MISC_DECAL_44 = 59, - MISC_DECAL_45 = 60, - MISC_DECAL_46 = 61, - MISC_DECAL_47 = 62, - MISC_DECAL_48 = 63, -*/ - SCORCH_COUNT -}; - -//------------------------------------------------------------------------------------------------- -enum WeaponSlotType CPP_11(: Int) -{ - PRIMARY_WEAPON = 0, - SECONDARY_WEAPON, - TERTIARY_WEAPON, - WEAPON_FOUR, - WEAPON_FIVE, - WEAPON_SIX, - WEAPON_SEVEN, - WEAPON_EIGHT, - - WEAPONSLOT_COUNT // keep last -}; - -//------------------------------------------------------------------------------------------------- -// Pathfind layers - ground is the first layer, each bridge is another. jba. -// Layer 1 is the ground. -// Layer 2 is the top layer - bridge if one is present, ground otherwise. -// Layer 2 - LAYER_LAST -1 are bridges. -// Layer_WALL is a special "wall" layer for letting units run aroound on top of a wall -// made of structures. -// Note that the bridges just index in the pathfinder, so you don't actually -// have a LAYER_BRIDGE_1 enum value. -enum PathfindLayerEnum CPP_11(: Int) {LAYER_INVALID = 0, LAYER_GROUND = 1, LAYER_WALL = 15, LAYER_LAST=15}; - -//------------------------------------------------------------------------------------------------- - -#endif // _GAME_TYPE_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// GameType.h +// Basic data types needed for the game engine. This is an extension of BaseType.h. +// Author: Michael S. Booth, April 2001 + +#pragma once + +#ifndef _GAME_TYPE_H_ +#define _GAME_TYPE_H_ + +#include "Lib/BaseType.h" + +// the default size of the world map +#define DEFAULT_WORLD_WIDTH 64 +#define DEFAULT_WORLD_HEIGHT 64 + +/// A unique, generic "identifier" used to access Objects. +enum ObjectID CPP_11(: Int) +{ + INVALID_ID = 0, + FORCE_OBJECTID_TO_LONG_SIZE = 0x7ffffff +}; + +/// A unique, generic "identifier" used to access Drawables. +enum DrawableID CPP_11(: Int) +{ + INVALID_DRAWABLE_ID = 0, + FORCE_DRAWABLEID_TO_LONG_SIZE = 0x7ffffff +}; + +/// A unique, generic "identifier" used to identify player specified formations. +enum FormationID CPP_11(: Int) +{ + NO_FORMATION_ID = 0, // Unit is not a member of any formation + FORCE_FORMATIONID_TO_LONG_SIZE = 0x7ffffff +}; + +#define INVALID_ANGLE -100.0f + +class INI; + +//------------------------------------------------------------------------------------------------- +/** The time of day enumeration, keep in sync with TimeOfDayNames[] */ +//------------------------------------------------------------------------------------------------- +enum TimeOfDay CPP_11(: Int) +{ + TIME_OF_DAY_INVALID = 0, + TIME_OF_DAY_FIRST = 1, + TIME_OF_DAY_MORNING = TIME_OF_DAY_FIRST, + TIME_OF_DAY_AFTERNOON, + TIME_OF_DAY_EVENING, + TIME_OF_DAY_NIGHT, + + TIME_OF_DAY_COUNT // keep this last +}; + +extern const char *TimeOfDayNames[]; +// defined in Common/GameType.cpp + +//------------------------------------------------------------------------------------------------- +enum Weather CPP_11(: Int) +{ + WEATHER_NORMAL = 0, + WEATHER_SNOWY = 1, + + WEATHER_COUNT // keep this last +}; + +extern const char *WeatherNames[]; + +enum Scorches CPP_11(: Int) +{ + SCORCH_1 = 0, + SCORCH_2 = 1, + SCORCH_3 = 2, + SCORCH_4 = 3, + SHADOW_SCORCH = 4, +/* SCORCH_6 = 5, + SCORCH_7 = 6, + SCORCH_8 = 7, + + CRATER_1 = 8, + CRATER_2 = 9, + CRATER_3 = 10, + CRATER_4 = 11, + CRATER_5 = 12, + CRATER_6 = 13, + CRATER_7 = 14, + CRATER_8 = 15, + + + MISC_DECAL_1 = 16, + MISC_DECAL_2 = 17, + MISC_DECAL_3 = 18, + MISC_DECAL_4 = 19, + MISC_DECAL_5 = 20, + MISC_DECAL_6 = 21, + MISC_DECAL_7 = 22, + MISC_DECAL_8 = 23, + + MISC_DECAL_9 = 24, + MISC_DECAL_10 = 25, + MISC_DECAL_11 = 26, + MISC_DECAL_12 = 27, + MISC_DECAL_13 = 28, + MISC_DECAL_14 = 29, + MISC_DECAL_15 = 30, + MISC_DECAL_16 = 31, + + MISC_DECAL_17 = 32, + MISC_DECAL_18 = 33, + MISC_DECAL_19 = 34, + MISC_DECAL_20 = 35, + MISC_DECAL_21 = 36, + MISC_DECAL_22 = 37, + MISC_DECAL_23 = 38, + MISC_DECAL_24 = 39, + + MISC_DECAL_25 = 40, + MISC_DECAL_26 = 41, + MISC_DECAL_27 = 42, + MISC_DECAL_28 = 43, + MISC_DECAL_29 = 44, + MISC_DECAL_30 = 45, + MISC_DECAL_31 = 46, + MISC_DECAL_32 = 47, + + MISC_DECAL_33 = 48, + MISC_DECAL_34 = 49, + MISC_DECAL_35 = 50, + MISC_DECAL_36 = 51, + MISC_DECAL_37 = 52, + MISC_DECAL_38 = 53, + MISC_DECAL_39 = 54, + MISC_DECAL_40 = 55, + + MISC_DECAL_41 = 56, + MISC_DECAL_42 = 57, + MISC_DECAL_43 = 58, + MISC_DECAL_44 = 59, + MISC_DECAL_45 = 60, + MISC_DECAL_46 = 61, + MISC_DECAL_47 = 62, + MISC_DECAL_48 = 63, +*/ + SCORCH_COUNT +}; + +//------------------------------------------------------------------------------------------------- +enum WeaponSlotType CPP_11(: Int) +{ + PRIMARY_WEAPON = 0, + SECONDARY_WEAPON, + TERTIARY_WEAPON, + WEAPON_FOUR, + WEAPON_FIVE, + WEAPON_SIX, + WEAPON_SEVEN, + WEAPON_EIGHT, + + WEAPONSLOT_COUNT // keep last +}; + +//------------------------------------------------------------------------------------------------- +// Pathfind layers - ground is the first layer, each bridge is another. jba. +// Layer 1 is the ground. +// Layer 2 is the top layer - bridge if one is present, ground otherwise. +// Layer 2 - LAYER_LAST -1 are bridges. +// Layer_WALL is a special "wall" layer for letting units run aroound on top of a wall +// made of structures. +// Note that the bridges just index in the pathfinder, so you don't actually +// have a LAYER_BRIDGE_1 enum value. +enum PathfindLayerEnum CPP_11(: Int) {LAYER_INVALID = 0, LAYER_GROUND = 1, LAYER_WALL = 15, LAYER_LAST=15}; + +//------------------------------------------------------------------------------------------------- + +#endif // _GAME_TYPE_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StealthUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StealthUpdate.h index 9469253882e..149b5aa79c3 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StealthUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StealthUpdate.h @@ -1,195 +1,195 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: StealthUpdate.h ////////////////////////////////////////////////////////////////////////// -// Author: Kris Morness, May 2002 -// Desc: An update that checks for a status bit to stealth the owning object -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __STEALTH_UPDATE_H_ -#define __STEALTH_UPDATE_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "GameLogic/Module/UpdateModule.h" - -// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// -class Thing; -enum StealthLookType CPP_11(: Int); -enum EvaMessage CPP_11(: Int); -class FXList; - -enum -{ - STEALTH_NOT_WHILE_ATTACKING = 0x00000001, - STEALTH_NOT_WHILE_MOVING = 0x00000002, - STEALTH_NOT_WHILE_USING_ABILITY = 0x00000004, - STEALTH_NOT_WHILE_FIRING_PRIMARY = 0x00000008, - STEALTH_NOT_WHILE_FIRING_SECONDARY = 0x00000010, - STEALTH_NOT_WHILE_FIRING_TERTIARY = 0x00000020, - STEALTH_ONLY_WITH_BLACK_MARKET = 0x00000040, - STEALTH_NOT_WHILE_TAKING_DAMAGE = 0x00000080, - STEALTH_NOT_WHILE_RIDERS_ATTACKING = 0x00000100, - STEALTH_NOT_WHILE_FIRING_FOUR = 0x00000200, - STEALTH_NOT_WHILE_FIRING_FIVE = 0x00000400, - STEALTH_NOT_WHILE_FIRING_SIX = 0x00000800, - STEALTH_NOT_WHILE_FIRING_SEVEN = 0x00001000, - STEALTH_NOT_WHILE_FIRING_EIGHT = 0x00002000, - STEALTH_NOT_WHILE_FIRING_WEAPON = (STEALTH_NOT_WHILE_FIRING_PRIMARY | STEALTH_NOT_WHILE_FIRING_SECONDARY | STEALTH_NOT_WHILE_FIRING_TERTIARY | STEALTH_NOT_WHILE_FIRING_FOUR | STEALTH_NOT_WHILE_FIRING_FIVE | STEALTH_NOT_WHILE_FIRING_SIX | STEALTH_NOT_WHILE_FIRING_SEVEN | STEALTH_NOT_WHILE_FIRING_EIGHT), -}; - -#ifdef DEFINE_STEALTHLEVEL_NAMES -static const char *TheStealthLevelNames[] = -{ - "ATTACKING", - "MOVING", - "USING_ABILITY", - "FIRING_PRIMARY", - "FIRING_SECONDARY", - "FIRING_TERTIARY", - "NO_BLACK_MARKET", - "TAKING_DAMAGE", - "RIDERS_ATTACKING", - "FIRING_WEAPON_FOUR", - "FIRING_WEAPON_FIVE", - "FIRING_WEAPON_SIX", - "FIRING_WEAPON_SEVEN", - "FIRING_WEAPON_EIGHT", - NULL -}; -#endif - -#define INVALID_OPACITY -1.0f - -//------------------------------------------------------------------------------------------------- -class StealthUpdateModuleData : public UpdateModuleData -{ -public: - ObjectStatusMaskType m_hintDetectableStates; - ObjectStatusMaskType m_requiredStatus; - ObjectStatusMaskType m_forbiddenStatus; - FXList *m_disguiseRevealFX; - FXList *m_disguiseFX; - Real m_stealthSpeed; - Real m_friendlyOpacityMin; - Real m_friendlyOpacityMax; - Real m_revealDistanceFromTarget; - UnsignedInt m_disguiseTransitionFrames; - UnsignedInt m_disguiseRevealTransitionFrames; - UnsignedInt m_pulseFrames; - UnsignedInt m_stealthDelay; - UnsignedInt m_stealthLevel; - UnsignedInt m_blackMarketCheckFrames; - EvaMessage m_enemyDetectionEvaEvent; - EvaMessage m_ownDetectionEvaEvent; - Bool m_innateStealth; - Bool m_orderIdleEnemiesToAttackMeUponReveal; - Bool m_teamDisguised; - Bool m_useRiderStealth; - Bool m_grantedBySpecialPower; - - StealthUpdateModuleData(); - static void buildFieldParse(MultiIniFieldParse& p); - -}; - -//------------------------------------------------------------------------------------------------- -class StealthUpdate : public UpdateModule -{ - - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( StealthUpdate, "StealthUpdate" ) - MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( StealthUpdate, StealthUpdateModuleData ); - -public: - - StealthUpdate( Thing *thing, const ModuleData* moduleData ); - // virtual destructor prototype provided by memory pool declaration - - - virtual StealthUpdate* getStealth() { return this; } - - - virtual UpdateSleepTime update(); - - //Still gets called, even if held -ML - virtual DisabledMaskType getDisabledTypesToProcess() const { return MAKE_DISABLED_MASK( DISABLED_HELD ); } - - // ??? ugh - Bool isDisguised() const { return m_disguiseAsTemplate != NULL; } - Int getDisguisedPlayerIndex() const { return m_disguiseAsPlayerIndex; } - const ThingTemplate *getDisguisedTemplate() { return m_disguiseAsTemplate; } - void markAsDetected( UnsignedInt numFrames = 0 ); - void disguiseAsObject( const Object *target ); //wrapper function for ease. - Real getFriendlyOpacity() const; - UnsignedInt getStealthDelay() const { return getStealthUpdateModuleData()->m_stealthDelay; } - UnsignedInt getStealthLevel() const { return getStealthUpdateModuleData()->m_stealthLevel; } - EvaMessage getEnemyDetectionEvaEvent() const { return getStealthUpdateModuleData()->m_enemyDetectionEvaEvent; } - EvaMessage getOwnDetectionEvaEvent() const { return getStealthUpdateModuleData()->m_ownDetectionEvaEvent; } - Bool getOrderIdleEnemiesToAttackMeUponReveal() const { return getStealthUpdateModuleData()->m_orderIdleEnemiesToAttackMeUponReveal; } - Object* calcStealthOwner(); //Is it me that can stealth or is it my rider? - Bool allowedToStealth( Object *stealthOwner ) const; - void receiveGrant( Bool active = TRUE, UnsignedInt frames = 0 ); - - Bool isGrantedBySpecialPower( void ) { return getStealthUpdateModuleData()->m_grantedBySpecialPower; } - Bool isTemporaryGrant() { return m_framesGranted > 0; } - -protected: - - StealthLookType calcStealthedStatusForPlayer(const Object* obj, const Player* player); - Bool canDisguise() const { return getStealthUpdateModuleData()->m_teamDisguised; } - Real getRevealDistanceFromTarget() const { return getStealthUpdateModuleData()->m_revealDistanceFromTarget; } - void hintDetectableWhileUnstealthed( void ) ; - - void changeVisualDisguise(); - - UpdateSleepTime calcSleepTime() const; - -private: - UnsignedInt m_stealthAllowedFrame; - UnsignedInt m_detectionExpiresFrame; - mutable UnsignedInt m_nextBlackMarketCheckFrame; - Bool m_enabled; - - Real m_pulsePhaseRate; - Real m_pulsePhase; - - //Disguise only members - Int m_disguiseAsPlayerIndex; //The player team we are wanting to disguise as (might not actually be disguised yet). - const ThingTemplate *m_disguiseAsTemplate; //The disguise template (might not actually be using it yet) - UnsignedInt m_disguiseTransitionFrames; //How many frames are left before transition is complete. - Bool m_disguiseHalfpointReached; //In the middle of the transition, we will switch drawables! - Bool m_transitioningToDisguise; //Set when we are disguising -- clear when we're transitioning out of. - Bool m_disguised; //We're disguised as far as other players are concerned. - UnsignedInt m_framesGranted; //0 means forever... everything else is number of frames before stealth lost. - - // runtime xfer members (does not need saving) - Bool m_xferRestoreDisguise; //Tells us we need to restore our disguise - WeaponSetType m_requiresWeaponSetType; - -}; - - -#endif - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: StealthUpdate.h ////////////////////////////////////////////////////////////////////////// +// Author: Kris Morness, May 2002 +// Desc: An update that checks for a status bit to stealth the owning object +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __STEALTH_UPDATE_H_ +#define __STEALTH_UPDATE_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "GameLogic/Module/UpdateModule.h" + +// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// +class Thing; +enum StealthLookType CPP_11(: Int); +enum EvaMessage CPP_11(: Int); +class FXList; + +enum +{ + STEALTH_NOT_WHILE_ATTACKING = 0x00000001, + STEALTH_NOT_WHILE_MOVING = 0x00000002, + STEALTH_NOT_WHILE_USING_ABILITY = 0x00000004, + STEALTH_NOT_WHILE_FIRING_PRIMARY = 0x00000008, + STEALTH_NOT_WHILE_FIRING_SECONDARY = 0x00000010, + STEALTH_NOT_WHILE_FIRING_TERTIARY = 0x00000020, + STEALTH_ONLY_WITH_BLACK_MARKET = 0x00000040, + STEALTH_NOT_WHILE_TAKING_DAMAGE = 0x00000080, + STEALTH_NOT_WHILE_RIDERS_ATTACKING = 0x00000100, + STEALTH_NOT_WHILE_FIRING_FOUR = 0x00000200, + STEALTH_NOT_WHILE_FIRING_FIVE = 0x00000400, + STEALTH_NOT_WHILE_FIRING_SIX = 0x00000800, + STEALTH_NOT_WHILE_FIRING_SEVEN = 0x00001000, + STEALTH_NOT_WHILE_FIRING_EIGHT = 0x00002000, + STEALTH_NOT_WHILE_FIRING_WEAPON = (STEALTH_NOT_WHILE_FIRING_PRIMARY | STEALTH_NOT_WHILE_FIRING_SECONDARY | STEALTH_NOT_WHILE_FIRING_TERTIARY | STEALTH_NOT_WHILE_FIRING_FOUR | STEALTH_NOT_WHILE_FIRING_FIVE | STEALTH_NOT_WHILE_FIRING_SIX | STEALTH_NOT_WHILE_FIRING_SEVEN | STEALTH_NOT_WHILE_FIRING_EIGHT), +}; + +#ifdef DEFINE_STEALTHLEVEL_NAMES +static const char *TheStealthLevelNames[] = +{ + "ATTACKING", + "MOVING", + "USING_ABILITY", + "FIRING_PRIMARY", + "FIRING_SECONDARY", + "FIRING_TERTIARY", + "NO_BLACK_MARKET", + "TAKING_DAMAGE", + "RIDERS_ATTACKING", + "FIRING_WEAPON_FOUR", + "FIRING_WEAPON_FIVE", + "FIRING_WEAPON_SIX", + "FIRING_WEAPON_SEVEN", + "FIRING_WEAPON_EIGHT", + NULL +}; +#endif + +#define INVALID_OPACITY -1.0f + +//------------------------------------------------------------------------------------------------- +class StealthUpdateModuleData : public UpdateModuleData +{ +public: + ObjectStatusMaskType m_hintDetectableStates; + ObjectStatusMaskType m_requiredStatus; + ObjectStatusMaskType m_forbiddenStatus; + FXList *m_disguiseRevealFX; + FXList *m_disguiseFX; + Real m_stealthSpeed; + Real m_friendlyOpacityMin; + Real m_friendlyOpacityMax; + Real m_revealDistanceFromTarget; + UnsignedInt m_disguiseTransitionFrames; + UnsignedInt m_disguiseRevealTransitionFrames; + UnsignedInt m_pulseFrames; + UnsignedInt m_stealthDelay; + UnsignedInt m_stealthLevel; + UnsignedInt m_blackMarketCheckFrames; + EvaMessage m_enemyDetectionEvaEvent; + EvaMessage m_ownDetectionEvaEvent; + Bool m_innateStealth; + Bool m_orderIdleEnemiesToAttackMeUponReveal; + Bool m_teamDisguised; + Bool m_useRiderStealth; + Bool m_grantedBySpecialPower; + + StealthUpdateModuleData(); + static void buildFieldParse(MultiIniFieldParse& p); + +}; + +//------------------------------------------------------------------------------------------------- +class StealthUpdate : public UpdateModule +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( StealthUpdate, "StealthUpdate" ) + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( StealthUpdate, StealthUpdateModuleData ); + +public: + + StealthUpdate( Thing *thing, const ModuleData* moduleData ); + // virtual destructor prototype provided by memory pool declaration + + + virtual StealthUpdate* getStealth() { return this; } + + + virtual UpdateSleepTime update(); + + //Still gets called, even if held -ML + virtual DisabledMaskType getDisabledTypesToProcess() const { return MAKE_DISABLED_MASK( DISABLED_HELD ); } + + // ??? ugh + Bool isDisguised() const { return m_disguiseAsTemplate != NULL; } + Int getDisguisedPlayerIndex() const { return m_disguiseAsPlayerIndex; } + const ThingTemplate *getDisguisedTemplate() { return m_disguiseAsTemplate; } + void markAsDetected( UnsignedInt numFrames = 0 ); + void disguiseAsObject( const Object *target ); //wrapper function for ease. + Real getFriendlyOpacity() const; + UnsignedInt getStealthDelay() const { return getStealthUpdateModuleData()->m_stealthDelay; } + UnsignedInt getStealthLevel() const { return getStealthUpdateModuleData()->m_stealthLevel; } + EvaMessage getEnemyDetectionEvaEvent() const { return getStealthUpdateModuleData()->m_enemyDetectionEvaEvent; } + EvaMessage getOwnDetectionEvaEvent() const { return getStealthUpdateModuleData()->m_ownDetectionEvaEvent; } + Bool getOrderIdleEnemiesToAttackMeUponReveal() const { return getStealthUpdateModuleData()->m_orderIdleEnemiesToAttackMeUponReveal; } + Object* calcStealthOwner(); //Is it me that can stealth or is it my rider? + Bool allowedToStealth( Object *stealthOwner ) const; + void receiveGrant( Bool active = TRUE, UnsignedInt frames = 0 ); + + Bool isGrantedBySpecialPower( void ) { return getStealthUpdateModuleData()->m_grantedBySpecialPower; } + Bool isTemporaryGrant() { return m_framesGranted > 0; } + +protected: + + StealthLookType calcStealthedStatusForPlayer(const Object* obj, const Player* player); + Bool canDisguise() const { return getStealthUpdateModuleData()->m_teamDisguised; } + Real getRevealDistanceFromTarget() const { return getStealthUpdateModuleData()->m_revealDistanceFromTarget; } + void hintDetectableWhileUnstealthed( void ) ; + + void changeVisualDisguise(); + + UpdateSleepTime calcSleepTime() const; + +private: + UnsignedInt m_stealthAllowedFrame; + UnsignedInt m_detectionExpiresFrame; + mutable UnsignedInt m_nextBlackMarketCheckFrame; + Bool m_enabled; + + Real m_pulsePhaseRate; + Real m_pulsePhase; + + //Disguise only members + Int m_disguiseAsPlayerIndex; //The player team we are wanting to disguise as (might not actually be disguised yet). + const ThingTemplate *m_disguiseAsTemplate; //The disguise template (might not actually be using it yet) + UnsignedInt m_disguiseTransitionFrames; //How many frames are left before transition is complete. + Bool m_disguiseHalfpointReached; //In the middle of the transition, we will switch drawables! + Bool m_transitioningToDisguise; //Set when we are disguising -- clear when we're transitioning out of. + Bool m_disguised; //We're disguised as far as other players are concerned. + UnsignedInt m_framesGranted; //0 means forever... everything else is number of frames before stealth lost. + + // runtime xfer members (does not need saving) + Bool m_xferRestoreDisguise; //Tells us we need to restore our disguise + WeaponSetType m_requiresWeaponSetType; + +}; + + +#endif + From 8498901c40b57a7192647d973cc018b0fe9e1f2e Mon Sep 17 00:00:00 2001 From: Andi Date: Sun, 8 Jun 2025 19:43:07 +0200 Subject: [PATCH 21/42] added ammo pips thin style --- .../GameEngine/Include/Common/ThingTemplate.h | 2 + .../GameEngine/Include/GameClient/Drawable.h | 4 ++ .../GameEngine/Source/GameClient/Drawable.cpp | 37 ++++++++++++++++++- 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/ThingTemplate.h b/GeneralsMD/Code/GameEngine/Include/Common/ThingTemplate.h index f11f82a79e5..73ce1542b48 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/ThingTemplate.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/ThingTemplate.h @@ -241,6 +241,7 @@ enum AmmoPipsStyle CPP_11(: Int) AMMO_PIPS_DEFAULT = 0, ///< Default style, showing each shot in clip AMMO_PIPS_BAR, ///< Show percentage bar AMMO_PIPS_SINGLE, ///< like default, but show a single pip only (full or empty) + AMMO_PIPS_THIN, ///< like default, but half width AMMO_PIPS_NUM_TYPES // leave this last }; @@ -250,6 +251,7 @@ static const char* AmmoPipsStyleNames[] = "DEFAULT", "PERCENTAGE_BAR", "SINGLE", + "THIN", NULL }; diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h index d115447b69d..fd63e91b4fe 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h @@ -769,8 +769,12 @@ class Drawable : public Thing, static Bool s_staticImagesInited; static const Image* s_veterancyImage[LEVEL_COUNT]; + static const Image* s_fullAmmo; static const Image* s_emptyAmmo; + static const Image* s_fullAmmoThin; + static const Image* s_emptyAmmoThin; + static const Image* s_fullContainer; static const Image* s_emptyContainer; static Anim2DTemplate** s_animationTemplates; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 35a1e200c7a..fb01a2859ac 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -289,6 +289,10 @@ const Int MAX_ENABLED_MODULES = 16; /*static*/ const Image* Drawable::s_veterancyImage[LEVEL_COUNT] = { NULL }; /*static*/ const Image* Drawable::s_fullAmmo = NULL; /*static*/ const Image* Drawable::s_emptyAmmo = NULL; + +/*static*/ const Image* Drawable::s_fullAmmoThin = NULL; +/*static*/ const Image* Drawable::s_emptyAmmoThin = NULL; + /*static*/ const Image* Drawable::s_fullContainer = NULL; /*static*/ const Image* Drawable::s_emptyContainer = NULL; /*static*/ Anim2DTemplate** Drawable::s_animationTemplates = NULL; @@ -309,6 +313,10 @@ const Int MAX_ENABLED_MODULES = 16; s_fullAmmo = TheMappedImageCollection->findImageByName("SCPAmmoFull"); s_emptyAmmo = TheMappedImageCollection->findImageByName("SCPAmmoEmpty"); + + s_fullAmmoThin = TheMappedImageCollection->findImageByName("SCPAmmoThinFull"); + s_emptyAmmoThin = TheMappedImageCollection->findImageByName("SCPAmmoThinEmpty"); + s_fullContainer = TheMappedImageCollection->findImageByName("SCPPipFull"); s_emptyContainer = TheMappedImageCollection->findImageByName("SCPPipEmpty"); @@ -2981,7 +2989,6 @@ void Drawable::drawAmmo( const IRegion2D *healthBarRegion ) #else Real scale = 1.0f; #endif - Int boxWidth = REAL_TO_INT(s_emptyAmmo->getImageWidth() * scale); Int boxHeight = REAL_TO_INT(s_emptyAmmo->getImageHeight() * scale); const Int SPACING = 1; @@ -3032,6 +3039,34 @@ void Drawable::drawAmmo( const IRegion2D *healthBarRegion ) return; } + case AMMO_PIPS_THIN: + { + if (!s_fullAmmoThin || !s_emptyAmmoThin) + return; + + Real scale = 1.0f; + Int boxWidth = REAL_TO_INT(s_emptyAmmoThin->getImageWidth() * scale); + Int boxHeight = REAL_TO_INT(s_emptyAmmoThin->getImageHeight() * scale); + const Int SPACING = 0; // 1; + ICoord2D screenCenter; + Coord3D pos = *obj->getPosition(); + pos.x += TheGlobalData->m_ammoPipWorldOffset.x; + pos.y += TheGlobalData->m_ammoPipWorldOffset.y; + pos.z += TheGlobalData->m_ammoPipWorldOffset.z + obj->getGeometryInfo().getMaxHeightAbovePosition(); + if (!TheTacticalView->worldToScreen(&pos, &screenCenter)) + return; + + Real bounding = obj->getGeometryInfo().getBoundingSphereRadius() * scale; + Int posx = healthBarRegion->lo.x; + Int posy = screenCenter.y + REAL_TO_INT(TheGlobalData->m_ammoPipScreenOffset.y * bounding); + + for (Int i = 0; i < numTotal; ++i) + { + TheDisplay->drawImage(i < numFull ? s_fullAmmoThin : s_emptyAmmoThin, posx, posy + 1, posx + boxWidth, posy + 1 + boxHeight); + posx += boxWidth + SPACING; + } + return; + } case AMMO_PIPS_BAR: { if (numTotal <= 0) return; From c880e00fa45b8eb36b14a7fcecc0e525cc6f9ac7 Mon Sep 17 00:00:00 2001 From: Andi Date: Mon, 9 Jun 2025 20:21:58 +0200 Subject: [PATCH 22/42] Basic Implementations - WIP --- .../Code/GameEngine/Include/Common/KindOf.h | 480 ++++---- .../GameEngine/Include/GameLogic/Locomotor.h | 1021 +++++++++-------- .../Include/GameLogic/Module/AIUpdate.h | 2 + .../Source/Common/System/KindOf.cpp | 29 + .../Source/Common/System/MemoryInit.cpp | 2 + .../Source/GameLogic/Object/Locomotor.cpp | 2 +- .../Object/Update/AIUpdate/JetAIUpdate.cpp | 349 +++++- 7 files changed, 1120 insertions(+), 765 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h b/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h index 15de5d358aa..00346f5d8dd 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h @@ -1,224 +1,256 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: KindOf.h ////////////////////////////////////////////////////////////////////////// -// Author: Steven Johnson, Dec 2001 -// Desc: -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __KINDOF_H_ -#define __KINDOF_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "Lib/BaseType.h" -#include "Common/BitFlags.h" -#include "Common/BitFlagsIO.h" - -//------------------------------------------------------------------------------------------------- -/** Kind of flags for determining groups of things that belong together - * NOTE: You *MUST* keep this in the same order as the KindOfNames[] below */ -//------------------------------------------------------------------------------------------------- -enum KindOfType CPP_11(: Int) -{ - KINDOF_INVALID = -1, - KINDOF_FIRST = 0, - KINDOF_OBSTACLE = KINDOF_FIRST, ///< an obstacle to land-based pathfinders - KINDOF_SELECTABLE, ///< Actually means MOUSE-INTERACTABLE (doesn't mean you can select it!) - KINDOF_IMMOBILE, ///< fixed in location - KINDOF_CAN_ATTACK, ///< can attack - KINDOF_STICK_TO_TERRAIN_SLOPE, ///< should be stuck at ground level, aligned to terrain slope. requires that IMMOBILE bit is also set. - KINDOF_CAN_CAST_REFLECTIONS, ///< can cast reflections in water - KINDOF_SHRUBBERY, ///< tree, bush, etc. - KINDOF_STRUCTURE, ///< structure of some sort (buildable or not) - KINDOF_INFANTRY, ///< unit like soldier etc - KINDOF_VEHICLE, ///< unit like tank, jeep, plane, helicopter, etc. - KINDOF_AIRCRAFT, ///< unit like plane, helicopter, etc., that is predominantly a flyer. (hovercraft are NOT aircraft) - KINDOF_HUGE_VEHICLE, ///< unit that is, technically, a vehicle, but WAY larger than normal (eg, Overlord) - KINDOF_DOZER, ///< a dozer - KINDOF_HARVESTER, ///< a harvester - KINDOF_COMMANDCENTER, ///< a command center -#ifdef ALLOW_SURRENDER - KINDOF_PRISON, ///< a prison detention center kind of thing - KINDOF_COLLECTS_PRISON_BOUNTY, ///< when prisoners are delivered to these, the player gets money - KINDOF_POW_TRUCK, ///< a pow truck can pick up and return prisoners -#endif - KINDOF_LINEBUILD, ///< wall-type thing that is built in a line - KINDOF_SALVAGER, ///< something that can create and use Salvage Crates - KINDOF_WEAPON_SALVAGER, ///< subset of salvager that can get weapon upgrades from salvage - KINDOF_TRANSPORT, ///< a true transport (has TransportContain) - KINDOF_BRIDGE, ///< a Bridge. (special structure) - KINDOF_LANDMARK_BRIDGE, ///< a landmark bridge (special bridge that isn't resizable) - KINDOF_BRIDGE_TOWER, ///< a bridge tower that we can target for bridge destruction - KINDOF_PROJECTILE, ///< Instead of being a ground or air unit, this object is special - KINDOF_PRELOAD, ///< all model data will be preloaded even if not on map - KINDOF_NO_GARRISON, ///< unit may not garrison bldgs, even if infantry bit is set - KINDOF_WAVEGUIDE, ///< water wave object - KINDOF_WAVE_EFFECT, ///< wave effect point - KINDOF_NO_COLLIDE, ///< Never collide with or be collided with - KINDOF_REPAIR_PAD, ///< is a repair pad object that can repair other machines - KINDOF_HEAL_PAD, ///< is a heal pad object that can heal flesh and bone units - KINDOF_STEALTH_GARRISON, /** enemy teams can't tell that unit is in building.. and if they - garrison that building, they stealth unit will eject. */ - KINDOF_CASH_GENERATOR, ///< used to check if the unit generates cash... checked by cash hackers and whatever else comes up - KINDOF_DRAWABLE_ONLY, ///< template is used only to create drawables (not Objects) - KINDOF_MP_COUNT_FOR_VICTORY, ///< If a player loses all his buildings that have this kindof in a multiplayer game, he loses. - KINDOF_REBUILD_HOLE, ///< a GLA rebuild hole - KINDOF_SCORE, ///< Object counts for Multiplayer scores, and short-game calculations (for buildings) - KINDOF_SCORE_CREATE, ///< Object only counts for multiplayer score for creation. - KINDOF_SCORE_DESTROY, ///< Object only counts for multiplayer score for destruction. - KINDOF_NO_HEAL_ICON, ///< do not ever display healing icons on these objects - KINDOF_CAN_RAPPEL, ///< can rappel. duh. - KINDOF_PARACHUTABLE, ///< parachutable object -#ifdef ALLOW_SURRENDER - KINDOF_CAN_SURRENDER, ///< object that can surrender -#endif - KINDOF_CAN_BE_REPULSED, ///< object that runs away from a repulsor object. - KINDOF_MOB_NEXUS, ///< object that cooyrdinates the members of a mob (i.e. GLAInfantryAngryMob) - KINDOF_IGNORED_IN_GUI, ///< object that is the members of a mob (i.e. GLAInfantryAngryMob) - KINDOF_CRATE, ///< a bonus crate - KINDOF_CAPTURABLE, ///< is "capturable" even if not an enemy (should generally be used only for structures, eg, Tech bldgs) - KINDOF_CLEARED_BY_BUILD, ///< is auto-cleared from the map when built over via construction - KINDOF_SMALL_MISSILE, ///< Missile object: ONLY USED FOR ANTI-MISSILE TARGETTING PURPOSES! Keep using PROJECTILE! - KINDOF_ALWAYS_VISIBLE, ///< is never obscured by fog of war or shroud. mostly for UI feedback objects. - KINDOF_UNATTACKABLE, ///< You cannot target this thing, it probably doesn't really exist - KINDOF_MINE, ///< a landmine. (possibly also extend to Col. Burton timed charges?) - KINDOF_CLEANUP_HAZARD, ///< radiation and bio-poison are samples of area conditions that can be cleaned up (or avoided) - KINDOF_PORTABLE_STRUCTURE, ///< Flag to identify building like subobjects an Overlord is allowed to Contain. - KINDOF_ALWAYS_SELECTABLE, ///< is never unselectable (even if effectively dead). mostly for UI feedback objects. - KINDOF_ATTACK_NEEDS_LINE_OF_SIGHT, ///< Unit has to have clear line of sight (los) to attack. - KINDOF_WALK_ON_TOP_OF_WALL, ///< Units can walk on top of a wall made of these kind of objects. - KINDOF_DEFENSIVE_WALL, ///< wall can't be driven thru, even if crusher, so pathfinder must path around it - KINDOF_FS_POWER, ///< Faction structure power building - KINDOF_FS_FACTORY, ///< Faction structure power building - KINDOF_FS_BASE_DEFENSE, ///< Faction structure base defense - KINDOF_FS_TECHNOLOGY, ///< Faction structure technology building - KINDOF_AIRCRAFT_PATH_AROUND, ///< Tall enough that aircraft need to path around this. - KINDOF_LOW_OVERLAPPABLE, ///< When overlapped, things always overlap at a 'low' height rather than our object geom - KINDOF_FORCEATTACKABLE, ///< unit is always attackable via force-attack, even if not selectable - KINDOF_AUTO_RALLYPOINT, ///< When immobile-structure-object is selected, left clicking on ground will set new rally point without requiring command button. - KINDOF_TECH_BUILDING, ///< Neutral tech building - Oil derrick, Hospital, Radio Station, Refinery. - KINDOF_POWERED, ///< This object gets the Underpowered disabled condition when its owning player has power consumption exceed supply - KINDOF_PRODUCED_AT_HELIPAD, ///< ugh... hacky fix for comanche. (srj) - KINDOF_DRONE, ///< Object drone type -- used for filtering them out of battle plan bonuses, making un-snipable, and whatever else may come up. - KINDOF_CAN_SEE_THROUGH_STRUCTURE,///< Structure does not block line of sight. - KINDOF_BALLISTIC_MISSILE, ///< Large ballistic missiles that are specifically large enough to be targetted by base defenses. - KINDOF_CLICK_THROUGH, ///< Objects with this will never be picked by mouse interactions! - KINDOF_SUPPLY_SOURCE_ON_PREVIEW,///< Any thing that we can get "supplies" from that we want to show up on the map preview - KINDOF_PARACHUTE, ///< it's a parachute - KINDOF_GARRISONABLE_UNTIL_DESTROYED, ///< Object is capable of garrisoning troops until completely destroyed. - KINDOF_BOAT, ///< It's a boat! - KINDOF_IMMUNE_TO_CAPTURE, ///< Under no circumstances can this building ever be captured. - KINDOF_HULK, ///< Hulk types so we can do special things to them via scripts or other things that may come up. - KINDOF_SHOW_PORTRAIT_WHEN_CONTROLLED, ///< Only shows portraits when controlled. - KINDOF_SPAWNS_ARE_THE_WEAPONS, ///< Evaluate the spawn slaves as this object's weapons. - KINDOF_CANNOT_BUILD_NEAR_SUPPLIES, ///< you can't be built "too close" to anything that provides supplies - KINDOF_SUPPLY_SOURCE, ///< this object provides supplies - KINDOF_REVEAL_TO_ALL, ///< this object reveals shroud for all players - KINDOF_DISGUISER, ///< This object has the ability to disguise. - KINDOF_INERT, ///< this object shouldn't be considered for any sort of interaction with any player. - KINDOF_HERO, ///< Any of the single-instance infantry, JarmenKell, BlackLotus, ColonelBurton - KINDOF_IGNORES_SELECT_ALL, ///< Too late to figure out intelligently if something should respond to a Select All command - KINDOF_DONT_AUTO_CRUSH_INFANTRY, ///< These units don't try to crush the infantry if ai. - KINDOF_CLIFF_JUMPER, ///< Can't climb cliffs, but can jump off of them. - KINDOF_FS_SUPPLY_DROPZONE, ///< A supply dropzone. - KINDOF_FS_SUPERWEAPON, ///< A superweapon structure like a nuke silo, particle uplink cannon, scudstorm. - KINDOF_FS_BLACK_MARKET, ///< Is this object a black market? - KINDOF_FS_SUPPLY_CENTER, ///< Is this object a supply center? - KINDOF_FS_STRATEGY_CENTER, ///< Is this object a strategy center? - KINDOF_MONEY_HACKER, ///< Unit that generates money from air. Needed for things that directly power them up. - KINDOF_ARMOR_SALVAGER, ///< subset of salvager that can get armor upgrades from salvage - KINDOF_REVEALS_ENEMY_PATHS, ///< like the listening outpost... when selected, any enemy drawable will draw show paths when moused over - KINDOF_BOOBY_TRAP, ///< A sticky bomb that gets set off by 5 random and unrelated events. - KINDOF_FS_FAKE, ///< Fake structure! - KINDOF_FS_INTERNET_CENTER, ///< Internet Center. - KINDOF_BLAST_CRATER, ///< deeply gouges out the terrain under object footprint - KINDOF_PROP, ///< A prop, visual only, doesn't interact with other objects (rock, street sign, inert fire hydrant) - KINDOF_OPTIMIZED_TREE, ///< An optimized, client side only tree. (The only good kind of tree. jba) - KINDOF_FS_ADVANCED_TECH, ///< Represents each faction's advanced techtree building -- strategy center, propaganda center, and palace. - KINDOF_FS_BARRACKS, ///< A barracks - KINDOF_FS_WARFACTORY, ///< A war factory or arms dealer. - KINDOF_FS_AIRFIELD, ///< An airfield. - KINDOF_AIRCRAFT_CARRIER, ///< An aircraft carrier. - KINDOF_NO_SELECT, ///< Can't select it but you can mouse over it to see it's health (drones!) - KINDOF_REJECT_UNMANNED, ///< Unit cannot enter an unmanned vehicle. - KINDOF_CANNOT_RETALIATE, ///< Unit will not retaliate if asked. - KINDOF_TECH_BASE_DEFENSE, ///< Tech Building that acts as base defence when captured - KINDOF_EMP_HARDENED, ///< Like a delivery plane (B52, B3, CargoPlane,etc.) or a SpectreGunship, which sort-of IS the weapon... - KINDOF_DEMOTRAP, ///< Added strictly only for disarming purposes. They don't act like mines which have rendering and selection implications! - KINDOF_CONSERVATIVE_BUILDING, ///< Conservative structures aren't considered part of your base for sneak attack boundary calculations... - KINDOF_IGNORE_DOCKING_BONES, ///< Structure will not look up docking bones. Patch 1.03 hack. - - KINDOF_COUNT // total number of kindofs - -}; - -typedef BitFlags KindOfMaskType; - -#define MAKE_KINDOF_MASK(k) KindOfMaskType(KindOfMaskType::kInit, (k)) - -inline Bool TEST_KINDOFMASK(const KindOfMaskType& m, KindOfType t) -{ - return m.test(t); -} - -inline Bool TEST_KINDOFMASK_ANY(const KindOfMaskType& m, const KindOfMaskType& mask) -{ - return m.anyIntersectionWith(mask); -} - -inline Bool TEST_KINDOFMASK_MULTI(const KindOfMaskType& m, const KindOfMaskType& mustBeSet, const KindOfMaskType& mustBeClear) -{ - return m.testSetAndClear(mustBeSet, mustBeClear); -} - -inline Bool KINDOFMASK_ANY_SET(const KindOfMaskType& m) -{ - return m.any(); -} - -inline void CLEAR_KINDOFMASK(KindOfMaskType& m) -{ - m.clear(); -} - -inline void SET_ALL_KINDOFMASK_BITS(KindOfMaskType& m) -{ - m.clear(); - m.flip(); -} - -inline void FLIP_KINDOFMASK(KindOfMaskType& m) -{ - m.flip(); -} - -// defined in Common/System/Kindof.cpp -extern KindOfMaskType KINDOFMASK_NONE; // inits to all zeroes -extern KindOfMaskType KINDOFMASK_FS; // Initializes all FS types for faction structures. -void initKindOfMasks(); - -#endif // __KINDOF_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: KindOf.h ////////////////////////////////////////////////////////////////////////// +// Author: Steven Johnson, Dec 2001 +// Desc: +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __KINDOF_H_ +#define __KINDOF_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Lib/BaseType.h" +#include "Common/BitFlags.h" +#include "Common/BitFlagsIO.h" + +//------------------------------------------------------------------------------------------------- +/** Kind of flags for determining groups of things that belong together + * NOTE: You *MUST* keep this in the same order as the KindOfNames[] below */ +//------------------------------------------------------------------------------------------------- +enum KindOfType CPP_11(: Int) +{ + KINDOF_INVALID = -1, + KINDOF_FIRST = 0, + KINDOF_OBSTACLE = KINDOF_FIRST, ///< an obstacle to land-based pathfinders + KINDOF_SELECTABLE, ///< Actually means MOUSE-INTERACTABLE (doesn't mean you can select it!) + KINDOF_IMMOBILE, ///< fixed in location + KINDOF_CAN_ATTACK, ///< can attack + KINDOF_STICK_TO_TERRAIN_SLOPE, ///< should be stuck at ground level, aligned to terrain slope. requires that IMMOBILE bit is also set. + KINDOF_CAN_CAST_REFLECTIONS, ///< can cast reflections in water + KINDOF_SHRUBBERY, ///< tree, bush, etc. + KINDOF_STRUCTURE, ///< structure of some sort (buildable or not) + KINDOF_INFANTRY, ///< unit like soldier etc + KINDOF_VEHICLE, ///< unit like tank, jeep, plane, helicopter, etc. + KINDOF_AIRCRAFT, ///< unit like plane, helicopter, etc., that is predominantly a flyer. (hovercraft are NOT aircraft) + KINDOF_HUGE_VEHICLE, ///< unit that is, technically, a vehicle, but WAY larger than normal (eg, Overlord) + KINDOF_DOZER, ///< a dozer + KINDOF_HARVESTER, ///< a harvester + KINDOF_COMMANDCENTER, ///< a command center +#ifdef ALLOW_SURRENDER + KINDOF_PRISON, ///< a prison detention center kind of thing + KINDOF_COLLECTS_PRISON_BOUNTY, ///< when prisoners are delivered to these, the player gets money + KINDOF_POW_TRUCK, ///< a pow truck can pick up and return prisoners +#endif + KINDOF_LINEBUILD, ///< wall-type thing that is built in a line + KINDOF_SALVAGER, ///< something that can create and use Salvage Crates + KINDOF_WEAPON_SALVAGER, ///< subset of salvager that can get weapon upgrades from salvage + KINDOF_TRANSPORT, ///< a true transport (has TransportContain) + KINDOF_BRIDGE, ///< a Bridge. (special structure) + KINDOF_LANDMARK_BRIDGE, ///< a landmark bridge (special bridge that isn't resizable) + KINDOF_BRIDGE_TOWER, ///< a bridge tower that we can target for bridge destruction + KINDOF_PROJECTILE, ///< Instead of being a ground or air unit, this object is special + KINDOF_PRELOAD, ///< all model data will be preloaded even if not on map + KINDOF_NO_GARRISON, ///< unit may not garrison bldgs, even if infantry bit is set + KINDOF_WAVEGUIDE, ///< water wave object + KINDOF_WAVE_EFFECT, ///< wave effect point + KINDOF_NO_COLLIDE, ///< Never collide with or be collided with + KINDOF_REPAIR_PAD, ///< is a repair pad object that can repair other machines + KINDOF_HEAL_PAD, ///< is a heal pad object that can heal flesh and bone units + KINDOF_STEALTH_GARRISON, /** enemy teams can't tell that unit is in building.. and if they + garrison that building, they stealth unit will eject. */ + KINDOF_CASH_GENERATOR, ///< used to check if the unit generates cash... checked by cash hackers and whatever else comes up + KINDOF_DRAWABLE_ONLY, ///< template is used only to create drawables (not Objects) + KINDOF_MP_COUNT_FOR_VICTORY, ///< If a player loses all his buildings that have this kindof in a multiplayer game, he loses. + KINDOF_REBUILD_HOLE, ///< a GLA rebuild hole + KINDOF_SCORE, ///< Object counts for Multiplayer scores, and short-game calculations (for buildings) + KINDOF_SCORE_CREATE, ///< Object only counts for multiplayer score for creation. + KINDOF_SCORE_DESTROY, ///< Object only counts for multiplayer score for destruction. + KINDOF_NO_HEAL_ICON, ///< do not ever display healing icons on these objects + KINDOF_CAN_RAPPEL, ///< can rappel. duh. + KINDOF_PARACHUTABLE, ///< parachutable object +#ifdef ALLOW_SURRENDER + KINDOF_CAN_SURRENDER, ///< object that can surrender +#endif + KINDOF_CAN_BE_REPULSED, ///< object that runs away from a repulsor object. + KINDOF_MOB_NEXUS, ///< object that cooyrdinates the members of a mob (i.e. GLAInfantryAngryMob) + KINDOF_IGNORED_IN_GUI, ///< object that is the members of a mob (i.e. GLAInfantryAngryMob) + KINDOF_CRATE, ///< a bonus crate + KINDOF_CAPTURABLE, ///< is "capturable" even if not an enemy (should generally be used only for structures, eg, Tech bldgs) + KINDOF_CLEARED_BY_BUILD, ///< is auto-cleared from the map when built over via construction + KINDOF_SMALL_MISSILE, ///< Missile object: ONLY USED FOR ANTI-MISSILE TARGETTING PURPOSES! Keep using PROJECTILE! + KINDOF_ALWAYS_VISIBLE, ///< is never obscured by fog of war or shroud. mostly for UI feedback objects. + KINDOF_UNATTACKABLE, ///< You cannot target this thing, it probably doesn't really exist + KINDOF_MINE, ///< a landmine. (possibly also extend to Col. Burton timed charges?) + KINDOF_CLEANUP_HAZARD, ///< radiation and bio-poison are samples of area conditions that can be cleaned up (or avoided) + KINDOF_PORTABLE_STRUCTURE, ///< Flag to identify building like subobjects an Overlord is allowed to Contain. + KINDOF_ALWAYS_SELECTABLE, ///< is never unselectable (even if effectively dead). mostly for UI feedback objects. + KINDOF_ATTACK_NEEDS_LINE_OF_SIGHT, ///< Unit has to have clear line of sight (los) to attack. + KINDOF_WALK_ON_TOP_OF_WALL, ///< Units can walk on top of a wall made of these kind of objects. + KINDOF_DEFENSIVE_WALL, ///< wall can't be driven thru, even if crusher, so pathfinder must path around it + KINDOF_FS_POWER, ///< Faction structure power building + KINDOF_FS_FACTORY, ///< Faction structure power building + KINDOF_FS_BASE_DEFENSE, ///< Faction structure base defense + KINDOF_FS_TECHNOLOGY, ///< Faction structure technology building + KINDOF_AIRCRAFT_PATH_AROUND, ///< Tall enough that aircraft need to path around this. + KINDOF_LOW_OVERLAPPABLE, ///< When overlapped, things always overlap at a 'low' height rather than our object geom + KINDOF_FORCEATTACKABLE, ///< unit is always attackable via force-attack, even if not selectable + KINDOF_AUTO_RALLYPOINT, ///< When immobile-structure-object is selected, left clicking on ground will set new rally point without requiring command button. + KINDOF_TECH_BUILDING, ///< Neutral tech building - Oil derrick, Hospital, Radio Station, Refinery. + KINDOF_POWERED, ///< This object gets the Underpowered disabled condition when its owning player has power consumption exceed supply + KINDOF_PRODUCED_AT_HELIPAD, ///< ugh... hacky fix for comanche. (srj) + KINDOF_DRONE, ///< Object drone type -- used for filtering them out of battle plan bonuses, making un-snipable, and whatever else may come up. + KINDOF_CAN_SEE_THROUGH_STRUCTURE,///< Structure does not block line of sight. + KINDOF_BALLISTIC_MISSILE, ///< Large ballistic missiles that are specifically large enough to be targetted by base defenses. + KINDOF_CLICK_THROUGH, ///< Objects with this will never be picked by mouse interactions! + KINDOF_SUPPLY_SOURCE_ON_PREVIEW,///< Any thing that we can get "supplies" from that we want to show up on the map preview + KINDOF_PARACHUTE, ///< it's a parachute + KINDOF_GARRISONABLE_UNTIL_DESTROYED, ///< Object is capable of garrisoning troops until completely destroyed. + KINDOF_BOAT, ///< It's a boat! + KINDOF_IMMUNE_TO_CAPTURE, ///< Under no circumstances can this building ever be captured. + KINDOF_HULK, ///< Hulk types so we can do special things to them via scripts or other things that may come up. + KINDOF_SHOW_PORTRAIT_WHEN_CONTROLLED, ///< Only shows portraits when controlled. + KINDOF_SPAWNS_ARE_THE_WEAPONS, ///< Evaluate the spawn slaves as this object's weapons. + KINDOF_CANNOT_BUILD_NEAR_SUPPLIES, ///< you can't be built "too close" to anything that provides supplies + KINDOF_SUPPLY_SOURCE, ///< this object provides supplies + KINDOF_REVEAL_TO_ALL, ///< this object reveals shroud for all players + KINDOF_DISGUISER, ///< This object has the ability to disguise. + KINDOF_INERT, ///< this object shouldn't be considered for any sort of interaction with any player. + KINDOF_HERO, ///< Any of the single-instance infantry, JarmenKell, BlackLotus, ColonelBurton + KINDOF_IGNORES_SELECT_ALL, ///< Too late to figure out intelligently if something should respond to a Select All command + KINDOF_DONT_AUTO_CRUSH_INFANTRY, ///< These units don't try to crush the infantry if ai. + KINDOF_CLIFF_JUMPER, ///< Can't climb cliffs, but can jump off of them. + KINDOF_FS_SUPPLY_DROPZONE, ///< A supply dropzone. + KINDOF_FS_SUPERWEAPON, ///< A superweapon structure like a nuke silo, particle uplink cannon, scudstorm. + KINDOF_FS_BLACK_MARKET, ///< Is this object a black market? + KINDOF_FS_SUPPLY_CENTER, ///< Is this object a supply center? + KINDOF_FS_STRATEGY_CENTER, ///< Is this object a strategy center? + KINDOF_MONEY_HACKER, ///< Unit that generates money from air. Needed for things that directly power them up. + KINDOF_ARMOR_SALVAGER, ///< subset of salvager that can get armor upgrades from salvage + KINDOF_REVEALS_ENEMY_PATHS, ///< like the listening outpost... when selected, any enemy drawable will draw show paths when moused over + KINDOF_BOOBY_TRAP, ///< A sticky bomb that gets set off by 5 random and unrelated events. + KINDOF_FS_FAKE, ///< Fake structure! + KINDOF_FS_INTERNET_CENTER, ///< Internet Center. + KINDOF_BLAST_CRATER, ///< deeply gouges out the terrain under object footprint + KINDOF_PROP, ///< A prop, visual only, doesn't interact with other objects (rock, street sign, inert fire hydrant) + KINDOF_OPTIMIZED_TREE, ///< An optimized, client side only tree. (The only good kind of tree. jba) + KINDOF_FS_ADVANCED_TECH, ///< Represents each faction's advanced techtree building -- strategy center, propaganda center, and palace. + KINDOF_FS_BARRACKS, ///< A barracks + KINDOF_FS_WARFACTORY, ///< A war factory or arms dealer. + KINDOF_FS_AIRFIELD, ///< An airfield. + KINDOF_AIRCRAFT_CARRIER, ///< An aircraft carrier. + KINDOF_NO_SELECT, ///< Can't select it but you can mouse over it to see it's health (drones!) + KINDOF_REJECT_UNMANNED, ///< Unit cannot enter an unmanned vehicle. + KINDOF_CANNOT_RETALIATE, ///< Unit will not retaliate if asked. + KINDOF_TECH_BASE_DEFENSE, ///< Tech Building that acts as base defence when captured + KINDOF_EMP_HARDENED, ///< Like a delivery plane (B52, B3, CargoPlane,etc.) or a SpectreGunship, which sort-of IS the weapon... + KINDOF_DEMOTRAP, ///< Added strictly only for disarming purposes. They don't act like mines which have rendering and selection implications! + KINDOF_CONSERVATIVE_BUILDING, ///< Conservative structures aren't considered part of your base for sneak attack boundary calculations... + KINDOF_IGNORE_DOCKING_BONES, ///< Structure will not look up docking bones. Patch 1.03 hack. + + // NEW KINDOFs + + KINDOF_VTOL, + KINDOF_LARGE_AIRCRAFT, + KINDOF_MEDIUM_AIRCRAFT, + KINDOF_SMALL_AIRCRAFT, + KINDOF_ARTILLERY, + KINDOF_HEAVY_ARTILLERY, + KINDOF_ANTI_AIR, + KINDOF_SCOUT, + KINDOF_COMMANDO, + KINDOF_HEAVY_INFANTRY, + KINDOF_SUPERHEAVY_VEHICLE, + + KINDOF_EXTRA1, + KINDOF_EXTRA2, + KINDOF_EXTRA3, + KINDOF_EXTRA4, + KINDOF_EXTRA5, + KINDOF_EXTRA6, + KINDOF_EXTRA7, + KINDOF_EXTRA8, + KINDOF_EXTRA9, + KINDOF_EXTRA10, + KINDOF_EXTRA11, + KINDOF_EXTRA12, + KINDOF_EXTRA13, + KINDOF_EXTRA14, + KINDOF_EXTRA15, + KINDOF_EXTRA16, + + + KINDOF_COUNT // total number of kindofs + +}; + +typedef BitFlags KindOfMaskType; + +#define MAKE_KINDOF_MASK(k) KindOfMaskType(KindOfMaskType::kInit, (k)) + +inline Bool TEST_KINDOFMASK(const KindOfMaskType& m, KindOfType t) +{ + return m.test(t); +} + +inline Bool TEST_KINDOFMASK_ANY(const KindOfMaskType& m, const KindOfMaskType& mask) +{ + return m.anyIntersectionWith(mask); +} + +inline Bool TEST_KINDOFMASK_MULTI(const KindOfMaskType& m, const KindOfMaskType& mustBeSet, const KindOfMaskType& mustBeClear) +{ + return m.testSetAndClear(mustBeSet, mustBeClear); +} + +inline Bool KINDOFMASK_ANY_SET(const KindOfMaskType& m) +{ + return m.any(); +} + +inline void CLEAR_KINDOFMASK(KindOfMaskType& m) +{ + m.clear(); +} + +inline void SET_ALL_KINDOFMASK_BITS(KindOfMaskType& m) +{ + m.clear(); + m.flip(); +} + +inline void FLIP_KINDOFMASK(KindOfMaskType& m) +{ + m.flip(); +} + +// defined in Common/System/Kindof.cpp +extern KindOfMaskType KINDOFMASK_NONE; // inits to all zeroes +extern KindOfMaskType KINDOFMASK_FS; // Initializes all FS types for faction structures. +void initKindOfMasks(); + +#endif // __KINDOF_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h index af8c7f1a97a..ed131fa618c 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h @@ -1,510 +1,511 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: Locomotor.h ///////////////////////////////////////////////////////////////////////////////// -// Author: Steven Johnson, Feb 2002 -// Desc: Locomotor Descriptions -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __Locomotor_H_ -#define __Locomotor_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "Common/NameKeyGenerator.h" -#include "Common/Override.h" -#include "Common/Snapshot.h" -#include "GameLogic/Damage.h" -#include "GameLogic/LocomotorSet.h" - -// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// -class Locomotor; -class LocomotorTemplate; -class INI; -class PhysicsBehavior; -enum BodyDamageType CPP_11(: Int); -enum PhysicsTurningType CPP_11(: Int); - -// if we ever re-enable jets circling for landing, we need this. so keep in around just in case. (srj) -#define NO_CIRCLE_FOR_LANDING - -//------------------------------------------------------------------------------------------------- -enum LocomotorAppearance CPP_11(: Int) -{ - LOCO_LEGS_TWO, - LOCO_WHEELS_FOUR, - LOCO_TREADS, - LOCO_HOVER, - LOCO_THRUST, - LOCO_WINGS, - LOCO_CLIMBER, // human climber - backs down cliffs. - LOCO_OTHER, - LOCO_MOTORCYCLE -}; - -enum LocomotorPriority CPP_11(: Int) -{ - LOCO_MOVES_BACK=0, // In a group, this one moves toward the back - LOCO_MOVES_MIDDLE=1, // In a group, this one stays in the middle - LOCO_MOVES_FRONT=2 // In a group, this one moves toward the front of the group -}; - -#ifdef DEFINE_LOCO_APPEARANCE_NAMES -static const char *TheLocomotorAppearanceNames[] = -{ - "TWO_LEGS", - "FOUR_WHEELS", - "TREADS", - "HOVER", - "THRUST", - "WINGS", - "CLIMBER", - "OTHER", - "MOTORCYCLE", - - NULL -}; -#endif - -//------------------------------------------------------------------------------------------------- -enum LocomotorBehaviorZ CPP_11(: Int) -{ - Z_NO_Z_MOTIVE_FORCE, // does whatever physics tells it, but has no z-force of its own. - Z_SEA_LEVEL, // keep at surface-of-water level - Z_SURFACE_RELATIVE_HEIGHT, // try to follow a specific height relative to terrain/water height - Z_ABSOLUTE_HEIGHT, // try follow a specific height regardless of terrain/water height - Z_FIXED_SURFACE_RELATIVE_HEIGHT, // stays fixed at surface-rel height, regardless of physics - Z_FIXED_ABSOLUTE_HEIGHT, // stays fixed at absolute height, regardless of physics - Z_RELATIVE_TO_GROUND_AND_BUILDINGS, // stays fixed at surface-rel height including buildings, regardless of physics - Z_SMOOTH_RELATIVE_TO_HIGHEST_LAYER // try to follow a height relative to the highest layer. -}; - -#ifdef DEFINE_LOCO_Z_NAMES -static const char *TheLocomotorBehaviorZNames[] = -{ - "NO_Z_MOTIVE_FORCE", - "SEA_LEVEL", - "SURFACE_RELATIVE_HEIGHT", - "ABSOLUTE_HEIGHT", - "FIXED_SURFACE_RELATIVE_HEIGHT", - "FIXED_ABSOLUTE_HEIGHT", - "FIXED_RELATIVE_TO_GROUND_AND_BUILDINGS", - "RELATIVE_TO_HIGHEST_LAYER", - - NULL -}; -#endif - -//------------------------------------------------------------------------------------------------- -class LocomotorTemplate : public Overridable -{ - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( LocomotorTemplate, "LocomotorTemplate" ) - friend class Locomotor; - -public: - - LocomotorTemplate(); - - /// field table for loading the values from an INI - const FieldParse* getFieldParse() const; - - void friend_setName(const AsciiString& n) { m_name = n; } - - void validate(); - -protected: - - -private: - /** - Units check: - - -- Velocity: dist/frame - -- Acceleration: dist/(frame*frame) - -- Forces: (mass*dist)/(frame*frame) - */ - AsciiString m_name; - LocomotorSurfaceTypeMask m_surfaces; ///< flags indicating the kinds of surfaces we can use - Real m_maxSpeed; ///< max speed - Real m_maxSpeedDamaged; ///< max speed when "damaged" - Real m_minSpeed; ///< we should never brake past this - Real m_maxTurnRate; ///< max rate at which we can turn, in rads/frame - Real m_maxTurnRateDamaged; ///< max turn rate when "damaged" - Real m_acceleration; ///< max acceleration - Real m_accelerationDamaged; ///< max acceleration when damaged - Real m_lift; ///< max lifting acceleration (flying objects only) - Real m_liftDamaged; ///< max lift when damaged - Real m_braking; ///< max braking (deceleration) - Real m_minTurnSpeed; ///< we must be going >= this speed in order to turn - Real m_preferredHeight; ///< our preferred height (if flying) - Real m_preferredHeightDamping; ///< how aggressively to adjust to preferred height: 1.0 = very much so, 0.1 = gradually, etc - Real m_circlingRadius; ///< for flying things, the radius at which they circle their "maintain" destination. (pos = cw, neg = ccw, 0 = smallest possible) - Real m_speedLimitZ; ///< try to avoid going up or down at more than this speed, if possible - Real m_extra2DFriction; ///< extra 2dfriction to apply (via Physics) - Real m_maxThrustAngle; ///< THRUST locos only: how much we deflect our thrust angle - LocomotorBehaviorZ m_behaviorZ; ///< z-axis behavior - LocomotorAppearance m_appearance; ///< how we should diddle the Drawable to imitate this motion - LocomotorPriority m_movePriority; ///< Where we move - front, middle, back. - - Real m_accelPitchLimit; ///< Maximum amount we will pitch up under acceleration (including recoil.) - Real m_decelPitchLimit; ///< Maximum amount we will pitch down under deceleration (including recoil.) - Real m_bounceKick; ///< How much simulating rough terrain "bounces" a wheel up. - Real m_pitchStiffness; ///< How stiff the springs are forward & back. - Real m_rollStiffness; ///< How stiff the springs are side to side. - Real m_pitchDamping; ///< How good the shock absorbers are. - Real m_rollDamping; ///< How good the shock absorbers are. - Real m_pitchByZVelCoef; ///< How much we pitch in response to z-speed. - Real m_thrustRoll; ///< Thrust roll around X axis - Real m_wobbleRate; ///< how fast thrust things "wobble" - Real m_minWobble; ///< how much thrust things "wobble" - Real m_maxWobble; ///< how much thrust things "wobble" - Real m_forwardVelCoef; ///< How much we pitch in response to speed. - Real m_lateralVelCoef; ///< How much we roll in response to speed. - Real m_forwardAccelCoef; ///< How much we pitch in response to acceleration. - Real m_lateralAccelCoef; ///< How much we roll in response to acceleration. - Real m_uniformAxialDamping; ///< For Attenuating the pitch and roll rates - Real m_turnPivotOffset; ///< should we pivot around noncenter? (-1.0 = rear, 0.0 = center, 1.0 = front) - Int m_airborneTargetingHeight; ///< The height transition at witch I should mark myself as a AA target. - - Real m_closeEnoughDist; ///< How close we have to approach the end of a path before stopping - Bool m_isCloseEnoughDist3D; ///< And is that calculation 3D, for very rare cases that need to move straight down. - Real m_ultraAccurateSlideIntoPlaceFactor; ///< how much we can fudge turning when ultra-accurate - - Bool m_locomotorWorksWhenDead; ///< should locomotor continue working even when object is "dead"? - Bool m_allowMotiveForceWhileAirborne; ///< can we apply motive when airborne? - Bool m_apply2DFrictionWhenAirborne; // apply "2d friction" even when airborne... useful for realistic-looking movement - Bool m_downhillOnly; // pinewood derby, moves only by gravity pulling downhill - Bool m_stickToGround; // if true, can't leave ground - Bool m_canMoveBackward; // if true, can move backwards. - Bool m_hasSuspension; ///< If true, calculate 4 wheel independent suspension values. - Real m_maximumWheelExtension; ///< Maximum distance wheels can move down. (negative value) - Real m_maximumWheelCompression; ///< Maximum distance wheels can move up. (positive value) - Real m_wheelTurnAngle; ///< How far the front wheels can turn. - - // Fields for wander locomotor - Real m_wanderWidthFactor; - Real m_wanderLengthFactor; - Real m_wanderAboutPointRadius; - - - Real m_rudderCorrectionDegree; - Real m_rudderCorrectionRate; - Real m_elevatorCorrectionDegree; - Real m_elevatorCorrectionRate; -}; - -typedef OVERRIDE LocomotorTemplateOverride; - -// --------------------------------------------------------- -class Locomotor : public MemoryPoolObject, public Snapshot -{ - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(Locomotor, "Locomotor" ) - - friend class LocomotorStore; - -public: - - void setPhysicsOptions(Object* obj); - - void locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalPos, - Real onPathDistToGoal, Real desiredSpeed, Bool *blocked); - void locoUpdate_moveTowardsAngle(Object* obj, Real angle); - /** - Kill any current (2D) velocity (but stay at current position, or as close as possible) - - return true if we can maintain the position without being called every frame (eg, we are - resting on the ground), false if not (eg, we are hovering or circling) - */ - Bool locoUpdate_maintainCurrentPosition(Object* obj); - - Real getMaxSpeedForCondition(BodyDamageType condition) const; ///< get max speed given condition - Real getMaxTurnRate(BodyDamageType condition) const; ///< get max turning rate given condition - Real getMaxAcceleration(BodyDamageType condition) const; ///< get acceleration given condition - Real getMaxLift(BodyDamageType condition) const; ///< get acceleration given condition - Real getBraking() const; ///< get braking given condition - - inline Real getPreferredHeight() const { return m_preferredHeight;} ///< Just return preferredheight, no damage consideration - inline void restorePreferredHeightFromTemplate() { m_preferredHeight = m_template->m_preferredHeight; }; - inline Real getPreferredHeightDamping() const { return m_preferredHeightDamping;} - inline LocomotorAppearance getAppearance() const { return m_template->m_appearance; } - inline LocomotorPriority getMovePriority() const { return m_template->m_movePriority; } - inline LocomotorSurfaceTypeMask getLegalSurfaces() const { return m_template->m_surfaces; } - - inline AsciiString getTemplateName() const { return m_template->m_name;} - inline Real getMinSpeed() const { return m_template->m_minSpeed;} - inline Real getAccelPitchLimit() const { return m_template->m_accelPitchLimit;} ///< Maximum amount we will pitch up or down under acceleration (including recoil.) - inline Real getDecelPitchLimit() const { return m_template->m_decelPitchLimit;} ///< Maximum amount we will pitch down under deceleration (including recoil.) - inline Real getBounceKick() const { return m_template->m_bounceKick;} ///< How much simulating rough terrain "bounces" a wheel up. - inline Real getPitchStiffness() const { return m_template->m_pitchStiffness;} ///< How stiff the springs are forward & back. - inline Real getRollStiffness() const { return m_template->m_rollStiffness;} ///< How stiff the springs are side to side. - inline Real getPitchDamping() const { return m_template->m_pitchDamping;} ///< How good the shock absorbers are. - inline Real getRollDamping() const { return m_template->m_rollDamping;} ///< How good the shock absorbers are. - inline Real getPitchByZVelCoef() const { return m_template->m_pitchByZVelCoef;} ///< How much we pitch in response to speed. - inline Real getThrustRoll() const { return m_template->m_thrustRoll; } ///< Thrust roll - inline Real getWobbleRate() const { return m_template->m_wobbleRate; } ///< how fast thrust things "wobble" - inline Real getMaxWobble() const { return m_template->m_maxWobble; } ///< how much thrust things "wobble" - inline Real getMinWobble() const { return m_template->m_minWobble; } ///< how much thrust things "wobble" - - inline Real getForwardVelCoef() const { return m_template->m_forwardVelCoef;} ///< How much we pitch in response to speed. - inline Real getLateralVelCoef() const { return m_template->m_lateralVelCoef;} ///< How much we roll in response to speed. - inline Real getForwardAccelCoef() const { return m_template->m_forwardAccelCoef;} ///< How much we pitch in response to acceleration. - inline Real getLateralAccelCoef() const { return m_template->m_lateralAccelCoef;} ///< How much we roll in response to acceleration. - inline Real getUniformAxialDamping() const { return m_template->m_uniformAxialDamping;} ///< How much we roll in response to acceleration. - inline Real getTurnPivotOffset() const { return m_template->m_turnPivotOffset;} - inline Bool getApply2DFrictionWhenAirborne() const { return m_template->m_apply2DFrictionWhenAirborne; } - inline Bool getIsDownhillOnly() const { return m_template->m_downhillOnly; } - inline Bool getAllowMotiveForceWhileAirborne() const { return m_template->m_allowMotiveForceWhileAirborne; } - inline Int getAirborneTargetingHeight() const { return m_template->m_airborneTargetingHeight; } - inline Bool getLocomotorWorksWhenDead() const { return m_template->m_locomotorWorksWhenDead; } - inline Bool getStickToGround() const { return m_template->m_stickToGround; } - inline Real getCloseEnoughDist() const { return m_closeEnoughDist; } - inline Bool isCloseEnoughDist3D() const { return getFlag(IS_CLOSE_ENOUGH_DIST_3D); } - inline Bool hasSuspension() const {return m_template->m_hasSuspension;} - inline Bool canMoveBackwards() const {return m_template->m_canMoveBackward;} - inline Real getMaxWheelExtension() const {return m_template->m_maximumWheelExtension;} - inline Real getMaxWheelCompression() const {return m_template->m_maximumWheelCompression;} - inline Real getWheelTurnAngle() const {return m_template->m_wheelTurnAngle;} - - - inline Real getRudderCorrectionDegree() const { return m_template->m_rudderCorrectionDegree;} ///< How much we roll in response to acceleration. - inline Real getRudderCorrectionRate() const { return m_template->m_rudderCorrectionRate;} ///< How much we roll in response to acceleration. - inline Real getElevatorCorrectionDegree() const { return m_template->m_elevatorCorrectionDegree;} ///< How much we roll in response to acceleration. - inline Real getElevatorCorrectionRate() const { return m_template->m_elevatorCorrectionRate;} ///< How much we roll in response to acceleration. - - - inline Real getWanderWidthFactor() const {return m_template->m_wanderWidthFactor;} - inline Real getWanderAboutPointRadius() const {return m_template->m_wanderAboutPointRadius;} - - Real calcMinTurnRadius(BodyDamageType condition, Real* timeToTravelThatDist) const; - - /// this is handy for doing things like forcing helicopters to crash realistically: cut their lift. - inline void setMaxLift(Real lift) { m_maxLift = lift; } - inline void setMaxSpeed(Real speed) - { - DEBUG_ASSERTCRASH(!(speed <= 0.0f && m_template->m_appearance == LOCO_THRUST), ("THRUST locos may not have zero speeds!\n")); - m_maxSpeed = speed; - } - inline void setMaxAcceleration(Real accel) { m_maxAccel = accel; } - inline void setMaxBraking(Real braking) { m_maxBraking = braking; } - inline void setMaxTurnRate(Real turn) { m_maxTurnRate = turn; } - inline void setAllowInvalidPosition(Bool allow) { setFlag(ALLOW_INVALID_POSITION, allow); } - inline void setCloseEnoughDist( Real dist ) { m_closeEnoughDist = dist; } - inline void setCloseEnoughDist3D( Bool setting ) { setFlag(IS_CLOSE_ENOUGH_DIST_3D, setting); } - inline Bool isInvalidPositionAllowed() const { return getFlag( ALLOW_INVALID_POSITION ); } - - inline void setPreferredHeight( Real height ) { m_preferredHeight = height; } - -#ifdef CIRCLE_FOR_LANDING - /** - if we are climbing/diving more than this, circle as needed rather - than just diving or climbing directly. (only useful for Winged things) - */ - inline void setAltitudeChangeThresholdForCircling(Real a) { m_circleThresh = a; } -#endif - - /** - when off (the default), things get to adjust their z-pos as their - loco says (in particular, airborne things tend to try to fly at a preferred height). - - when on, they do their best to reach the specified zpos, even if it's not at their preferred height. - this is used mainly for force missiles to swoop in on their target, and to force airplane takeoff/landing - to go smoothly. - */ - inline void setUsePreciseZPos(Bool u) { setFlag(PRECISE_Z_POS, u); } - - /** - when off (the default), units slow down as they approach their target. - - when on, units go full speed till the end, and may overshoot their target. - this is useful mainly in some weird, temporary situations where we know we are - going to follow this move with another one... or for carbombs. - */ - inline void setNoSlowDownAsApproachingDest(Bool u) { setFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST, u); } - - /** - when off (the default), units do their normal stuff. - - when on, we cheat and make very precise motion, regardless of loco settings. - this is accomplished by cranking up the unit's turning rate, friction, lift (for airborne things), - and possibly other things. This is useful mainly when doing maneuvers where precision - is VITAL, such as airplane takeoff/landing. - - For ground units, it also allows units to have a destination off of a pathfing grid. - - */ - inline void setUltraAccurate(Bool u) { setFlag(ULTRA_ACCURATE, u); } - inline Bool isUltraAccurate() const { return getFlag(ULTRA_ACCURATE); } - - inline Bool isMovingBackwards(void) const {return getFlag(MOVING_BACKWARDS);} - - void startMove(void); ///< Indicates that a move is starting, primarily to reset the donut timer. jba. - -protected: - void moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionLegsWander(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionHover(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - - void maintainCurrentPositionThrust(Object* obj, PhysicsBehavior *physics); - void maintainCurrentPositionOther(Object* obj, PhysicsBehavior *physics); - void maintainCurrentPositionLegs(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } - void maintainCurrentPositionWheels(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } - void maintainCurrentPositionTreads(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } - void maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physics); - void maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physics); - - PhysicsTurningType rotateTowardsPosition(Object* obj, const Coord3D& goalPos, Real *relAngle=NULL); - - /* - return true if we can maintain the position without being called every frame (eg, we are - resting on the ground), false if not (eg, we are hovering or circling) - */ - Bool handleBehaviorZ(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos); - PhysicsTurningType rotateObjAroundLocoPivot(Object* obj, const Coord3D& goalPos, Real maxTurnRate, Real *relAngle = NULL); - - Real getSurfaceHtAtPt(Real x, Real y); - Real calcLiftToUseAtPt(Object* obj, PhysicsBehavior *physics, Real curZ, Real surfaceAtPt, Real preferredHeight); - - Bool fixInvalidPosition(Object* obj, PhysicsBehavior *physics); - -protected: - // snapshot methods - virtual void crc( Xfer *xfer ); - virtual void xfer( Xfer *xfer ); - virtual void loadPostProcess( void ); - -protected: - - Locomotor(const LocomotorTemplate* tmpl); - - // Note, "Law of the Big Three" applies here - //Locomotor(); -- nope, we don't have a default ctor. (srj) - Locomotor(const Locomotor& that); - Locomotor& operator=(const Locomotor& that); - //~Locomotor(); - -private: - - // - // Note: these values are saved in save files, so you MUST NOT REMOVE OR CHANGE - // existing values! - // - enum LocoFlag - { - IS_BRAKING = 0, - ALLOW_INVALID_POSITION, - MAINTAIN_POS_IS_VALID, - PRECISE_Z_POS, - NO_SLOW_DOWN_AS_APPROACHING_DEST, - OVER_WATER, // To allow things to move slower/faster over water and do special effects - ULTRA_ACCURATE, - MOVING_BACKWARDS, // If we are moving backwards. - DOING_THREE_POINT_TURN, // If we are doing a 3 pt turn. - CLIMBING, // If we are in the process of climbing. - IS_CLOSE_ENOUGH_DIST_3D, - OFFSET_INCREASING - }; - - inline Bool getFlag(LocoFlag f) const { return (m_flags & (1 << f)) != 0; } - inline void setFlag(LocoFlag f, Bool b) { if (b) m_flags |= (1< > LocomotorTemplateMap; - - LocomotorTemplateMap m_locomotorTemplates; - -}; - -// EXTERNALS ////////////////////////////////////////////////////////////////////////////////////// -extern LocomotorStore *TheLocomotorStore; - -#endif // __Locomotor_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: Locomotor.h ///////////////////////////////////////////////////////////////////////////////// +// Author: Steven Johnson, Feb 2002 +// Desc: Locomotor Descriptions +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __Locomotor_H_ +#define __Locomotor_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Common/NameKeyGenerator.h" +#include "Common/Override.h" +#include "Common/Snapshot.h" +#include "GameLogic/Damage.h" +#include "GameLogic/LocomotorSet.h" + +// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// +class Locomotor; +class LocomotorTemplate; +class INI; +class PhysicsBehavior; +enum BodyDamageType CPP_11(: Int); +enum PhysicsTurningType CPP_11(: Int); + +// if we ever re-enable jets circling for landing, we need this. so keep in around just in case. (srj) +#define NO_CIRCLE_FOR_LANDING + +//------------------------------------------------------------------------------------------------- +enum LocomotorAppearance CPP_11(: Int) +{ + LOCO_LEGS_TWO, + LOCO_WHEELS_FOUR, + LOCO_TREADS, + LOCO_HOVER, + LOCO_THRUST, + LOCO_WINGS, + LOCO_CLIMBER, // human climber - backs down cliffs. + LOCO_OTHER, + LOCO_MOTORCYCLE +}; + +enum LocomotorPriority CPP_11(: Int) +{ + LOCO_MOVES_BACK=0, // In a group, this one moves toward the back + LOCO_MOVES_MIDDLE=1, // In a group, this one stays in the middle + LOCO_MOVES_FRONT=2 // In a group, this one moves toward the front of the group +}; + +#ifdef DEFINE_LOCO_APPEARANCE_NAMES +static const char *TheLocomotorAppearanceNames[] = +{ + "TWO_LEGS", + "FOUR_WHEELS", + "TREADS", + "HOVER", + "THRUST", + "WINGS", + "CLIMBER", + "OTHER", + "MOTORCYCLE", + + NULL +}; +#endif + +//------------------------------------------------------------------------------------------------- +enum LocomotorBehaviorZ CPP_11(: Int) +{ + Z_NO_Z_MOTIVE_FORCE, // does whatever physics tells it, but has no z-force of its own. + Z_SEA_LEVEL, // keep at surface-of-water level + Z_SURFACE_RELATIVE_HEIGHT, // try to follow a specific height relative to terrain/water height + Z_ABSOLUTE_HEIGHT, // try follow a specific height regardless of terrain/water height + Z_FIXED_SURFACE_RELATIVE_HEIGHT, // stays fixed at surface-rel height, regardless of physics + Z_FIXED_ABSOLUTE_HEIGHT, // stays fixed at absolute height, regardless of physics + Z_RELATIVE_TO_GROUND_AND_BUILDINGS, // stays fixed at surface-rel height including buildings, regardless of physics + Z_SMOOTH_RELATIVE_TO_HIGHEST_LAYER // try to follow a height relative to the highest layer. +}; + +#ifdef DEFINE_LOCO_Z_NAMES +static const char *TheLocomotorBehaviorZNames[] = +{ + "NO_Z_MOTIVE_FORCE", + "SEA_LEVEL", + "SURFACE_RELATIVE_HEIGHT", + "ABSOLUTE_HEIGHT", + "FIXED_SURFACE_RELATIVE_HEIGHT", + "FIXED_ABSOLUTE_HEIGHT", + "FIXED_RELATIVE_TO_GROUND_AND_BUILDINGS", + "RELATIVE_TO_HIGHEST_LAYER", + + NULL +}; +#endif + +//------------------------------------------------------------------------------------------------- +class LocomotorTemplate : public Overridable +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( LocomotorTemplate, "LocomotorTemplate" ) + friend class Locomotor; + +public: + + LocomotorTemplate(); + + /// field table for loading the values from an INI + const FieldParse* getFieldParse() const; + + void friend_setName(const AsciiString& n) { m_name = n; } + + void validate(); + +protected: + + +private: + /** + Units check: + + -- Velocity: dist/frame + -- Acceleration: dist/(frame*frame) + -- Forces: (mass*dist)/(frame*frame) + */ + AsciiString m_name; + LocomotorSurfaceTypeMask m_surfaces; ///< flags indicating the kinds of surfaces we can use + Real m_maxSpeed; ///< max speed + Real m_maxSpeedDamaged; ///< max speed when "damaged" + Real m_minSpeed; ///< we should never brake past this + Real m_maxTurnRate; ///< max rate at which we can turn, in rads/frame + Real m_maxTurnRateDamaged; ///< max turn rate when "damaged" + Real m_acceleration; ///< max acceleration + Real m_accelerationDamaged; ///< max acceleration when damaged + Real m_lift; ///< max lifting acceleration (flying objects only) + Real m_liftDamaged; ///< max lift when damaged + Real m_braking; ///< max braking (deceleration) + Real m_minTurnSpeed; ///< we must be going >= this speed in order to turn + Real m_preferredHeight; ///< our preferred height (if flying) + Real m_preferredHeightDamping; ///< how aggressively to adjust to preferred height: 1.0 = very much so, 0.1 = gradually, etc + Real m_circlingRadius; ///< for flying things, the radius at which they circle their "maintain" destination. (pos = cw, neg = ccw, 0 = smallest possible) + Real m_speedLimitZ; ///< try to avoid going up or down at more than this speed, if possible + Real m_extra2DFriction; ///< extra 2dfriction to apply (via Physics) + Real m_maxThrustAngle; ///< THRUST locos only: how much we deflect our thrust angle + LocomotorBehaviorZ m_behaviorZ; ///< z-axis behavior + LocomotorAppearance m_appearance; ///< how we should diddle the Drawable to imitate this motion + LocomotorPriority m_movePriority; ///< Where we move - front, middle, back. + + Real m_accelPitchLimit; ///< Maximum amount we will pitch up under acceleration (including recoil.) + Real m_decelPitchLimit; ///< Maximum amount we will pitch down under deceleration (including recoil.) + Real m_bounceKick; ///< How much simulating rough terrain "bounces" a wheel up. + Real m_pitchStiffness; ///< How stiff the springs are forward & back. + Real m_rollStiffness; ///< How stiff the springs are side to side. + Real m_pitchDamping; ///< How good the shock absorbers are. + Real m_rollDamping; ///< How good the shock absorbers are. + Real m_pitchByZVelCoef; ///< How much we pitch in response to z-speed. + Real m_thrustRoll; ///< Thrust roll around X axis + Real m_wobbleRate; ///< how fast thrust things "wobble" + Real m_minWobble; ///< how much thrust things "wobble" + Real m_maxWobble; ///< how much thrust things "wobble" + Real m_forwardVelCoef; ///< How much we pitch in response to speed. + Real m_lateralVelCoef; ///< How much we roll in response to speed. + Real m_forwardAccelCoef; ///< How much we pitch in response to acceleration. + Real m_lateralAccelCoef; ///< How much we roll in response to acceleration. + Real m_uniformAxialDamping; ///< For Attenuating the pitch and roll rates + Real m_turnPivotOffset; ///< should we pivot around noncenter? (-1.0 = rear, 0.0 = center, 1.0 = front) + Int m_airborneTargetingHeight; ///< The height transition at witch I should mark myself as a AA target. + + Real m_closeEnoughDist; ///< How close we have to approach the end of a path before stopping + Bool m_isCloseEnoughDist3D; ///< And is that calculation 3D, for very rare cases that need to move straight down. + Real m_ultraAccurateSlideIntoPlaceFactor; ///< how much we can fudge turning when ultra-accurate + + Bool m_locomotorWorksWhenDead; ///< should locomotor continue working even when object is "dead"? + Bool m_allowMotiveForceWhileAirborne; ///< can we apply motive when airborne? + Bool m_apply2DFrictionWhenAirborne; // apply "2d friction" even when airborne... useful for realistic-looking movement + Bool m_downhillOnly; // pinewood derby, moves only by gravity pulling downhill + Bool m_stickToGround; // if true, can't leave ground + Bool m_canMoveBackward; // if true, can move backwards. + Bool m_hasSuspension; ///< If true, calculate 4 wheel independent suspension values. + Real m_maximumWheelExtension; ///< Maximum distance wheels can move down. (negative value) + Real m_maximumWheelCompression; ///< Maximum distance wheels can move up. (positive value) + Real m_wheelTurnAngle; ///< How far the front wheels can turn. + + // Fields for wander locomotor + Real m_wanderWidthFactor; + Real m_wanderLengthFactor; + Real m_wanderAboutPointRadius; + + + Real m_rudderCorrectionDegree; + Real m_rudderCorrectionRate; + Real m_elevatorCorrectionDegree; + Real m_elevatorCorrectionRate; +}; + +typedef OVERRIDE LocomotorTemplateOverride; + +// --------------------------------------------------------- +class Locomotor : public MemoryPoolObject, public Snapshot +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(Locomotor, "Locomotor" ) + + friend class LocomotorStore; + +public: + + void setPhysicsOptions(Object* obj); + + void locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalPos, + Real onPathDistToGoal, Real desiredSpeed, Bool *blocked); + void locoUpdate_moveTowardsAngle(Object* obj, Real angle); + /** + Kill any current (2D) velocity (but stay at current position, or as close as possible) + + return true if we can maintain the position without being called every frame (eg, we are + resting on the ground), false if not (eg, we are hovering or circling) + */ + Bool locoUpdate_maintainCurrentPosition(Object* obj); + + Real getMaxSpeedForCondition(BodyDamageType condition) const; ///< get max speed given condition + Real getMaxTurnRate(BodyDamageType condition) const; ///< get max turning rate given condition + Real getMaxAcceleration(BodyDamageType condition) const; ///< get acceleration given condition + Real getMaxLift(BodyDamageType condition) const; ///< get acceleration given condition + Real getBraking() const; ///< get braking given condition + + inline Real getPreferredHeight() const { return m_preferredHeight;} ///< Just return preferredheight, no damage consideration + inline void restorePreferredHeightFromTemplate() { m_preferredHeight = m_template->m_preferredHeight; }; + inline Real getPreferredHeightDamping() const { return m_preferredHeightDamping;} + inline LocomotorAppearance getAppearance() const { return m_template->m_appearance; } + inline LocomotorPriority getMovePriority() const { return m_template->m_movePriority; } + inline LocomotorSurfaceTypeMask getLegalSurfaces() const { return m_template->m_surfaces; } + + inline AsciiString getTemplateName() const { return m_template->m_name;} + inline Real getMinSpeed() const { return m_template->m_minSpeed;} + inline Real getAccelPitchLimit() const { return m_template->m_accelPitchLimit;} ///< Maximum amount we will pitch up or down under acceleration (including recoil.) + inline Real getDecelPitchLimit() const { return m_template->m_decelPitchLimit;} ///< Maximum amount we will pitch down under deceleration (including recoil.) + inline Real getBounceKick() const { return m_template->m_bounceKick;} ///< How much simulating rough terrain "bounces" a wheel up. + inline Real getPitchStiffness() const { return m_template->m_pitchStiffness;} ///< How stiff the springs are forward & back. + inline Real getRollStiffness() const { return m_template->m_rollStiffness;} ///< How stiff the springs are side to side. + inline Real getPitchDamping() const { return m_template->m_pitchDamping;} ///< How good the shock absorbers are. + inline Real getRollDamping() const { return m_template->m_rollDamping;} ///< How good the shock absorbers are. + inline Real getPitchByZVelCoef() const { return m_template->m_pitchByZVelCoef;} ///< How much we pitch in response to speed. + inline Real getThrustRoll() const { return m_template->m_thrustRoll; } ///< Thrust roll + inline Real getWobbleRate() const { return m_template->m_wobbleRate; } ///< how fast thrust things "wobble" + inline Real getMaxWobble() const { return m_template->m_maxWobble; } ///< how much thrust things "wobble" + inline Real getMinWobble() const { return m_template->m_minWobble; } ///< how much thrust things "wobble" + + inline Real getForwardVelCoef() const { return m_template->m_forwardVelCoef;} ///< How much we pitch in response to speed. + inline Real getLateralVelCoef() const { return m_template->m_lateralVelCoef;} ///< How much we roll in response to speed. + inline Real getForwardAccelCoef() const { return m_template->m_forwardAccelCoef;} ///< How much we pitch in response to acceleration. + inline Real getLateralAccelCoef() const { return m_template->m_lateralAccelCoef;} ///< How much we roll in response to acceleration. + inline Real getUniformAxialDamping() const { return m_template->m_uniformAxialDamping;} ///< How much we roll in response to acceleration. + inline Real getTurnPivotOffset() const { return m_template->m_turnPivotOffset;} + inline Bool getApply2DFrictionWhenAirborne() const { return m_template->m_apply2DFrictionWhenAirborne; } + inline Bool getIsDownhillOnly() const { return m_template->m_downhillOnly; } + inline Bool getAllowMotiveForceWhileAirborne() const { return m_template->m_allowMotiveForceWhileAirborne; } + inline Int getAirborneTargetingHeight() const { return m_template->m_airborneTargetingHeight; } + inline Bool getLocomotorWorksWhenDead() const { return m_template->m_locomotorWorksWhenDead; } + inline Bool getStickToGround() const { return m_template->m_stickToGround; } + inline Real getCloseEnoughDist() const { return m_closeEnoughDist; } + inline Bool isCloseEnoughDist3D() const { return getFlag(IS_CLOSE_ENOUGH_DIST_3D); } + inline Bool hasSuspension() const {return m_template->m_hasSuspension;} + inline Bool canMoveBackwards() const {return m_template->m_canMoveBackward;} + inline Real getMaxWheelExtension() const {return m_template->m_maximumWheelExtension;} + inline Real getMaxWheelCompression() const {return m_template->m_maximumWheelCompression;} + inline Real getWheelTurnAngle() const {return m_template->m_wheelTurnAngle;} + + + inline Real getRudderCorrectionDegree() const { return m_template->m_rudderCorrectionDegree;} ///< How much we roll in response to acceleration. + inline Real getRudderCorrectionRate() const { return m_template->m_rudderCorrectionRate;} ///< How much we roll in response to acceleration. + inline Real getElevatorCorrectionDegree() const { return m_template->m_elevatorCorrectionDegree;} ///< How much we roll in response to acceleration. + inline Real getElevatorCorrectionRate() const { return m_template->m_elevatorCorrectionRate;} ///< How much we roll in response to acceleration. + + + inline Real getWanderWidthFactor() const {return m_template->m_wanderWidthFactor;} + inline Real getWanderAboutPointRadius() const {return m_template->m_wanderAboutPointRadius;} + + Real calcMinTurnRadius(BodyDamageType condition, Real* timeToTravelThatDist) const; + + /// this is handy for doing things like forcing helicopters to crash realistically: cut their lift. + inline void setMaxLift(Real lift) { m_maxLift = lift; } + inline void setMaxSpeed(Real speed) + { + DEBUG_ASSERTCRASH(!(speed <= 0.0f && m_template->m_appearance == LOCO_THRUST), ("THRUST locos may not have zero speeds!\n")); + m_maxSpeed = speed; + } + inline void setMaxAcceleration(Real accel) { m_maxAccel = accel; } + inline void setMaxBraking(Real braking) { m_maxBraking = braking; } + inline void setMaxTurnRate(Real turn) { m_maxTurnRate = turn; } + inline void setAllowInvalidPosition(Bool allow) { setFlag(ALLOW_INVALID_POSITION, allow); } + inline void setCloseEnoughDist( Real dist ) { m_closeEnoughDist = dist; } + inline void setCloseEnoughDist3D( Bool setting ) { setFlag(IS_CLOSE_ENOUGH_DIST_3D, setting); } + inline Bool isInvalidPositionAllowed() const { return getFlag( ALLOW_INVALID_POSITION ); } + + inline void setPreferredHeight( Real height ) { m_preferredHeight = height; } + +#ifdef CIRCLE_FOR_LANDING + /** + if we are climbing/diving more than this, circle as needed rather + than just diving or climbing directly. (only useful for Winged things) + */ + inline void setAltitudeChangeThresholdForCircling(Real a) { m_circleThresh = a; } +#endif + + /** + when off (the default), things get to adjust their z-pos as their + loco says (in particular, airborne things tend to try to fly at a preferred height). + + when on, they do their best to reach the specified zpos, even if it's not at their preferred height. + this is used mainly for force missiles to swoop in on their target, and to force airplane takeoff/landing + to go smoothly. + */ + inline void setUsePreciseZPos(Bool u) { setFlag(PRECISE_Z_POS, u); } + + /** + when off (the default), units slow down as they approach their target. + + when on, units go full speed till the end, and may overshoot their target. + this is useful mainly in some weird, temporary situations where we know we are + going to follow this move with another one... or for carbombs. + */ + inline void setNoSlowDownAsApproachingDest(Bool u) { setFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST, u); } + + /** + when off (the default), units do their normal stuff. + + when on, we cheat and make very precise motion, regardless of loco settings. + this is accomplished by cranking up the unit's turning rate, friction, lift (for airborne things), + and possibly other things. This is useful mainly when doing maneuvers where precision + is VITAL, such as airplane takeoff/landing. + + For ground units, it also allows units to have a destination off of a pathfing grid. + + */ + inline void setUltraAccurate(Bool u) { setFlag(ULTRA_ACCURATE, u); } + inline Bool isUltraAccurate() const { return getFlag(ULTRA_ACCURATE); } + + inline Bool isMovingBackwards(void) const {return getFlag(MOVING_BACKWARDS);} + + void startMove(void); ///< Indicates that a move is starting, primarily to reset the donut timer. jba. + + static Real getSurfaceHtAtPt(Real x, Real y); + +protected: + void moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionLegsWander(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionHover(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + + void maintainCurrentPositionThrust(Object* obj, PhysicsBehavior *physics); + void maintainCurrentPositionOther(Object* obj, PhysicsBehavior *physics); + void maintainCurrentPositionLegs(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } + void maintainCurrentPositionWheels(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } + void maintainCurrentPositionTreads(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } + void maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physics); + void maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physics); + + PhysicsTurningType rotateTowardsPosition(Object* obj, const Coord3D& goalPos, Real *relAngle=NULL); + + /* + return true if we can maintain the position without being called every frame (eg, we are + resting on the ground), false if not (eg, we are hovering or circling) + */ + Bool handleBehaviorZ(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos); + PhysicsTurningType rotateObjAroundLocoPivot(Object* obj, const Coord3D& goalPos, Real maxTurnRate, Real *relAngle = NULL); + + Real calcLiftToUseAtPt(Object* obj, PhysicsBehavior *physics, Real curZ, Real surfaceAtPt, Real preferredHeight); + + Bool fixInvalidPosition(Object* obj, PhysicsBehavior *physics); + +protected: + // snapshot methods + virtual void crc( Xfer *xfer ); + virtual void xfer( Xfer *xfer ); + virtual void loadPostProcess( void ); + +protected: + + Locomotor(const LocomotorTemplate* tmpl); + + // Note, "Law of the Big Three" applies here + //Locomotor(); -- nope, we don't have a default ctor. (srj) + Locomotor(const Locomotor& that); + Locomotor& operator=(const Locomotor& that); + //~Locomotor(); + +private: + + // + // Note: these values are saved in save files, so you MUST NOT REMOVE OR CHANGE + // existing values! + // + enum LocoFlag + { + IS_BRAKING = 0, + ALLOW_INVALID_POSITION, + MAINTAIN_POS_IS_VALID, + PRECISE_Z_POS, + NO_SLOW_DOWN_AS_APPROACHING_DEST, + OVER_WATER, // To allow things to move slower/faster over water and do special effects + ULTRA_ACCURATE, + MOVING_BACKWARDS, // If we are moving backwards. + DOING_THREE_POINT_TURN, // If we are doing a 3 pt turn. + CLIMBING, // If we are in the process of climbing. + IS_CLOSE_ENOUGH_DIST_3D, + OFFSET_INCREASING + }; + + inline Bool getFlag(LocoFlag f) const { return (m_flags & (1 << f)) != 0; } + inline void setFlag(LocoFlag f, Bool b) { if (b) m_flags |= (1< > LocomotorTemplateMap; + + LocomotorTemplateMap m_locomotorTemplates; + +}; + +// EXTERNALS ////////////////////////////////////////////////////////////////////////////////////// +extern LocomotorStore *TheLocomotorStore; + +#endif // __Locomotor_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h index 791bece902d..1d9b761a3b0 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h @@ -83,6 +83,7 @@ enum LocomotorSetType CPP_11(: Int) LOCOMOTORSET_TAXIING, // set used for normally-airborne items while taxiing on ground LOCOMOTORSET_SUPERSONIC, // set used for high-speed attacks LOCOMOTORSET_SLUGGISH, // set used for abnormally slow (but not damaged) speeds + LOCOMOTORSET_VTOL, // set used for VTOL aircraft to take off and land LOCOMOTORSET_COUNT ///< keep last, please }; @@ -107,6 +108,7 @@ static const char *TheLocomotorSetNames[] = "SET_TAXIING", "SET_SUPERSONIC", "SET_SLUGGISH", + "SET_VTOL", NULL }; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp index 47ef9795f91..6057df6810a 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp @@ -158,6 +158,35 @@ const char* KindOfMaskType::s_bitNameList[] = "CONSERVATIVE_BUILDING", "IGNORE_DOCKING_BONES", + "VTOL", + "LARGE_AIRCRAFT", + "MEDIUM_AIRCRAFT", + "SMALL_AIRCRAFT", + "ARTILLERY", + "HEAVY_ARTILLERY", + "ANTI_AIR", + "SCOUT", + "COMMANDO", + "HEAVY_INFANTRY", + "SUPERHEAVY_VEHICLE", + + "EXTRA1", + "EXTRA2", + "EXTRA3", + "EXTRA4", + "EXTRA5", + "EXTRA6", + "EXTRA7", + "EXTRA8", + "EXTRA9", + "EXTRA10", + "EXTRA11", + "EXTRA12", + "EXTRA13", + "EXTRA14", + "EXTRA15", + "EXTRA16", + NULL }; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp index 33e2fc9fe60..46636ca4094 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp @@ -139,6 +139,7 @@ static PoolSizeRec sizes[] = { "AIStateMachine", 600, 32 }, { "JetAIStateMachine", 64, 32 }, { "HeliAIStateMachine", 64, 32 }, + { "VtolAIStateMachine", 64, 32 }, { "AIAttackMoveStateMachine", 2048, 32 }, { "AIAttackThenIdleStateMachine", 512, 32 }, { "AttackStateMachine", 512, 32 }, @@ -488,6 +489,7 @@ static PoolSizeRec sizes[] = { "JetAwaitingRunwayState", 64, 32 }, { "JetOrHeliCirclingDeadAirfieldState", 64, 32 }, { "HeliTakeoffOrLandingState", 64, 32 }, + { "VtolTakeoffOrLandingState", 64, 32 }, { "JetOrHeliParkOrientState", 64, 32 }, { "JetOrHeliReloadAmmoState", 64, 32 }, { "SupplyTruckBusyState", 600, 32 }, diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp index c7db2121230..297d5fc1dd6 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp @@ -2050,7 +2050,7 @@ void Locomotor::moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, } //------------------------------------------------------------------------------------------------- -Real Locomotor::getSurfaceHtAtPt(Real x, Real y) +/*static*/ Real Locomotor::getSurfaceHtAtPt(Real x, Real y) { Real ht = 0; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp index 66c80683f4b..3c5b5926a2a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp @@ -942,22 +942,22 @@ static Real calcDistSqr(const Coord3D& a, const Coord3D& b) Success: we are on the ground at the runway start Failure: we are unable to get on the ground */ -class HeliTakeoffOrLandingState : public State +class HeliTakeoffOrLandingState : public State { - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(HeliTakeoffOrLandingState, "HeliTakeoffOrLandingState") + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(HeliTakeoffOrLandingState, "HeliTakeoffOrLandingState") protected: // snapshot interface - virtual void crc( Xfer *xfer ) + virtual void crc(Xfer* xfer) { // empty. jba. } - virtual void xfer( Xfer *xfer ) + virtual void xfer(Xfer* xfer) { // version XferVersion currentVersion = 1; XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); + xfer->xferVersion(&version, currentVersion); // set on create. xfer->xferBool(&m_landing); xfer->xferCoord3D(&m_path[0]); @@ -978,17 +978,17 @@ class HeliTakeoffOrLandingState : public State Real m_parkingOrientation; Bool m_landing; public: - HeliTakeoffOrLandingState( StateMachine *machine, Bool landing ) : m_landing(landing), - State( machine, "HeliTakeoffOrLandingState" ), m_index(0) - { - m_parkingLoc.zero(); - } + HeliTakeoffOrLandingState(StateMachine* machine, Bool landing) : m_landing(landing), + State(machine, "HeliTakeoffOrLandingState"), m_index(0) + { + m_parkingLoc.zero(); + } virtual StateReturnType onEnter() { Object* jet = getMachineOwner(); JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if( !jetAI ) + if (!jetAI) return STATE_FAILURE; jetAI->friend_setTakeoffInProgress(!m_landing); @@ -1006,7 +1006,7 @@ class HeliTakeoffOrLandingState : public State ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID(), &airfield); if (pp == NULL) return STATE_SUCCESS; // no airfield? just skip this step - + Coord3D landingApproach; if (jet->isKindOf(KINDOF_PRODUCED_AT_HELIPAD)) { @@ -1034,7 +1034,7 @@ class HeliTakeoffOrLandingState : public State landingApproach = m_parkingLoc; landingApproach.z += (ppinfo.runwayApproach.z - ppinfo.runwayEnd.z); } - + if (m_landing) { m_path[0] = landingApproach; @@ -1058,10 +1058,10 @@ class HeliTakeoffOrLandingState : public State return STATE_FAILURE; JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if( !jetAI ) + if (!jetAI) return STATE_FAILURE; -// I have disabled this because it is no longer necessary and is a bit funky lookin' (srj) + // I have disabled this because it is no longer necessary and is a bit funky lookin' (srj) #ifdef NOT_IN_USE // magically position it correctly. jet->getPhysics()->scrubVelocity2D(0); @@ -1071,18 +1071,18 @@ class HeliTakeoffOrLandingState : public State Coord3D pos = *jet->getPosition(); Real dx = hoverloc.x - pos.x; Real dy = hoverloc.y - pos.y; - Real dSqr = dx*dx+dy*dy; + Real dSqr = dx * dx + dy * dy; const Real DARN_CLOSE = 0.25f; - if (dSqr < DARN_CLOSE) + if (dSqr < DARN_CLOSE) { jet->setPosition(&hoverloc); - } - else + } + else { Real dist = sqrtf(dSqr); - if (dist<1) dist = 1; - pos.x += PATHFIND_CELL_SIZE_F*dx/(dist*LOGICFRAMES_PER_SECOND); - pos.y += PATHFIND_CELL_SIZE_F*dy/(dist*LOGICFRAMES_PER_SECOND); + if (dist < 1) dist = 1; + pos.x += PATHFIND_CELL_SIZE_F * dx / (dist * LOGICFRAMES_PER_SECOND); + pos.y += PATHFIND_CELL_SIZE_F * dy / (dist * LOGICFRAMES_PER_SECOND); jet->setPosition(&pos); } #else @@ -1100,25 +1100,25 @@ class HeliTakeoffOrLandingState : public State jetAI->setLocomotorGoalPositionExplicit(m_path[m_index]); const Real THRESH = 3.0f; - const Real THRESH_SQR = THRESH*THRESH; + const Real THRESH_SQR = THRESH * THRESH; const Coord3D* a = jet->getPosition(); const Coord3D* b = &m_path[m_index]; Real distSqr = calcDistSqr(*a, *b); if (distSqr <= THRESH_SQR) ++m_index; - + if (m_index >= 2) return STATE_SUCCESS; return STATE_CONTINUE; } - virtual void onExit( StateExitType status ) + virtual void onExit(StateExitType status) { // just in case. Object* jet = getMachineOwner(); JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); - if( !jetAI ) + if (!jetAI) return; jetAI->friend_setTakeoffInProgress(false); @@ -1154,6 +1154,245 @@ class HeliTakeoffOrLandingState : public State }; EMPTY_DTOR(HeliTakeoffOrLandingState) +// ------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------- +/* + Success: we are on the ground at the runway start + Failure: we are unable to get on the ground +*/ +class VtolTakeoffOrLandingState : public State +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(VtolTakeoffOrLandingState, "VtolTakeoffOrLandingState") +protected: + // snapshot interface + virtual void crc(Xfer* xfer) + { + // empty. jba. + } + + virtual void xfer(Xfer* xfer) + { + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion(&version, currentVersion); + + // set on create. xfer->xferBool(&m_landing); + xfer->xferCoord3D(&m_path[0]); + xfer->xferCoord3D(&m_path[1]); + xfer->xferInt(&m_index); + xfer->xferCoord3D(&m_parkingLoc); + xfer->xferReal(&m_parkingOrientation); + } + virtual void loadPostProcess() + { + // empty. jba. + } + +private: + Coord3D m_path[2]; + Int m_index; + Coord3D m_parkingLoc; + Real m_parkingOrientation; + Bool m_landing; +public: + VtolTakeoffOrLandingState(StateMachine* machine, Bool landing) : m_landing(landing), + State(machine, "VtolTakeoffOrLandingState"), m_index(0) + { + m_parkingLoc.zero(); + } + + virtual StateReturnType onEnter() + { + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if (!jetAI) + return STATE_FAILURE; + + jetAI->friend_setTakeoffInProgress(!m_landing); + jetAI->friend_setLandingInProgress(m_landing); + jetAI->friend_setAllowAirLoco(true); + + //TODO: different sound and state for landing and takeoff + jetAI->AIUpdateInterface::chooseLocomotorSet(LOCOMOTORSET_VTOL); + jetAI->friend_enableAfterburners(true); + + Locomotor* loco = jetAI->getCurLocomotor(); + DEBUG_ASSERTCRASH(loco, ("no loco")); + loco->setUsePreciseZPos(true); + loco->setUltraAccurate(true); + + jetAI->ignoreObstacleID(jet->getProducerID()); + + Object* airfield; + ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID(), &airfield); + if (pp == NULL) + return STATE_SUCCESS; // no airfield? just skip this step + + Coord3D landingApproach; + + ParkingPlaceBehaviorInterface::PPInfo ppinfo; + + if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo)) + return STATE_FAILURE; + m_parkingLoc = ppinfo.parkingSpace; + m_parkingOrientation = ppinfo.parkingOrientation; + landingApproach = m_parkingLoc; + landingApproach.z += (ppinfo.runwayApproach.z - ppinfo.runwayEnd.z); + + + if (m_landing) + { + m_path[0] = landingApproach; + m_path[1] = m_parkingLoc; + + DEBUG_LOG((">>> HeliTakeoffOrLANDINGState - Enter: m_path[0] = %f, %f, %f\n", + m_path[0].x, m_path[0].y, m_path[0].z)); + DEBUG_LOG((">>> HeliTakeoffOrLANDINGState - Enter: m_path[1] = %f, %f, %f\n", + m_path[1].x, m_path[1].y, m_path[1].z)); + + jetAI->friend_setUseSpecialReturnLoco(false); + } + else + { // Take-off + m_path[0] = m_parkingLoc; + m_path[1] = landingApproach; + + // We return to our preferred height + Real targetHeight = jetAI->getCurLocomotor()->getPreferredHeight() + + Locomotor::getSurfaceHtAtPt(landingApproach.x, landingApproach.y); + + m_path[1].z = targetHeight; + + //m_path[2] = landingApproach; + //m_path[2].z = targetHeight; + + DEBUG_LOG((">>> HeliTAKEOFFOrLandingState - Enter: m_path[1] = %f, %f, %f\n", + m_path[1].x, m_path[1].y, m_path[1].z)); + + //m_index = 1; + //TheAI->pathfinder()->updateGoal(jet, &m_path[m_index], LAYER_GROUND); + //return STATE_CONTINUE; + } + m_index = 0; + + DEBUG_LOG(("HeliTakeoffOrLandingState - Enter: Locomotor = %s \n", loco->getTemplateName().str())); + + return STATE_CONTINUE; + } + + virtual StateReturnType update() + { + Object* jet = getMachineOwner(); + if (jet->isEffectivelyDead()) + return STATE_FAILURE; + + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if (!jetAI) + return STATE_FAILURE; + + // I have disabled this because it is no longer necessary and is a bit funky lookin' (srj) +#ifdef NOT_IN_USE + // magically position it correctly. + jet->getPhysics()->scrubVelocity2D(0); + Coord3D hoverloc = m_path[m_index]; + hoverloc.z = jet->getPosition()->z; +#if 1 + Coord3D pos = *jet->getPosition(); + Real dx = hoverloc.x - pos.x; + Real dy = hoverloc.y - pos.y; + Real dSqr = dx * dx + dy * dy; + const Real DARN_CLOSE = 0.25f; + if (dSqr < DARN_CLOSE) + { + jet->setPosition(&hoverloc); + } + else + { + Real dist = sqrtf(dSqr); + if (dist < 1) dist = 1; + pos.x += PATHFIND_CELL_SIZE_F * dx / (dist * LOGICFRAMES_PER_SECOND); + pos.y += PATHFIND_CELL_SIZE_F * dy / (dist * LOGICFRAMES_PER_SECOND); + jet->setPosition(&pos); + } +#else + jet->setPosition(&hoverloc); +#endif + jet->setOrientation(m_parkingOrientation); +#endif + Int targetIndex = 2; + + if (!m_landing) { + //targetIndex = 3; + TheAI->pathfinder()->updateGoal(jet, &m_path[m_index], LAYER_GROUND); + } + + jetAI->setLocomotorGoalPositionExplicit(m_path[m_index]); + + DEBUG_LOG((">>> HeliTakeoffOrLandingState - Update: index = %d, goalPos = %f, %f, %f; loco = %s\n", + m_index, m_path[m_index].x, m_path[m_index].y, m_path[m_index].z, + jetAI->getCurLocomotor()->getTemplateName().str())); + + const Real THRESH = 3.0f; + const Real THRESH_SQR = THRESH * THRESH; + const Coord3D* a = jet->getPosition(); + const Coord3D* b = &m_path[m_index]; + Real distSqr = calcDistSqr(*a, *b); + if (distSqr <= THRESH_SQR) + ++m_index; + + if (m_index >= targetIndex) + return STATE_SUCCESS; + + return STATE_CONTINUE; + } + + virtual void onExit(StateExitType status) + { + // just in case. + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if (!jetAI) + return; + + jetAI->friend_setTakeoffInProgress(false); + jetAI->friend_setLandingInProgress(false); + + // Paranoia checks - sometimes onExit is called when we are + // shutting down, and not all pieces are valid. CurLocomotor + // is definitely null in some cases. jba. + Locomotor* loco = jetAI->getCurLocomotor(); + if (loco) + { + loco->setUsePreciseZPos(false); + loco->setUltraAccurate(false); + // don't restore lift if dead -- this may fight with JetSlowDeathBehavior! + if (!jet->isEffectivelyDead()) + loco->setMaxLift(BIGNUM); + } + + jetAI->ignoreObstacleID(INVALID_ID); + ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); + if (m_landing) + { + jetAI->friend_setAllowAirLoco(false); + jetAI->AIUpdateInterface::chooseLocomotorSet(LOCOMOTORSET_TAXIING); + } + else + { + jetAI->chooseLocomotorSet(LOCOMOTORSET_NORMAL); + + if (pp && !jetAI->friend_keepsParkingSpaceWhenAirborne()) + pp->releaseSpace(jet->getID()); + } + + jetAI->friend_enableAfterburners(false); + } + +}; +EMPTY_DTOR(VtolTakeoffOrLandingState) + + //------------------------------------------------------------------------------------------------- class JetOrHeliParkOrientState : public State { @@ -1660,6 +1899,40 @@ HeliAIStateMachine::~HeliAIStateMachine() //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class VtolAIStateMachine : public AIStateMachine +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(VtolAIStateMachine, "VtolAIStateMachine"); + +public: + VtolAIStateMachine(Object* owner, AsciiString name); + +}; + +//------------------------------------------------------------------------------------------------- +VtolAIStateMachine::VtolAIStateMachine(Object* owner, AsciiString name) : AIStateMachine(owner, name) +{ + defineState(RETURNING_FOR_LANDING, newInstance(JetOrHeliReturnForLandingState)(this), LANDING_AWAIT_CLEARANCE, RETURN_TO_DEAD_AIRFIELD); + defineState(TAKING_OFF_AWAIT_CLEARANCE, newInstance(SuccessState)(this), TAKING_OFF, AI_IDLE); + defineState(TAKING_OFF, newInstance(VtolTakeoffOrLandingState)(this, false), AI_IDLE, AI_IDLE); + defineState(LANDING_AWAIT_CLEARANCE, newInstance(SuccessState)(this), ORIENT_FOR_PARKING_PLACE, AI_IDLE); + defineState(ORIENT_FOR_PARKING_PLACE, newInstance(JetOrHeliParkOrientState)(this), LANDING, AI_IDLE); + defineState(LANDING, newInstance(VtolTakeoffOrLandingState)(this, true), RELOAD_AMMO, AI_IDLE); + defineState(RELOAD_AMMO, newInstance(JetOrHeliReloadAmmoState)(this), AI_IDLE, AI_IDLE); + defineState(RETURN_TO_DEAD_AIRFIELD, newInstance(JetOrHeliReturningToDeadAirfieldState)(this), CIRCLING_DEAD_AIRFIELD, RETURN_TO_DEAD_AIRFIELD); + defineState(CIRCLING_DEAD_AIRFIELD, newInstance(JetOrHeliCirclingDeadAirfieldState)(this), AI_IDLE, AI_IDLE); + defineState(TAXI_FROM_HANGAR, newInstance(JetOrHeliTaxiState)(this, FROM_HANGAR), AI_IDLE, AI_IDLE); +} + +//------------------------------------------------------------------------------------------------- +VtolAIStateMachine::~VtolAIStateMachine() +{ +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + //------------------------------------------------------------------------------------------------- JetAIUpdateModuleData::JetAIUpdateModuleData() { @@ -1721,10 +1994,26 @@ JetAIUpdateModuleData::JetAIUpdateModuleData() //------------------------------------------------------------------------------------------------- AIStateMachine* JetAIUpdate::makeStateMachine() { - if (getJetAIUpdateModuleData()->m_needsRunway) - return newInstance(JetAIStateMachine)( getObject(), "JetAIStateMachine"); - else - return newInstance(HeliAIStateMachine)( getObject(), "HeliAIStateMachine"); + + + // If we need a runway, we are a jet + if (getJetAIUpdateModuleData()->m_needsRunway) { + return newInstance(JetAIStateMachine)(getObject(), "JetAIStateMachine"); + } + else { + + // return newInstance(HeliAIStateMachine)(getObject(), "HeliAIStateMachine"); + + if (getObject()->isKindOf(KINDOF_PRODUCED_AT_HELIPAD)) { + return newInstance(HeliAIStateMachine)(getObject(), "HeliAIStateMachine"); + } + // Else we need hybrid VTOL logic + else { + return newInstance(VtolAIStateMachine)(getObject(), "VtolAIStateMachine"); + } + + } + } //------------------------------------------------------------------------------------------------- From 7af6e410302e172948b5a6235411cd3d4bc1cd97 Mon Sep 17 00:00:00 2001 From: andreasw Date: Tue, 10 Jun 2025 20:33:52 +0200 Subject: [PATCH 23/42] Improve VTOL AI - Good enough for now --- .../GameEngine/Include/Common/ModelState.h | 5 + .../Include/GameLogic/Module/JetAIUpdate.h | 4 + .../GameEngine/Source/Common/BitFlags.cpp | 3 + .../Source/Common/System/MemoryInit.cpp | 1 + .../Object/Update/AIUpdate/JetAIUpdate.cpp | 229 ++++++++++++++++-- 5 files changed, 222 insertions(+), 20 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h b/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h index a683e17985d..9800fa6e664 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h @@ -279,6 +279,11 @@ enum ModelConditionFlagType CPP_11(: Int) MODELCONDITION_RELOADING_H, MODELCONDITION_USING_WEAPON_H, + // VTOL + MODELCONDITION_TAKEOFF, + MODELCONDITION_LANDING, + + // // Note: these values are saved in save files, so you MUST NOT REMOVE OR CHANGE // existing values! diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/JetAIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/JetAIUpdate.h index 19fedcdbe6b..a3d5f0f5845 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/JetAIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/JetAIUpdate.h @@ -126,6 +126,8 @@ class JetAIUpdate : public AIUpdateInterface void friend_setAllowCircling(Bool v) { setFlag(ALLOW_CIRCLING, v); } const Coord3D& friend_getLandingPosForHelipadStuff() const { return m_landingPosForHelipadStuff; } void friend_enableAfterburners(Bool v); + void friend_enableTakeOffEffects(Bool v); // For VTOL and Helicopters + void friend_enableLandingEffects(Bool v); // For VTOL and Helicopters void friend_setAllowAirLoco(Bool a); Bool friend_isTakeoffOrLandingInProgress() const { @@ -169,6 +171,8 @@ class JetAIUpdate : public AIUpdateInterface Coord3D m_producerLocation; ///< remember this, so that if our producer dies, we have a place to circle aimlessly AICommandParmsStorage m_mostRecentCommand; AudioEventRTS m_afterburnerSound; ///< Sound when afterburners on + AudioEventRTS m_takeOffSound; ///< Sound when VTOL or Heli takes off + AudioEventRTS m_landingSound; ///< Sound when VTOL or Heli lands UnsignedInt m_attackLocoExpireFrame; UnsignedInt m_attackersMissExpireFrame; UnsignedInt m_returnToBaseFrame; ///< if nonzero, return to base at this frame when we are idle, even if not out of ammo diff --git a/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp b/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp index 40b1f6b59ea..46f55c48a34 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp @@ -207,6 +207,9 @@ const char* ModelConditionFlags::s_bitNameList[] = "BETWEEN_FIRING_SHOTS_H", "RELOADING_H", "USING_WEAPON_H", + + "TAKEOFF", + "LANDING", NULL }; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp index 46636ca4094..03b13b8c8fa 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp @@ -491,6 +491,7 @@ static PoolSizeRec sizes[] = { "HeliTakeoffOrLandingState", 64, 32 }, { "VtolTakeoffOrLandingState", 64, 32 }, { "JetOrHeliParkOrientState", 64, 32 }, + { "VtolParkOrientState", 64, 32 }, { "JetOrHeliReloadAmmoState", 64, 32 }, { "SupplyTruckBusyState", 600, 32 }, { "SupplyTruckIdleState", 600, 32 }, diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp index 3c5b5926a2a..5648abf5f77 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp @@ -1154,6 +1154,131 @@ class HeliTakeoffOrLandingState : public State }; EMPTY_DTOR(HeliTakeoffOrLandingState) + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class VtolParkOrientState : public State +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(VtolParkOrientState, "VtolParkOrientState") +protected: + // snapshot interface STUBBED. + virtual void crc(Xfer* xfer) {}; + virtual void xfer(Xfer* xfer) { XferVersion cv = 1; XferVersion v = cv; xfer->xferVersion(&v, cv); } + virtual void loadPostProcess() {}; + +public: + VtolParkOrientState(StateMachine* machine) : State(machine, "VtolParkOrientState") {} + + virtual StateReturnType onEnter() + { + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if (!jetAI) + return STATE_FAILURE; + + jetAI->friend_setTakeoffInProgress(false); + jetAI->friend_setLandingInProgress(true); + + jetAI->ignoreObstacleID(jet->getProducerID()); + + jetAI->friend_setUseSpecialReturnLoco(false); + jetAI->AIUpdateInterface::chooseLocomotorSet(LOCOMOTORSET_VTOL); + + return STATE_CONTINUE; + } + + virtual StateReturnType update() + { + Object* jet = getMachineOwner(); + if (jet->isEffectivelyDead()) + return STATE_FAILURE; + + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if (!jetAI) + { + return STATE_FAILURE; + } + + ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); + if (pp == NULL) + return STATE_FAILURE; + + ParkingPlaceBehaviorInterface::PPInfo ppinfo; + if (!pp->reserveSpace(jet->getID(), jetAI->friend_getParkingOffset(), &ppinfo)) + return STATE_FAILURE; + + // Check Orientation + Real angleDiff = fabs(stdAngleDiff(jet->getOrientation(), ppinfo.parkingOrientation)); + + // Check Position (Slide into place) + Coord3D hoverloc = ppinfo.parkingSpace; + if (jet->testStatus(OBJECT_STATUS_DECK_HEIGHT_OFFSET)) + { + hoverloc = ppinfo.runwayPrep; + } + hoverloc.z = jet->getPosition()->z; + + Coord3D pos = *jet->getPosition(); + Real dx = hoverloc.x - pos.x; + Real dy = hoverloc.y - pos.y; + Real dSqr = dx * dx + dy * dy; + const Real DARN_CLOSE = 3.0f; // 0.25f; + + /*DEBUG_LOG((">>> VtolParkOrientState - Update: dx = %f, dy = %f, dSqr = %f; loco = %s\n", + dx, dy, dSqr, + jetAI->getCurLocomotor()->getTemplateName().str()));*/ + + if (dSqr < DARN_CLOSE) + { + jet->setPosition(&hoverloc); + } + else + { + Real dist = sqrtf(dSqr); + if (dist < 2) dist = 2; + pos.x += PATHFIND_CELL_SIZE_F * dx / (dist * LOGICFRAMES_PER_SECOND) * 5.0f; + pos.y += PATHFIND_CELL_SIZE_F * dy / (dist * LOGICFRAMES_PER_SECOND) * 5.0f; + jet->setPosition(&pos); + } + + const Real A_THRESH = 0.001f; + if (angleDiff <= A_THRESH && dSqr <= DARN_CLOSE) { + return STATE_SUCCESS; + } + + //if (fabs(stdAngleDiff(jet->getOrientation(), ppinfo.parkingOrientation)) <= THRESH) + // return STATE_SUCCESS; + + // magically position it correctly. + /*jet->getPhysics()->scrubVelocity2D(0); + Coord3D hoverloc = ppinfo.parkingSpace; + if (jet->testStatus(OBJECT_STATUS_DECK_HEIGHT_OFFSET)) + { + hoverloc = ppinfo.runwayPrep; + } + + hoverloc.z = jet->getPosition()->z; + jet->setPosition(&hoverloc);*/ + + jetAI->setLocomotorGoalOrientation(ppinfo.parkingOrientation); + + return STATE_CONTINUE; + } + + virtual void onExit(StateExitType status) + { + Object* jet = getMachineOwner(); + JetAIUpdate* jetAI = (JetAIUpdate*)jet->getAIUpdateInterface(); + if (!jetAI) + return; + + jetAI->friend_setTakeoffInProgress(false); + jetAI->friend_setLandingInProgress(false); + jetAI->ignoreObstacleID(INVALID_ID); + } +}; +EMPTY_DTOR(VtolParkOrientState) + // ------------------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------------------- /* @@ -1215,7 +1340,6 @@ class VtolTakeoffOrLandingState : public State //TODO: different sound and state for landing and takeoff jetAI->AIUpdateInterface::chooseLocomotorSet(LOCOMOTORSET_VTOL); - jetAI->friend_enableAfterburners(true); Locomotor* loco = jetAI->getCurLocomotor(); DEBUG_ASSERTCRASH(loco, ("no loco")); @@ -1237,24 +1361,33 @@ class VtolTakeoffOrLandingState : public State return STATE_FAILURE; m_parkingLoc = ppinfo.parkingSpace; m_parkingOrientation = ppinfo.parkingOrientation; - landingApproach = m_parkingLoc; - landingApproach.z += (ppinfo.runwayApproach.z - ppinfo.runwayEnd.z); + + //landingApproach = m_parkingLoc; + //landingApproach.z += (ppinfo.runwayApproach.z - ppinfo.runwayEnd.z); if (m_landing) { + jetAI->friend_enableLandingEffects(true); + + landingApproach = *jet->getPosition(); + m_path[0] = landingApproach; m_path[1] = m_parkingLoc; - DEBUG_LOG((">>> HeliTakeoffOrLANDINGState - Enter: m_path[0] = %f, %f, %f\n", + /*DEBUG_LOG((">>> HeliTakeoffOrLANDINGState - Enter: m_path[0] = %f, %f, %f\n", m_path[0].x, m_path[0].y, m_path[0].z)); DEBUG_LOG((">>> HeliTakeoffOrLANDINGState - Enter: m_path[1] = %f, %f, %f\n", - m_path[1].x, m_path[1].y, m_path[1].z)); + m_path[1].x, m_path[1].y, m_path[1].z));*/ jetAI->friend_setUseSpecialReturnLoco(false); } else { // Take-off + jetAI->friend_enableTakeOffEffects(true); + + landingApproach = m_parkingLoc; + m_path[0] = m_parkingLoc; m_path[1] = landingApproach; @@ -1264,19 +1397,12 @@ class VtolTakeoffOrLandingState : public State m_path[1].z = targetHeight; - //m_path[2] = landingApproach; - //m_path[2].z = targetHeight; - - DEBUG_LOG((">>> HeliTAKEOFFOrLandingState - Enter: m_path[1] = %f, %f, %f\n", - m_path[1].x, m_path[1].y, m_path[1].z)); - - //m_index = 1; - //TheAI->pathfinder()->updateGoal(jet, &m_path[m_index], LAYER_GROUND); - //return STATE_CONTINUE; + //DEBUG_LOG((">>> HeliTAKEOFFOrLandingState - Enter: m_path[1] = %f, %f, %f\n", + // m_path[1].x, m_path[1].y, m_path[1].z)); } m_index = 0; - DEBUG_LOG(("HeliTakeoffOrLandingState - Enter: Locomotor = %s \n", loco->getTemplateName().str())); + // DEBUG_LOG(("HeliTakeoffOrLandingState - Enter: Locomotor = %s \n", loco->getTemplateName().str())); return STATE_CONTINUE; } @@ -1292,6 +1418,7 @@ class VtolTakeoffOrLandingState : public State return STATE_FAILURE; // I have disabled this because it is no longer necessary and is a bit funky lookin' (srj) + #ifdef NOT_IN_USE // magically position it correctly. jet->getPhysics()->scrubVelocity2D(0); @@ -1329,9 +1456,9 @@ class VtolTakeoffOrLandingState : public State jetAI->setLocomotorGoalPositionExplicit(m_path[m_index]); - DEBUG_LOG((">>> HeliTakeoffOrLandingState - Update: index = %d, goalPos = %f, %f, %f; loco = %s\n", - m_index, m_path[m_index].x, m_path[m_index].y, m_path[m_index].z, - jetAI->getCurLocomotor()->getTemplateName().str())); + //DEBUG_LOG((">>> HeliTakeoffOrLandingState - Update: index = %d, goalPos = %f, %f, %f; loco = %s\n", + // m_index, m_path[m_index].x, m_path[m_index].y, m_path[m_index].z, + // jetAI->getCurLocomotor()->getTemplateName().str())); const Real THRESH = 3.0f; const Real THRESH_SQR = THRESH * THRESH; @@ -1375,18 +1502,28 @@ class VtolTakeoffOrLandingState : public State ParkingPlaceBehaviorInterface* pp = getPP(jet->getProducerID()); if (m_landing) { + jetAI->friend_enableLandingEffects(false); jetAI->friend_setAllowAirLoco(false); jetAI->AIUpdateInterface::chooseLocomotorSet(LOCOMOTORSET_TAXIING); } else { + jetAI->friend_enableTakeOffEffects(false); + jetAI->chooseLocomotorSet(LOCOMOTORSET_NORMAL); + //TODO: Fix this terrible nose tilt + + // Snap to preferred height + /*Coord3D pos = *jet->getPosition(); + pos.z = jetAI->getCurLocomotor()->getPreferredHeight() + + Locomotor::getSurfaceHtAtPt(pos.x, pos.y); + jet->setPosition(&pos);*/ + if (pp && !jetAI->friend_keepsParkingSpaceWhenAirborne()) pp->releaseSpace(jet->getID()); } - jetAI->friend_enableAfterburners(false); } }; @@ -1916,7 +2053,7 @@ VtolAIStateMachine::VtolAIStateMachine(Object* owner, AsciiString name) : AIStat defineState(TAKING_OFF_AWAIT_CLEARANCE, newInstance(SuccessState)(this), TAKING_OFF, AI_IDLE); defineState(TAKING_OFF, newInstance(VtolTakeoffOrLandingState)(this, false), AI_IDLE, AI_IDLE); defineState(LANDING_AWAIT_CLEARANCE, newInstance(SuccessState)(this), ORIENT_FOR_PARKING_PLACE, AI_IDLE); - defineState(ORIENT_FOR_PARKING_PLACE, newInstance(JetOrHeliParkOrientState)(this), LANDING, AI_IDLE); + defineState(ORIENT_FOR_PARKING_PLACE, newInstance(VtolParkOrientState)(this), LANDING, AI_IDLE); defineState(LANDING, newInstance(VtolTakeoffOrLandingState)(this, true), RELOAD_AMMO, AI_IDLE); defineState(RELOAD_AMMO, newInstance(JetOrHeliReloadAmmoState)(this), AI_IDLE, AI_IDLE); defineState(RETURN_TO_DEAD_AIRFIELD, newInstance(JetOrHeliReturningToDeadAirfieldState)(this), CIRCLING_DEAD_AIRFIELD, RETURN_TO_DEAD_AIRFIELD); @@ -2022,6 +2159,13 @@ JetAIUpdate::JetAIUpdate( Thing *thing, const ModuleData* moduleData ) : AIUpdat m_flags = 0; m_afterburnerSound = *(getObject()->getTemplate()->getPerUnitSound("Afterburner")); m_afterburnerSound.setObjectID(getObject()->getID()); + + m_takeOffSound = *(getObject()->getTemplate()->getPerUnitSound("TakeOff")); + m_takeOffSound.setObjectID(getObject()->getID()); + + m_landingSound = *(getObject()->getTemplate()->getPerUnitSound("Landing")); + m_landingSound.setObjectID(getObject()->getID()); + m_attackLocoExpireFrame = 0; m_attackersMissExpireFrame = 0; m_untargetableExpireFrame = 0; @@ -2829,6 +2973,51 @@ void JetAIUpdate::friend_enableAfterburners(Bool v) } } +//------------------------------------------------------------------------------------------------- +void JetAIUpdate::friend_enableTakeOffEffects(Bool v) +{ + Object* jet = getObject(); + if (v) + { + jet->setModelConditionState(MODELCONDITION_TAKEOFF); + if (!m_takeOffSound.isCurrentlyPlaying()) + { + m_takeOffSound.setObjectID(jet->getID()); + m_takeOffSound.setPlayingHandle(TheAudio->addAudioEvent(&m_takeOffSound)); + } + } + else + { + jet->clearModelConditionState(MODELCONDITION_TAKEOFF); + if (m_takeOffSound.isCurrentlyPlaying()) + { + TheAudio->removeAudioEvent(m_takeOffSound.getPlayingHandle()); + } + } +} +//------------------------------------------------------------------------------------------------- +void JetAIUpdate::friend_enableLandingEffects(Bool v) +{ + Object* jet = getObject(); + if (v) + { + jet->setModelConditionState(MODELCONDITION_LANDING); + if (!m_landingSound.isCurrentlyPlaying()) + { + m_landingSound.setObjectID(jet->getID()); + m_landingSound.setPlayingHandle(TheAudio->addAudioEvent(&m_landingSound)); + } + } + else + { + jet->clearModelConditionState(MODELCONDITION_LANDING); + if (m_landingSound.isCurrentlyPlaying()) + { + TheAudio->removeAudioEvent(m_landingSound.getPlayingHandle()); + } + } +} + //------------------------------------------------------------------------------------------------- void JetAIUpdate::friend_addWaypointToGoalPath( const Coord3D &bestPos ) { From a69d4339f8a1c9c041946b2cc8eb0b61350a3182 Mon Sep 17 00:00:00 2001 From: andreasw Date: Tue, 10 Jun 2025 20:36:07 +0200 Subject: [PATCH 24/42] line endings --- .../Code/GameEngine/Include/Common/KindOf.h | 512 ++++----- .../GameEngine/Include/GameLogic/Locomotor.h | 1022 ++++++++--------- 2 files changed, 767 insertions(+), 767 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h b/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h index 00346f5d8dd..3d86987e6ac 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h @@ -1,256 +1,256 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: KindOf.h ////////////////////////////////////////////////////////////////////////// -// Author: Steven Johnson, Dec 2001 -// Desc: -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __KINDOF_H_ -#define __KINDOF_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "Lib/BaseType.h" -#include "Common/BitFlags.h" -#include "Common/BitFlagsIO.h" - -//------------------------------------------------------------------------------------------------- -/** Kind of flags for determining groups of things that belong together - * NOTE: You *MUST* keep this in the same order as the KindOfNames[] below */ -//------------------------------------------------------------------------------------------------- -enum KindOfType CPP_11(: Int) -{ - KINDOF_INVALID = -1, - KINDOF_FIRST = 0, - KINDOF_OBSTACLE = KINDOF_FIRST, ///< an obstacle to land-based pathfinders - KINDOF_SELECTABLE, ///< Actually means MOUSE-INTERACTABLE (doesn't mean you can select it!) - KINDOF_IMMOBILE, ///< fixed in location - KINDOF_CAN_ATTACK, ///< can attack - KINDOF_STICK_TO_TERRAIN_SLOPE, ///< should be stuck at ground level, aligned to terrain slope. requires that IMMOBILE bit is also set. - KINDOF_CAN_CAST_REFLECTIONS, ///< can cast reflections in water - KINDOF_SHRUBBERY, ///< tree, bush, etc. - KINDOF_STRUCTURE, ///< structure of some sort (buildable or not) - KINDOF_INFANTRY, ///< unit like soldier etc - KINDOF_VEHICLE, ///< unit like tank, jeep, plane, helicopter, etc. - KINDOF_AIRCRAFT, ///< unit like plane, helicopter, etc., that is predominantly a flyer. (hovercraft are NOT aircraft) - KINDOF_HUGE_VEHICLE, ///< unit that is, technically, a vehicle, but WAY larger than normal (eg, Overlord) - KINDOF_DOZER, ///< a dozer - KINDOF_HARVESTER, ///< a harvester - KINDOF_COMMANDCENTER, ///< a command center -#ifdef ALLOW_SURRENDER - KINDOF_PRISON, ///< a prison detention center kind of thing - KINDOF_COLLECTS_PRISON_BOUNTY, ///< when prisoners are delivered to these, the player gets money - KINDOF_POW_TRUCK, ///< a pow truck can pick up and return prisoners -#endif - KINDOF_LINEBUILD, ///< wall-type thing that is built in a line - KINDOF_SALVAGER, ///< something that can create and use Salvage Crates - KINDOF_WEAPON_SALVAGER, ///< subset of salvager that can get weapon upgrades from salvage - KINDOF_TRANSPORT, ///< a true transport (has TransportContain) - KINDOF_BRIDGE, ///< a Bridge. (special structure) - KINDOF_LANDMARK_BRIDGE, ///< a landmark bridge (special bridge that isn't resizable) - KINDOF_BRIDGE_TOWER, ///< a bridge tower that we can target for bridge destruction - KINDOF_PROJECTILE, ///< Instead of being a ground or air unit, this object is special - KINDOF_PRELOAD, ///< all model data will be preloaded even if not on map - KINDOF_NO_GARRISON, ///< unit may not garrison bldgs, even if infantry bit is set - KINDOF_WAVEGUIDE, ///< water wave object - KINDOF_WAVE_EFFECT, ///< wave effect point - KINDOF_NO_COLLIDE, ///< Never collide with or be collided with - KINDOF_REPAIR_PAD, ///< is a repair pad object that can repair other machines - KINDOF_HEAL_PAD, ///< is a heal pad object that can heal flesh and bone units - KINDOF_STEALTH_GARRISON, /** enemy teams can't tell that unit is in building.. and if they - garrison that building, they stealth unit will eject. */ - KINDOF_CASH_GENERATOR, ///< used to check if the unit generates cash... checked by cash hackers and whatever else comes up - KINDOF_DRAWABLE_ONLY, ///< template is used only to create drawables (not Objects) - KINDOF_MP_COUNT_FOR_VICTORY, ///< If a player loses all his buildings that have this kindof in a multiplayer game, he loses. - KINDOF_REBUILD_HOLE, ///< a GLA rebuild hole - KINDOF_SCORE, ///< Object counts for Multiplayer scores, and short-game calculations (for buildings) - KINDOF_SCORE_CREATE, ///< Object only counts for multiplayer score for creation. - KINDOF_SCORE_DESTROY, ///< Object only counts for multiplayer score for destruction. - KINDOF_NO_HEAL_ICON, ///< do not ever display healing icons on these objects - KINDOF_CAN_RAPPEL, ///< can rappel. duh. - KINDOF_PARACHUTABLE, ///< parachutable object -#ifdef ALLOW_SURRENDER - KINDOF_CAN_SURRENDER, ///< object that can surrender -#endif - KINDOF_CAN_BE_REPULSED, ///< object that runs away from a repulsor object. - KINDOF_MOB_NEXUS, ///< object that cooyrdinates the members of a mob (i.e. GLAInfantryAngryMob) - KINDOF_IGNORED_IN_GUI, ///< object that is the members of a mob (i.e. GLAInfantryAngryMob) - KINDOF_CRATE, ///< a bonus crate - KINDOF_CAPTURABLE, ///< is "capturable" even if not an enemy (should generally be used only for structures, eg, Tech bldgs) - KINDOF_CLEARED_BY_BUILD, ///< is auto-cleared from the map when built over via construction - KINDOF_SMALL_MISSILE, ///< Missile object: ONLY USED FOR ANTI-MISSILE TARGETTING PURPOSES! Keep using PROJECTILE! - KINDOF_ALWAYS_VISIBLE, ///< is never obscured by fog of war or shroud. mostly for UI feedback objects. - KINDOF_UNATTACKABLE, ///< You cannot target this thing, it probably doesn't really exist - KINDOF_MINE, ///< a landmine. (possibly also extend to Col. Burton timed charges?) - KINDOF_CLEANUP_HAZARD, ///< radiation and bio-poison are samples of area conditions that can be cleaned up (or avoided) - KINDOF_PORTABLE_STRUCTURE, ///< Flag to identify building like subobjects an Overlord is allowed to Contain. - KINDOF_ALWAYS_SELECTABLE, ///< is never unselectable (even if effectively dead). mostly for UI feedback objects. - KINDOF_ATTACK_NEEDS_LINE_OF_SIGHT, ///< Unit has to have clear line of sight (los) to attack. - KINDOF_WALK_ON_TOP_OF_WALL, ///< Units can walk on top of a wall made of these kind of objects. - KINDOF_DEFENSIVE_WALL, ///< wall can't be driven thru, even if crusher, so pathfinder must path around it - KINDOF_FS_POWER, ///< Faction structure power building - KINDOF_FS_FACTORY, ///< Faction structure power building - KINDOF_FS_BASE_DEFENSE, ///< Faction structure base defense - KINDOF_FS_TECHNOLOGY, ///< Faction structure technology building - KINDOF_AIRCRAFT_PATH_AROUND, ///< Tall enough that aircraft need to path around this. - KINDOF_LOW_OVERLAPPABLE, ///< When overlapped, things always overlap at a 'low' height rather than our object geom - KINDOF_FORCEATTACKABLE, ///< unit is always attackable via force-attack, even if not selectable - KINDOF_AUTO_RALLYPOINT, ///< When immobile-structure-object is selected, left clicking on ground will set new rally point without requiring command button. - KINDOF_TECH_BUILDING, ///< Neutral tech building - Oil derrick, Hospital, Radio Station, Refinery. - KINDOF_POWERED, ///< This object gets the Underpowered disabled condition when its owning player has power consumption exceed supply - KINDOF_PRODUCED_AT_HELIPAD, ///< ugh... hacky fix for comanche. (srj) - KINDOF_DRONE, ///< Object drone type -- used for filtering them out of battle plan bonuses, making un-snipable, and whatever else may come up. - KINDOF_CAN_SEE_THROUGH_STRUCTURE,///< Structure does not block line of sight. - KINDOF_BALLISTIC_MISSILE, ///< Large ballistic missiles that are specifically large enough to be targetted by base defenses. - KINDOF_CLICK_THROUGH, ///< Objects with this will never be picked by mouse interactions! - KINDOF_SUPPLY_SOURCE_ON_PREVIEW,///< Any thing that we can get "supplies" from that we want to show up on the map preview - KINDOF_PARACHUTE, ///< it's a parachute - KINDOF_GARRISONABLE_UNTIL_DESTROYED, ///< Object is capable of garrisoning troops until completely destroyed. - KINDOF_BOAT, ///< It's a boat! - KINDOF_IMMUNE_TO_CAPTURE, ///< Under no circumstances can this building ever be captured. - KINDOF_HULK, ///< Hulk types so we can do special things to them via scripts or other things that may come up. - KINDOF_SHOW_PORTRAIT_WHEN_CONTROLLED, ///< Only shows portraits when controlled. - KINDOF_SPAWNS_ARE_THE_WEAPONS, ///< Evaluate the spawn slaves as this object's weapons. - KINDOF_CANNOT_BUILD_NEAR_SUPPLIES, ///< you can't be built "too close" to anything that provides supplies - KINDOF_SUPPLY_SOURCE, ///< this object provides supplies - KINDOF_REVEAL_TO_ALL, ///< this object reveals shroud for all players - KINDOF_DISGUISER, ///< This object has the ability to disguise. - KINDOF_INERT, ///< this object shouldn't be considered for any sort of interaction with any player. - KINDOF_HERO, ///< Any of the single-instance infantry, JarmenKell, BlackLotus, ColonelBurton - KINDOF_IGNORES_SELECT_ALL, ///< Too late to figure out intelligently if something should respond to a Select All command - KINDOF_DONT_AUTO_CRUSH_INFANTRY, ///< These units don't try to crush the infantry if ai. - KINDOF_CLIFF_JUMPER, ///< Can't climb cliffs, but can jump off of them. - KINDOF_FS_SUPPLY_DROPZONE, ///< A supply dropzone. - KINDOF_FS_SUPERWEAPON, ///< A superweapon structure like a nuke silo, particle uplink cannon, scudstorm. - KINDOF_FS_BLACK_MARKET, ///< Is this object a black market? - KINDOF_FS_SUPPLY_CENTER, ///< Is this object a supply center? - KINDOF_FS_STRATEGY_CENTER, ///< Is this object a strategy center? - KINDOF_MONEY_HACKER, ///< Unit that generates money from air. Needed for things that directly power them up. - KINDOF_ARMOR_SALVAGER, ///< subset of salvager that can get armor upgrades from salvage - KINDOF_REVEALS_ENEMY_PATHS, ///< like the listening outpost... when selected, any enemy drawable will draw show paths when moused over - KINDOF_BOOBY_TRAP, ///< A sticky bomb that gets set off by 5 random and unrelated events. - KINDOF_FS_FAKE, ///< Fake structure! - KINDOF_FS_INTERNET_CENTER, ///< Internet Center. - KINDOF_BLAST_CRATER, ///< deeply gouges out the terrain under object footprint - KINDOF_PROP, ///< A prop, visual only, doesn't interact with other objects (rock, street sign, inert fire hydrant) - KINDOF_OPTIMIZED_TREE, ///< An optimized, client side only tree. (The only good kind of tree. jba) - KINDOF_FS_ADVANCED_TECH, ///< Represents each faction's advanced techtree building -- strategy center, propaganda center, and palace. - KINDOF_FS_BARRACKS, ///< A barracks - KINDOF_FS_WARFACTORY, ///< A war factory or arms dealer. - KINDOF_FS_AIRFIELD, ///< An airfield. - KINDOF_AIRCRAFT_CARRIER, ///< An aircraft carrier. - KINDOF_NO_SELECT, ///< Can't select it but you can mouse over it to see it's health (drones!) - KINDOF_REJECT_UNMANNED, ///< Unit cannot enter an unmanned vehicle. - KINDOF_CANNOT_RETALIATE, ///< Unit will not retaliate if asked. - KINDOF_TECH_BASE_DEFENSE, ///< Tech Building that acts as base defence when captured - KINDOF_EMP_HARDENED, ///< Like a delivery plane (B52, B3, CargoPlane,etc.) or a SpectreGunship, which sort-of IS the weapon... - KINDOF_DEMOTRAP, ///< Added strictly only for disarming purposes. They don't act like mines which have rendering and selection implications! - KINDOF_CONSERVATIVE_BUILDING, ///< Conservative structures aren't considered part of your base for sneak attack boundary calculations... - KINDOF_IGNORE_DOCKING_BONES, ///< Structure will not look up docking bones. Patch 1.03 hack. - - // NEW KINDOFs - - KINDOF_VTOL, - KINDOF_LARGE_AIRCRAFT, - KINDOF_MEDIUM_AIRCRAFT, - KINDOF_SMALL_AIRCRAFT, - KINDOF_ARTILLERY, - KINDOF_HEAVY_ARTILLERY, - KINDOF_ANTI_AIR, - KINDOF_SCOUT, - KINDOF_COMMANDO, - KINDOF_HEAVY_INFANTRY, - KINDOF_SUPERHEAVY_VEHICLE, - - KINDOF_EXTRA1, - KINDOF_EXTRA2, - KINDOF_EXTRA3, - KINDOF_EXTRA4, - KINDOF_EXTRA5, - KINDOF_EXTRA6, - KINDOF_EXTRA7, - KINDOF_EXTRA8, - KINDOF_EXTRA9, - KINDOF_EXTRA10, - KINDOF_EXTRA11, - KINDOF_EXTRA12, - KINDOF_EXTRA13, - KINDOF_EXTRA14, - KINDOF_EXTRA15, - KINDOF_EXTRA16, - - - KINDOF_COUNT // total number of kindofs - -}; - -typedef BitFlags KindOfMaskType; - -#define MAKE_KINDOF_MASK(k) KindOfMaskType(KindOfMaskType::kInit, (k)) - -inline Bool TEST_KINDOFMASK(const KindOfMaskType& m, KindOfType t) -{ - return m.test(t); -} - -inline Bool TEST_KINDOFMASK_ANY(const KindOfMaskType& m, const KindOfMaskType& mask) -{ - return m.anyIntersectionWith(mask); -} - -inline Bool TEST_KINDOFMASK_MULTI(const KindOfMaskType& m, const KindOfMaskType& mustBeSet, const KindOfMaskType& mustBeClear) -{ - return m.testSetAndClear(mustBeSet, mustBeClear); -} - -inline Bool KINDOFMASK_ANY_SET(const KindOfMaskType& m) -{ - return m.any(); -} - -inline void CLEAR_KINDOFMASK(KindOfMaskType& m) -{ - m.clear(); -} - -inline void SET_ALL_KINDOFMASK_BITS(KindOfMaskType& m) -{ - m.clear(); - m.flip(); -} - -inline void FLIP_KINDOFMASK(KindOfMaskType& m) -{ - m.flip(); -} - -// defined in Common/System/Kindof.cpp -extern KindOfMaskType KINDOFMASK_NONE; // inits to all zeroes -extern KindOfMaskType KINDOFMASK_FS; // Initializes all FS types for faction structures. -void initKindOfMasks(); - -#endif // __KINDOF_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: KindOf.h ////////////////////////////////////////////////////////////////////////// +// Author: Steven Johnson, Dec 2001 +// Desc: +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __KINDOF_H_ +#define __KINDOF_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Lib/BaseType.h" +#include "Common/BitFlags.h" +#include "Common/BitFlagsIO.h" + +//------------------------------------------------------------------------------------------------- +/** Kind of flags for determining groups of things that belong together + * NOTE: You *MUST* keep this in the same order as the KindOfNames[] below */ +//------------------------------------------------------------------------------------------------- +enum KindOfType CPP_11(: Int) +{ + KINDOF_INVALID = -1, + KINDOF_FIRST = 0, + KINDOF_OBSTACLE = KINDOF_FIRST, ///< an obstacle to land-based pathfinders + KINDOF_SELECTABLE, ///< Actually means MOUSE-INTERACTABLE (doesn't mean you can select it!) + KINDOF_IMMOBILE, ///< fixed in location + KINDOF_CAN_ATTACK, ///< can attack + KINDOF_STICK_TO_TERRAIN_SLOPE, ///< should be stuck at ground level, aligned to terrain slope. requires that IMMOBILE bit is also set. + KINDOF_CAN_CAST_REFLECTIONS, ///< can cast reflections in water + KINDOF_SHRUBBERY, ///< tree, bush, etc. + KINDOF_STRUCTURE, ///< structure of some sort (buildable or not) + KINDOF_INFANTRY, ///< unit like soldier etc + KINDOF_VEHICLE, ///< unit like tank, jeep, plane, helicopter, etc. + KINDOF_AIRCRAFT, ///< unit like plane, helicopter, etc., that is predominantly a flyer. (hovercraft are NOT aircraft) + KINDOF_HUGE_VEHICLE, ///< unit that is, technically, a vehicle, but WAY larger than normal (eg, Overlord) + KINDOF_DOZER, ///< a dozer + KINDOF_HARVESTER, ///< a harvester + KINDOF_COMMANDCENTER, ///< a command center +#ifdef ALLOW_SURRENDER + KINDOF_PRISON, ///< a prison detention center kind of thing + KINDOF_COLLECTS_PRISON_BOUNTY, ///< when prisoners are delivered to these, the player gets money + KINDOF_POW_TRUCK, ///< a pow truck can pick up and return prisoners +#endif + KINDOF_LINEBUILD, ///< wall-type thing that is built in a line + KINDOF_SALVAGER, ///< something that can create and use Salvage Crates + KINDOF_WEAPON_SALVAGER, ///< subset of salvager that can get weapon upgrades from salvage + KINDOF_TRANSPORT, ///< a true transport (has TransportContain) + KINDOF_BRIDGE, ///< a Bridge. (special structure) + KINDOF_LANDMARK_BRIDGE, ///< a landmark bridge (special bridge that isn't resizable) + KINDOF_BRIDGE_TOWER, ///< a bridge tower that we can target for bridge destruction + KINDOF_PROJECTILE, ///< Instead of being a ground or air unit, this object is special + KINDOF_PRELOAD, ///< all model data will be preloaded even if not on map + KINDOF_NO_GARRISON, ///< unit may not garrison bldgs, even if infantry bit is set + KINDOF_WAVEGUIDE, ///< water wave object + KINDOF_WAVE_EFFECT, ///< wave effect point + KINDOF_NO_COLLIDE, ///< Never collide with or be collided with + KINDOF_REPAIR_PAD, ///< is a repair pad object that can repair other machines + KINDOF_HEAL_PAD, ///< is a heal pad object that can heal flesh and bone units + KINDOF_STEALTH_GARRISON, /** enemy teams can't tell that unit is in building.. and if they + garrison that building, they stealth unit will eject. */ + KINDOF_CASH_GENERATOR, ///< used to check if the unit generates cash... checked by cash hackers and whatever else comes up + KINDOF_DRAWABLE_ONLY, ///< template is used only to create drawables (not Objects) + KINDOF_MP_COUNT_FOR_VICTORY, ///< If a player loses all his buildings that have this kindof in a multiplayer game, he loses. + KINDOF_REBUILD_HOLE, ///< a GLA rebuild hole + KINDOF_SCORE, ///< Object counts for Multiplayer scores, and short-game calculations (for buildings) + KINDOF_SCORE_CREATE, ///< Object only counts for multiplayer score for creation. + KINDOF_SCORE_DESTROY, ///< Object only counts for multiplayer score for destruction. + KINDOF_NO_HEAL_ICON, ///< do not ever display healing icons on these objects + KINDOF_CAN_RAPPEL, ///< can rappel. duh. + KINDOF_PARACHUTABLE, ///< parachutable object +#ifdef ALLOW_SURRENDER + KINDOF_CAN_SURRENDER, ///< object that can surrender +#endif + KINDOF_CAN_BE_REPULSED, ///< object that runs away from a repulsor object. + KINDOF_MOB_NEXUS, ///< object that cooyrdinates the members of a mob (i.e. GLAInfantryAngryMob) + KINDOF_IGNORED_IN_GUI, ///< object that is the members of a mob (i.e. GLAInfantryAngryMob) + KINDOF_CRATE, ///< a bonus crate + KINDOF_CAPTURABLE, ///< is "capturable" even if not an enemy (should generally be used only for structures, eg, Tech bldgs) + KINDOF_CLEARED_BY_BUILD, ///< is auto-cleared from the map when built over via construction + KINDOF_SMALL_MISSILE, ///< Missile object: ONLY USED FOR ANTI-MISSILE TARGETTING PURPOSES! Keep using PROJECTILE! + KINDOF_ALWAYS_VISIBLE, ///< is never obscured by fog of war or shroud. mostly for UI feedback objects. + KINDOF_UNATTACKABLE, ///< You cannot target this thing, it probably doesn't really exist + KINDOF_MINE, ///< a landmine. (possibly also extend to Col. Burton timed charges?) + KINDOF_CLEANUP_HAZARD, ///< radiation and bio-poison are samples of area conditions that can be cleaned up (or avoided) + KINDOF_PORTABLE_STRUCTURE, ///< Flag to identify building like subobjects an Overlord is allowed to Contain. + KINDOF_ALWAYS_SELECTABLE, ///< is never unselectable (even if effectively dead). mostly for UI feedback objects. + KINDOF_ATTACK_NEEDS_LINE_OF_SIGHT, ///< Unit has to have clear line of sight (los) to attack. + KINDOF_WALK_ON_TOP_OF_WALL, ///< Units can walk on top of a wall made of these kind of objects. + KINDOF_DEFENSIVE_WALL, ///< wall can't be driven thru, even if crusher, so pathfinder must path around it + KINDOF_FS_POWER, ///< Faction structure power building + KINDOF_FS_FACTORY, ///< Faction structure power building + KINDOF_FS_BASE_DEFENSE, ///< Faction structure base defense + KINDOF_FS_TECHNOLOGY, ///< Faction structure technology building + KINDOF_AIRCRAFT_PATH_AROUND, ///< Tall enough that aircraft need to path around this. + KINDOF_LOW_OVERLAPPABLE, ///< When overlapped, things always overlap at a 'low' height rather than our object geom + KINDOF_FORCEATTACKABLE, ///< unit is always attackable via force-attack, even if not selectable + KINDOF_AUTO_RALLYPOINT, ///< When immobile-structure-object is selected, left clicking on ground will set new rally point without requiring command button. + KINDOF_TECH_BUILDING, ///< Neutral tech building - Oil derrick, Hospital, Radio Station, Refinery. + KINDOF_POWERED, ///< This object gets the Underpowered disabled condition when its owning player has power consumption exceed supply + KINDOF_PRODUCED_AT_HELIPAD, ///< ugh... hacky fix for comanche. (srj) + KINDOF_DRONE, ///< Object drone type -- used for filtering them out of battle plan bonuses, making un-snipable, and whatever else may come up. + KINDOF_CAN_SEE_THROUGH_STRUCTURE,///< Structure does not block line of sight. + KINDOF_BALLISTIC_MISSILE, ///< Large ballistic missiles that are specifically large enough to be targetted by base defenses. + KINDOF_CLICK_THROUGH, ///< Objects with this will never be picked by mouse interactions! + KINDOF_SUPPLY_SOURCE_ON_PREVIEW,///< Any thing that we can get "supplies" from that we want to show up on the map preview + KINDOF_PARACHUTE, ///< it's a parachute + KINDOF_GARRISONABLE_UNTIL_DESTROYED, ///< Object is capable of garrisoning troops until completely destroyed. + KINDOF_BOAT, ///< It's a boat! + KINDOF_IMMUNE_TO_CAPTURE, ///< Under no circumstances can this building ever be captured. + KINDOF_HULK, ///< Hulk types so we can do special things to them via scripts or other things that may come up. + KINDOF_SHOW_PORTRAIT_WHEN_CONTROLLED, ///< Only shows portraits when controlled. + KINDOF_SPAWNS_ARE_THE_WEAPONS, ///< Evaluate the spawn slaves as this object's weapons. + KINDOF_CANNOT_BUILD_NEAR_SUPPLIES, ///< you can't be built "too close" to anything that provides supplies + KINDOF_SUPPLY_SOURCE, ///< this object provides supplies + KINDOF_REVEAL_TO_ALL, ///< this object reveals shroud for all players + KINDOF_DISGUISER, ///< This object has the ability to disguise. + KINDOF_INERT, ///< this object shouldn't be considered for any sort of interaction with any player. + KINDOF_HERO, ///< Any of the single-instance infantry, JarmenKell, BlackLotus, ColonelBurton + KINDOF_IGNORES_SELECT_ALL, ///< Too late to figure out intelligently if something should respond to a Select All command + KINDOF_DONT_AUTO_CRUSH_INFANTRY, ///< These units don't try to crush the infantry if ai. + KINDOF_CLIFF_JUMPER, ///< Can't climb cliffs, but can jump off of them. + KINDOF_FS_SUPPLY_DROPZONE, ///< A supply dropzone. + KINDOF_FS_SUPERWEAPON, ///< A superweapon structure like a nuke silo, particle uplink cannon, scudstorm. + KINDOF_FS_BLACK_MARKET, ///< Is this object a black market? + KINDOF_FS_SUPPLY_CENTER, ///< Is this object a supply center? + KINDOF_FS_STRATEGY_CENTER, ///< Is this object a strategy center? + KINDOF_MONEY_HACKER, ///< Unit that generates money from air. Needed for things that directly power them up. + KINDOF_ARMOR_SALVAGER, ///< subset of salvager that can get armor upgrades from salvage + KINDOF_REVEALS_ENEMY_PATHS, ///< like the listening outpost... when selected, any enemy drawable will draw show paths when moused over + KINDOF_BOOBY_TRAP, ///< A sticky bomb that gets set off by 5 random and unrelated events. + KINDOF_FS_FAKE, ///< Fake structure! + KINDOF_FS_INTERNET_CENTER, ///< Internet Center. + KINDOF_BLAST_CRATER, ///< deeply gouges out the terrain under object footprint + KINDOF_PROP, ///< A prop, visual only, doesn't interact with other objects (rock, street sign, inert fire hydrant) + KINDOF_OPTIMIZED_TREE, ///< An optimized, client side only tree. (The only good kind of tree. jba) + KINDOF_FS_ADVANCED_TECH, ///< Represents each faction's advanced techtree building -- strategy center, propaganda center, and palace. + KINDOF_FS_BARRACKS, ///< A barracks + KINDOF_FS_WARFACTORY, ///< A war factory or arms dealer. + KINDOF_FS_AIRFIELD, ///< An airfield. + KINDOF_AIRCRAFT_CARRIER, ///< An aircraft carrier. + KINDOF_NO_SELECT, ///< Can't select it but you can mouse over it to see it's health (drones!) + KINDOF_REJECT_UNMANNED, ///< Unit cannot enter an unmanned vehicle. + KINDOF_CANNOT_RETALIATE, ///< Unit will not retaliate if asked. + KINDOF_TECH_BASE_DEFENSE, ///< Tech Building that acts as base defence when captured + KINDOF_EMP_HARDENED, ///< Like a delivery plane (B52, B3, CargoPlane,etc.) or a SpectreGunship, which sort-of IS the weapon... + KINDOF_DEMOTRAP, ///< Added strictly only for disarming purposes. They don't act like mines which have rendering and selection implications! + KINDOF_CONSERVATIVE_BUILDING, ///< Conservative structures aren't considered part of your base for sneak attack boundary calculations... + KINDOF_IGNORE_DOCKING_BONES, ///< Structure will not look up docking bones. Patch 1.03 hack. + + // NEW KINDOFs + + KINDOF_VTOL, + KINDOF_LARGE_AIRCRAFT, + KINDOF_MEDIUM_AIRCRAFT, + KINDOF_SMALL_AIRCRAFT, + KINDOF_ARTILLERY, + KINDOF_HEAVY_ARTILLERY, + KINDOF_ANTI_AIR, + KINDOF_SCOUT, + KINDOF_COMMANDO, + KINDOF_HEAVY_INFANTRY, + KINDOF_SUPERHEAVY_VEHICLE, + + KINDOF_EXTRA1, + KINDOF_EXTRA2, + KINDOF_EXTRA3, + KINDOF_EXTRA4, + KINDOF_EXTRA5, + KINDOF_EXTRA6, + KINDOF_EXTRA7, + KINDOF_EXTRA8, + KINDOF_EXTRA9, + KINDOF_EXTRA10, + KINDOF_EXTRA11, + KINDOF_EXTRA12, + KINDOF_EXTRA13, + KINDOF_EXTRA14, + KINDOF_EXTRA15, + KINDOF_EXTRA16, + + + KINDOF_COUNT // total number of kindofs + +}; + +typedef BitFlags KindOfMaskType; + +#define MAKE_KINDOF_MASK(k) KindOfMaskType(KindOfMaskType::kInit, (k)) + +inline Bool TEST_KINDOFMASK(const KindOfMaskType& m, KindOfType t) +{ + return m.test(t); +} + +inline Bool TEST_KINDOFMASK_ANY(const KindOfMaskType& m, const KindOfMaskType& mask) +{ + return m.anyIntersectionWith(mask); +} + +inline Bool TEST_KINDOFMASK_MULTI(const KindOfMaskType& m, const KindOfMaskType& mustBeSet, const KindOfMaskType& mustBeClear) +{ + return m.testSetAndClear(mustBeSet, mustBeClear); +} + +inline Bool KINDOFMASK_ANY_SET(const KindOfMaskType& m) +{ + return m.any(); +} + +inline void CLEAR_KINDOFMASK(KindOfMaskType& m) +{ + m.clear(); +} + +inline void SET_ALL_KINDOFMASK_BITS(KindOfMaskType& m) +{ + m.clear(); + m.flip(); +} + +inline void FLIP_KINDOFMASK(KindOfMaskType& m) +{ + m.flip(); +} + +// defined in Common/System/Kindof.cpp +extern KindOfMaskType KINDOFMASK_NONE; // inits to all zeroes +extern KindOfMaskType KINDOFMASK_FS; // Initializes all FS types for faction structures. +void initKindOfMasks(); + +#endif // __KINDOF_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h index ed131fa618c..a60a7d5a7e9 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h @@ -1,511 +1,511 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: Locomotor.h ///////////////////////////////////////////////////////////////////////////////// -// Author: Steven Johnson, Feb 2002 -// Desc: Locomotor Descriptions -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __Locomotor_H_ -#define __Locomotor_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "Common/NameKeyGenerator.h" -#include "Common/Override.h" -#include "Common/Snapshot.h" -#include "GameLogic/Damage.h" -#include "GameLogic/LocomotorSet.h" - -// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// -class Locomotor; -class LocomotorTemplate; -class INI; -class PhysicsBehavior; -enum BodyDamageType CPP_11(: Int); -enum PhysicsTurningType CPP_11(: Int); - -// if we ever re-enable jets circling for landing, we need this. so keep in around just in case. (srj) -#define NO_CIRCLE_FOR_LANDING - -//------------------------------------------------------------------------------------------------- -enum LocomotorAppearance CPP_11(: Int) -{ - LOCO_LEGS_TWO, - LOCO_WHEELS_FOUR, - LOCO_TREADS, - LOCO_HOVER, - LOCO_THRUST, - LOCO_WINGS, - LOCO_CLIMBER, // human climber - backs down cliffs. - LOCO_OTHER, - LOCO_MOTORCYCLE -}; - -enum LocomotorPriority CPP_11(: Int) -{ - LOCO_MOVES_BACK=0, // In a group, this one moves toward the back - LOCO_MOVES_MIDDLE=1, // In a group, this one stays in the middle - LOCO_MOVES_FRONT=2 // In a group, this one moves toward the front of the group -}; - -#ifdef DEFINE_LOCO_APPEARANCE_NAMES -static const char *TheLocomotorAppearanceNames[] = -{ - "TWO_LEGS", - "FOUR_WHEELS", - "TREADS", - "HOVER", - "THRUST", - "WINGS", - "CLIMBER", - "OTHER", - "MOTORCYCLE", - - NULL -}; -#endif - -//------------------------------------------------------------------------------------------------- -enum LocomotorBehaviorZ CPP_11(: Int) -{ - Z_NO_Z_MOTIVE_FORCE, // does whatever physics tells it, but has no z-force of its own. - Z_SEA_LEVEL, // keep at surface-of-water level - Z_SURFACE_RELATIVE_HEIGHT, // try to follow a specific height relative to terrain/water height - Z_ABSOLUTE_HEIGHT, // try follow a specific height regardless of terrain/water height - Z_FIXED_SURFACE_RELATIVE_HEIGHT, // stays fixed at surface-rel height, regardless of physics - Z_FIXED_ABSOLUTE_HEIGHT, // stays fixed at absolute height, regardless of physics - Z_RELATIVE_TO_GROUND_AND_BUILDINGS, // stays fixed at surface-rel height including buildings, regardless of physics - Z_SMOOTH_RELATIVE_TO_HIGHEST_LAYER // try to follow a height relative to the highest layer. -}; - -#ifdef DEFINE_LOCO_Z_NAMES -static const char *TheLocomotorBehaviorZNames[] = -{ - "NO_Z_MOTIVE_FORCE", - "SEA_LEVEL", - "SURFACE_RELATIVE_HEIGHT", - "ABSOLUTE_HEIGHT", - "FIXED_SURFACE_RELATIVE_HEIGHT", - "FIXED_ABSOLUTE_HEIGHT", - "FIXED_RELATIVE_TO_GROUND_AND_BUILDINGS", - "RELATIVE_TO_HIGHEST_LAYER", - - NULL -}; -#endif - -//------------------------------------------------------------------------------------------------- -class LocomotorTemplate : public Overridable -{ - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( LocomotorTemplate, "LocomotorTemplate" ) - friend class Locomotor; - -public: - - LocomotorTemplate(); - - /// field table for loading the values from an INI - const FieldParse* getFieldParse() const; - - void friend_setName(const AsciiString& n) { m_name = n; } - - void validate(); - -protected: - - -private: - /** - Units check: - - -- Velocity: dist/frame - -- Acceleration: dist/(frame*frame) - -- Forces: (mass*dist)/(frame*frame) - */ - AsciiString m_name; - LocomotorSurfaceTypeMask m_surfaces; ///< flags indicating the kinds of surfaces we can use - Real m_maxSpeed; ///< max speed - Real m_maxSpeedDamaged; ///< max speed when "damaged" - Real m_minSpeed; ///< we should never brake past this - Real m_maxTurnRate; ///< max rate at which we can turn, in rads/frame - Real m_maxTurnRateDamaged; ///< max turn rate when "damaged" - Real m_acceleration; ///< max acceleration - Real m_accelerationDamaged; ///< max acceleration when damaged - Real m_lift; ///< max lifting acceleration (flying objects only) - Real m_liftDamaged; ///< max lift when damaged - Real m_braking; ///< max braking (deceleration) - Real m_minTurnSpeed; ///< we must be going >= this speed in order to turn - Real m_preferredHeight; ///< our preferred height (if flying) - Real m_preferredHeightDamping; ///< how aggressively to adjust to preferred height: 1.0 = very much so, 0.1 = gradually, etc - Real m_circlingRadius; ///< for flying things, the radius at which they circle their "maintain" destination. (pos = cw, neg = ccw, 0 = smallest possible) - Real m_speedLimitZ; ///< try to avoid going up or down at more than this speed, if possible - Real m_extra2DFriction; ///< extra 2dfriction to apply (via Physics) - Real m_maxThrustAngle; ///< THRUST locos only: how much we deflect our thrust angle - LocomotorBehaviorZ m_behaviorZ; ///< z-axis behavior - LocomotorAppearance m_appearance; ///< how we should diddle the Drawable to imitate this motion - LocomotorPriority m_movePriority; ///< Where we move - front, middle, back. - - Real m_accelPitchLimit; ///< Maximum amount we will pitch up under acceleration (including recoil.) - Real m_decelPitchLimit; ///< Maximum amount we will pitch down under deceleration (including recoil.) - Real m_bounceKick; ///< How much simulating rough terrain "bounces" a wheel up. - Real m_pitchStiffness; ///< How stiff the springs are forward & back. - Real m_rollStiffness; ///< How stiff the springs are side to side. - Real m_pitchDamping; ///< How good the shock absorbers are. - Real m_rollDamping; ///< How good the shock absorbers are. - Real m_pitchByZVelCoef; ///< How much we pitch in response to z-speed. - Real m_thrustRoll; ///< Thrust roll around X axis - Real m_wobbleRate; ///< how fast thrust things "wobble" - Real m_minWobble; ///< how much thrust things "wobble" - Real m_maxWobble; ///< how much thrust things "wobble" - Real m_forwardVelCoef; ///< How much we pitch in response to speed. - Real m_lateralVelCoef; ///< How much we roll in response to speed. - Real m_forwardAccelCoef; ///< How much we pitch in response to acceleration. - Real m_lateralAccelCoef; ///< How much we roll in response to acceleration. - Real m_uniformAxialDamping; ///< For Attenuating the pitch and roll rates - Real m_turnPivotOffset; ///< should we pivot around noncenter? (-1.0 = rear, 0.0 = center, 1.0 = front) - Int m_airborneTargetingHeight; ///< The height transition at witch I should mark myself as a AA target. - - Real m_closeEnoughDist; ///< How close we have to approach the end of a path before stopping - Bool m_isCloseEnoughDist3D; ///< And is that calculation 3D, for very rare cases that need to move straight down. - Real m_ultraAccurateSlideIntoPlaceFactor; ///< how much we can fudge turning when ultra-accurate - - Bool m_locomotorWorksWhenDead; ///< should locomotor continue working even when object is "dead"? - Bool m_allowMotiveForceWhileAirborne; ///< can we apply motive when airborne? - Bool m_apply2DFrictionWhenAirborne; // apply "2d friction" even when airborne... useful for realistic-looking movement - Bool m_downhillOnly; // pinewood derby, moves only by gravity pulling downhill - Bool m_stickToGround; // if true, can't leave ground - Bool m_canMoveBackward; // if true, can move backwards. - Bool m_hasSuspension; ///< If true, calculate 4 wheel independent suspension values. - Real m_maximumWheelExtension; ///< Maximum distance wheels can move down. (negative value) - Real m_maximumWheelCompression; ///< Maximum distance wheels can move up. (positive value) - Real m_wheelTurnAngle; ///< How far the front wheels can turn. - - // Fields for wander locomotor - Real m_wanderWidthFactor; - Real m_wanderLengthFactor; - Real m_wanderAboutPointRadius; - - - Real m_rudderCorrectionDegree; - Real m_rudderCorrectionRate; - Real m_elevatorCorrectionDegree; - Real m_elevatorCorrectionRate; -}; - -typedef OVERRIDE LocomotorTemplateOverride; - -// --------------------------------------------------------- -class Locomotor : public MemoryPoolObject, public Snapshot -{ - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(Locomotor, "Locomotor" ) - - friend class LocomotorStore; - -public: - - void setPhysicsOptions(Object* obj); - - void locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalPos, - Real onPathDistToGoal, Real desiredSpeed, Bool *blocked); - void locoUpdate_moveTowardsAngle(Object* obj, Real angle); - /** - Kill any current (2D) velocity (but stay at current position, or as close as possible) - - return true if we can maintain the position without being called every frame (eg, we are - resting on the ground), false if not (eg, we are hovering or circling) - */ - Bool locoUpdate_maintainCurrentPosition(Object* obj); - - Real getMaxSpeedForCondition(BodyDamageType condition) const; ///< get max speed given condition - Real getMaxTurnRate(BodyDamageType condition) const; ///< get max turning rate given condition - Real getMaxAcceleration(BodyDamageType condition) const; ///< get acceleration given condition - Real getMaxLift(BodyDamageType condition) const; ///< get acceleration given condition - Real getBraking() const; ///< get braking given condition - - inline Real getPreferredHeight() const { return m_preferredHeight;} ///< Just return preferredheight, no damage consideration - inline void restorePreferredHeightFromTemplate() { m_preferredHeight = m_template->m_preferredHeight; }; - inline Real getPreferredHeightDamping() const { return m_preferredHeightDamping;} - inline LocomotorAppearance getAppearance() const { return m_template->m_appearance; } - inline LocomotorPriority getMovePriority() const { return m_template->m_movePriority; } - inline LocomotorSurfaceTypeMask getLegalSurfaces() const { return m_template->m_surfaces; } - - inline AsciiString getTemplateName() const { return m_template->m_name;} - inline Real getMinSpeed() const { return m_template->m_minSpeed;} - inline Real getAccelPitchLimit() const { return m_template->m_accelPitchLimit;} ///< Maximum amount we will pitch up or down under acceleration (including recoil.) - inline Real getDecelPitchLimit() const { return m_template->m_decelPitchLimit;} ///< Maximum amount we will pitch down under deceleration (including recoil.) - inline Real getBounceKick() const { return m_template->m_bounceKick;} ///< How much simulating rough terrain "bounces" a wheel up. - inline Real getPitchStiffness() const { return m_template->m_pitchStiffness;} ///< How stiff the springs are forward & back. - inline Real getRollStiffness() const { return m_template->m_rollStiffness;} ///< How stiff the springs are side to side. - inline Real getPitchDamping() const { return m_template->m_pitchDamping;} ///< How good the shock absorbers are. - inline Real getRollDamping() const { return m_template->m_rollDamping;} ///< How good the shock absorbers are. - inline Real getPitchByZVelCoef() const { return m_template->m_pitchByZVelCoef;} ///< How much we pitch in response to speed. - inline Real getThrustRoll() const { return m_template->m_thrustRoll; } ///< Thrust roll - inline Real getWobbleRate() const { return m_template->m_wobbleRate; } ///< how fast thrust things "wobble" - inline Real getMaxWobble() const { return m_template->m_maxWobble; } ///< how much thrust things "wobble" - inline Real getMinWobble() const { return m_template->m_minWobble; } ///< how much thrust things "wobble" - - inline Real getForwardVelCoef() const { return m_template->m_forwardVelCoef;} ///< How much we pitch in response to speed. - inline Real getLateralVelCoef() const { return m_template->m_lateralVelCoef;} ///< How much we roll in response to speed. - inline Real getForwardAccelCoef() const { return m_template->m_forwardAccelCoef;} ///< How much we pitch in response to acceleration. - inline Real getLateralAccelCoef() const { return m_template->m_lateralAccelCoef;} ///< How much we roll in response to acceleration. - inline Real getUniformAxialDamping() const { return m_template->m_uniformAxialDamping;} ///< How much we roll in response to acceleration. - inline Real getTurnPivotOffset() const { return m_template->m_turnPivotOffset;} - inline Bool getApply2DFrictionWhenAirborne() const { return m_template->m_apply2DFrictionWhenAirborne; } - inline Bool getIsDownhillOnly() const { return m_template->m_downhillOnly; } - inline Bool getAllowMotiveForceWhileAirborne() const { return m_template->m_allowMotiveForceWhileAirborne; } - inline Int getAirborneTargetingHeight() const { return m_template->m_airborneTargetingHeight; } - inline Bool getLocomotorWorksWhenDead() const { return m_template->m_locomotorWorksWhenDead; } - inline Bool getStickToGround() const { return m_template->m_stickToGround; } - inline Real getCloseEnoughDist() const { return m_closeEnoughDist; } - inline Bool isCloseEnoughDist3D() const { return getFlag(IS_CLOSE_ENOUGH_DIST_3D); } - inline Bool hasSuspension() const {return m_template->m_hasSuspension;} - inline Bool canMoveBackwards() const {return m_template->m_canMoveBackward;} - inline Real getMaxWheelExtension() const {return m_template->m_maximumWheelExtension;} - inline Real getMaxWheelCompression() const {return m_template->m_maximumWheelCompression;} - inline Real getWheelTurnAngle() const {return m_template->m_wheelTurnAngle;} - - - inline Real getRudderCorrectionDegree() const { return m_template->m_rudderCorrectionDegree;} ///< How much we roll in response to acceleration. - inline Real getRudderCorrectionRate() const { return m_template->m_rudderCorrectionRate;} ///< How much we roll in response to acceleration. - inline Real getElevatorCorrectionDegree() const { return m_template->m_elevatorCorrectionDegree;} ///< How much we roll in response to acceleration. - inline Real getElevatorCorrectionRate() const { return m_template->m_elevatorCorrectionRate;} ///< How much we roll in response to acceleration. - - - inline Real getWanderWidthFactor() const {return m_template->m_wanderWidthFactor;} - inline Real getWanderAboutPointRadius() const {return m_template->m_wanderAboutPointRadius;} - - Real calcMinTurnRadius(BodyDamageType condition, Real* timeToTravelThatDist) const; - - /// this is handy for doing things like forcing helicopters to crash realistically: cut their lift. - inline void setMaxLift(Real lift) { m_maxLift = lift; } - inline void setMaxSpeed(Real speed) - { - DEBUG_ASSERTCRASH(!(speed <= 0.0f && m_template->m_appearance == LOCO_THRUST), ("THRUST locos may not have zero speeds!\n")); - m_maxSpeed = speed; - } - inline void setMaxAcceleration(Real accel) { m_maxAccel = accel; } - inline void setMaxBraking(Real braking) { m_maxBraking = braking; } - inline void setMaxTurnRate(Real turn) { m_maxTurnRate = turn; } - inline void setAllowInvalidPosition(Bool allow) { setFlag(ALLOW_INVALID_POSITION, allow); } - inline void setCloseEnoughDist( Real dist ) { m_closeEnoughDist = dist; } - inline void setCloseEnoughDist3D( Bool setting ) { setFlag(IS_CLOSE_ENOUGH_DIST_3D, setting); } - inline Bool isInvalidPositionAllowed() const { return getFlag( ALLOW_INVALID_POSITION ); } - - inline void setPreferredHeight( Real height ) { m_preferredHeight = height; } - -#ifdef CIRCLE_FOR_LANDING - /** - if we are climbing/diving more than this, circle as needed rather - than just diving or climbing directly. (only useful for Winged things) - */ - inline void setAltitudeChangeThresholdForCircling(Real a) { m_circleThresh = a; } -#endif - - /** - when off (the default), things get to adjust their z-pos as their - loco says (in particular, airborne things tend to try to fly at a preferred height). - - when on, they do their best to reach the specified zpos, even if it's not at their preferred height. - this is used mainly for force missiles to swoop in on their target, and to force airplane takeoff/landing - to go smoothly. - */ - inline void setUsePreciseZPos(Bool u) { setFlag(PRECISE_Z_POS, u); } - - /** - when off (the default), units slow down as they approach their target. - - when on, units go full speed till the end, and may overshoot their target. - this is useful mainly in some weird, temporary situations where we know we are - going to follow this move with another one... or for carbombs. - */ - inline void setNoSlowDownAsApproachingDest(Bool u) { setFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST, u); } - - /** - when off (the default), units do their normal stuff. - - when on, we cheat and make very precise motion, regardless of loco settings. - this is accomplished by cranking up the unit's turning rate, friction, lift (for airborne things), - and possibly other things. This is useful mainly when doing maneuvers where precision - is VITAL, such as airplane takeoff/landing. - - For ground units, it also allows units to have a destination off of a pathfing grid. - - */ - inline void setUltraAccurate(Bool u) { setFlag(ULTRA_ACCURATE, u); } - inline Bool isUltraAccurate() const { return getFlag(ULTRA_ACCURATE); } - - inline Bool isMovingBackwards(void) const {return getFlag(MOVING_BACKWARDS);} - - void startMove(void); ///< Indicates that a move is starting, primarily to reset the donut timer. jba. - - static Real getSurfaceHtAtPt(Real x, Real y); - -protected: - void moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionLegsWander(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionHover(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - - void maintainCurrentPositionThrust(Object* obj, PhysicsBehavior *physics); - void maintainCurrentPositionOther(Object* obj, PhysicsBehavior *physics); - void maintainCurrentPositionLegs(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } - void maintainCurrentPositionWheels(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } - void maintainCurrentPositionTreads(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } - void maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physics); - void maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physics); - - PhysicsTurningType rotateTowardsPosition(Object* obj, const Coord3D& goalPos, Real *relAngle=NULL); - - /* - return true if we can maintain the position without being called every frame (eg, we are - resting on the ground), false if not (eg, we are hovering or circling) - */ - Bool handleBehaviorZ(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos); - PhysicsTurningType rotateObjAroundLocoPivot(Object* obj, const Coord3D& goalPos, Real maxTurnRate, Real *relAngle = NULL); - - Real calcLiftToUseAtPt(Object* obj, PhysicsBehavior *physics, Real curZ, Real surfaceAtPt, Real preferredHeight); - - Bool fixInvalidPosition(Object* obj, PhysicsBehavior *physics); - -protected: - // snapshot methods - virtual void crc( Xfer *xfer ); - virtual void xfer( Xfer *xfer ); - virtual void loadPostProcess( void ); - -protected: - - Locomotor(const LocomotorTemplate* tmpl); - - // Note, "Law of the Big Three" applies here - //Locomotor(); -- nope, we don't have a default ctor. (srj) - Locomotor(const Locomotor& that); - Locomotor& operator=(const Locomotor& that); - //~Locomotor(); - -private: - - // - // Note: these values are saved in save files, so you MUST NOT REMOVE OR CHANGE - // existing values! - // - enum LocoFlag - { - IS_BRAKING = 0, - ALLOW_INVALID_POSITION, - MAINTAIN_POS_IS_VALID, - PRECISE_Z_POS, - NO_SLOW_DOWN_AS_APPROACHING_DEST, - OVER_WATER, // To allow things to move slower/faster over water and do special effects - ULTRA_ACCURATE, - MOVING_BACKWARDS, // If we are moving backwards. - DOING_THREE_POINT_TURN, // If we are doing a 3 pt turn. - CLIMBING, // If we are in the process of climbing. - IS_CLOSE_ENOUGH_DIST_3D, - OFFSET_INCREASING - }; - - inline Bool getFlag(LocoFlag f) const { return (m_flags & (1 << f)) != 0; } - inline void setFlag(LocoFlag f, Bool b) { if (b) m_flags |= (1< > LocomotorTemplateMap; - - LocomotorTemplateMap m_locomotorTemplates; - -}; - -// EXTERNALS ////////////////////////////////////////////////////////////////////////////////////// -extern LocomotorStore *TheLocomotorStore; - -#endif // __Locomotor_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: Locomotor.h ///////////////////////////////////////////////////////////////////////////////// +// Author: Steven Johnson, Feb 2002 +// Desc: Locomotor Descriptions +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __Locomotor_H_ +#define __Locomotor_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Common/NameKeyGenerator.h" +#include "Common/Override.h" +#include "Common/Snapshot.h" +#include "GameLogic/Damage.h" +#include "GameLogic/LocomotorSet.h" + +// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// +class Locomotor; +class LocomotorTemplate; +class INI; +class PhysicsBehavior; +enum BodyDamageType CPP_11(: Int); +enum PhysicsTurningType CPP_11(: Int); + +// if we ever re-enable jets circling for landing, we need this. so keep in around just in case. (srj) +#define NO_CIRCLE_FOR_LANDING + +//------------------------------------------------------------------------------------------------- +enum LocomotorAppearance CPP_11(: Int) +{ + LOCO_LEGS_TWO, + LOCO_WHEELS_FOUR, + LOCO_TREADS, + LOCO_HOVER, + LOCO_THRUST, + LOCO_WINGS, + LOCO_CLIMBER, // human climber - backs down cliffs. + LOCO_OTHER, + LOCO_MOTORCYCLE +}; + +enum LocomotorPriority CPP_11(: Int) +{ + LOCO_MOVES_BACK=0, // In a group, this one moves toward the back + LOCO_MOVES_MIDDLE=1, // In a group, this one stays in the middle + LOCO_MOVES_FRONT=2 // In a group, this one moves toward the front of the group +}; + +#ifdef DEFINE_LOCO_APPEARANCE_NAMES +static const char *TheLocomotorAppearanceNames[] = +{ + "TWO_LEGS", + "FOUR_WHEELS", + "TREADS", + "HOVER", + "THRUST", + "WINGS", + "CLIMBER", + "OTHER", + "MOTORCYCLE", + + NULL +}; +#endif + +//------------------------------------------------------------------------------------------------- +enum LocomotorBehaviorZ CPP_11(: Int) +{ + Z_NO_Z_MOTIVE_FORCE, // does whatever physics tells it, but has no z-force of its own. + Z_SEA_LEVEL, // keep at surface-of-water level + Z_SURFACE_RELATIVE_HEIGHT, // try to follow a specific height relative to terrain/water height + Z_ABSOLUTE_HEIGHT, // try follow a specific height regardless of terrain/water height + Z_FIXED_SURFACE_RELATIVE_HEIGHT, // stays fixed at surface-rel height, regardless of physics + Z_FIXED_ABSOLUTE_HEIGHT, // stays fixed at absolute height, regardless of physics + Z_RELATIVE_TO_GROUND_AND_BUILDINGS, // stays fixed at surface-rel height including buildings, regardless of physics + Z_SMOOTH_RELATIVE_TO_HIGHEST_LAYER // try to follow a height relative to the highest layer. +}; + +#ifdef DEFINE_LOCO_Z_NAMES +static const char *TheLocomotorBehaviorZNames[] = +{ + "NO_Z_MOTIVE_FORCE", + "SEA_LEVEL", + "SURFACE_RELATIVE_HEIGHT", + "ABSOLUTE_HEIGHT", + "FIXED_SURFACE_RELATIVE_HEIGHT", + "FIXED_ABSOLUTE_HEIGHT", + "FIXED_RELATIVE_TO_GROUND_AND_BUILDINGS", + "RELATIVE_TO_HIGHEST_LAYER", + + NULL +}; +#endif + +//------------------------------------------------------------------------------------------------- +class LocomotorTemplate : public Overridable +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( LocomotorTemplate, "LocomotorTemplate" ) + friend class Locomotor; + +public: + + LocomotorTemplate(); + + /// field table for loading the values from an INI + const FieldParse* getFieldParse() const; + + void friend_setName(const AsciiString& n) { m_name = n; } + + void validate(); + +protected: + + +private: + /** + Units check: + + -- Velocity: dist/frame + -- Acceleration: dist/(frame*frame) + -- Forces: (mass*dist)/(frame*frame) + */ + AsciiString m_name; + LocomotorSurfaceTypeMask m_surfaces; ///< flags indicating the kinds of surfaces we can use + Real m_maxSpeed; ///< max speed + Real m_maxSpeedDamaged; ///< max speed when "damaged" + Real m_minSpeed; ///< we should never brake past this + Real m_maxTurnRate; ///< max rate at which we can turn, in rads/frame + Real m_maxTurnRateDamaged; ///< max turn rate when "damaged" + Real m_acceleration; ///< max acceleration + Real m_accelerationDamaged; ///< max acceleration when damaged + Real m_lift; ///< max lifting acceleration (flying objects only) + Real m_liftDamaged; ///< max lift when damaged + Real m_braking; ///< max braking (deceleration) + Real m_minTurnSpeed; ///< we must be going >= this speed in order to turn + Real m_preferredHeight; ///< our preferred height (if flying) + Real m_preferredHeightDamping; ///< how aggressively to adjust to preferred height: 1.0 = very much so, 0.1 = gradually, etc + Real m_circlingRadius; ///< for flying things, the radius at which they circle their "maintain" destination. (pos = cw, neg = ccw, 0 = smallest possible) + Real m_speedLimitZ; ///< try to avoid going up or down at more than this speed, if possible + Real m_extra2DFriction; ///< extra 2dfriction to apply (via Physics) + Real m_maxThrustAngle; ///< THRUST locos only: how much we deflect our thrust angle + LocomotorBehaviorZ m_behaviorZ; ///< z-axis behavior + LocomotorAppearance m_appearance; ///< how we should diddle the Drawable to imitate this motion + LocomotorPriority m_movePriority; ///< Where we move - front, middle, back. + + Real m_accelPitchLimit; ///< Maximum amount we will pitch up under acceleration (including recoil.) + Real m_decelPitchLimit; ///< Maximum amount we will pitch down under deceleration (including recoil.) + Real m_bounceKick; ///< How much simulating rough terrain "bounces" a wheel up. + Real m_pitchStiffness; ///< How stiff the springs are forward & back. + Real m_rollStiffness; ///< How stiff the springs are side to side. + Real m_pitchDamping; ///< How good the shock absorbers are. + Real m_rollDamping; ///< How good the shock absorbers are. + Real m_pitchByZVelCoef; ///< How much we pitch in response to z-speed. + Real m_thrustRoll; ///< Thrust roll around X axis + Real m_wobbleRate; ///< how fast thrust things "wobble" + Real m_minWobble; ///< how much thrust things "wobble" + Real m_maxWobble; ///< how much thrust things "wobble" + Real m_forwardVelCoef; ///< How much we pitch in response to speed. + Real m_lateralVelCoef; ///< How much we roll in response to speed. + Real m_forwardAccelCoef; ///< How much we pitch in response to acceleration. + Real m_lateralAccelCoef; ///< How much we roll in response to acceleration. + Real m_uniformAxialDamping; ///< For Attenuating the pitch and roll rates + Real m_turnPivotOffset; ///< should we pivot around noncenter? (-1.0 = rear, 0.0 = center, 1.0 = front) + Int m_airborneTargetingHeight; ///< The height transition at witch I should mark myself as a AA target. + + Real m_closeEnoughDist; ///< How close we have to approach the end of a path before stopping + Bool m_isCloseEnoughDist3D; ///< And is that calculation 3D, for very rare cases that need to move straight down. + Real m_ultraAccurateSlideIntoPlaceFactor; ///< how much we can fudge turning when ultra-accurate + + Bool m_locomotorWorksWhenDead; ///< should locomotor continue working even when object is "dead"? + Bool m_allowMotiveForceWhileAirborne; ///< can we apply motive when airborne? + Bool m_apply2DFrictionWhenAirborne; // apply "2d friction" even when airborne... useful for realistic-looking movement + Bool m_downhillOnly; // pinewood derby, moves only by gravity pulling downhill + Bool m_stickToGround; // if true, can't leave ground + Bool m_canMoveBackward; // if true, can move backwards. + Bool m_hasSuspension; ///< If true, calculate 4 wheel independent suspension values. + Real m_maximumWheelExtension; ///< Maximum distance wheels can move down. (negative value) + Real m_maximumWheelCompression; ///< Maximum distance wheels can move up. (positive value) + Real m_wheelTurnAngle; ///< How far the front wheels can turn. + + // Fields for wander locomotor + Real m_wanderWidthFactor; + Real m_wanderLengthFactor; + Real m_wanderAboutPointRadius; + + + Real m_rudderCorrectionDegree; + Real m_rudderCorrectionRate; + Real m_elevatorCorrectionDegree; + Real m_elevatorCorrectionRate; +}; + +typedef OVERRIDE LocomotorTemplateOverride; + +// --------------------------------------------------------- +class Locomotor : public MemoryPoolObject, public Snapshot +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(Locomotor, "Locomotor" ) + + friend class LocomotorStore; + +public: + + void setPhysicsOptions(Object* obj); + + void locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalPos, + Real onPathDistToGoal, Real desiredSpeed, Bool *blocked); + void locoUpdate_moveTowardsAngle(Object* obj, Real angle); + /** + Kill any current (2D) velocity (but stay at current position, or as close as possible) + + return true if we can maintain the position without being called every frame (eg, we are + resting on the ground), false if not (eg, we are hovering or circling) + */ + Bool locoUpdate_maintainCurrentPosition(Object* obj); + + Real getMaxSpeedForCondition(BodyDamageType condition) const; ///< get max speed given condition + Real getMaxTurnRate(BodyDamageType condition) const; ///< get max turning rate given condition + Real getMaxAcceleration(BodyDamageType condition) const; ///< get acceleration given condition + Real getMaxLift(BodyDamageType condition) const; ///< get acceleration given condition + Real getBraking() const; ///< get braking given condition + + inline Real getPreferredHeight() const { return m_preferredHeight;} ///< Just return preferredheight, no damage consideration + inline void restorePreferredHeightFromTemplate() { m_preferredHeight = m_template->m_preferredHeight; }; + inline Real getPreferredHeightDamping() const { return m_preferredHeightDamping;} + inline LocomotorAppearance getAppearance() const { return m_template->m_appearance; } + inline LocomotorPriority getMovePriority() const { return m_template->m_movePriority; } + inline LocomotorSurfaceTypeMask getLegalSurfaces() const { return m_template->m_surfaces; } + + inline AsciiString getTemplateName() const { return m_template->m_name;} + inline Real getMinSpeed() const { return m_template->m_minSpeed;} + inline Real getAccelPitchLimit() const { return m_template->m_accelPitchLimit;} ///< Maximum amount we will pitch up or down under acceleration (including recoil.) + inline Real getDecelPitchLimit() const { return m_template->m_decelPitchLimit;} ///< Maximum amount we will pitch down under deceleration (including recoil.) + inline Real getBounceKick() const { return m_template->m_bounceKick;} ///< How much simulating rough terrain "bounces" a wheel up. + inline Real getPitchStiffness() const { return m_template->m_pitchStiffness;} ///< How stiff the springs are forward & back. + inline Real getRollStiffness() const { return m_template->m_rollStiffness;} ///< How stiff the springs are side to side. + inline Real getPitchDamping() const { return m_template->m_pitchDamping;} ///< How good the shock absorbers are. + inline Real getRollDamping() const { return m_template->m_rollDamping;} ///< How good the shock absorbers are. + inline Real getPitchByZVelCoef() const { return m_template->m_pitchByZVelCoef;} ///< How much we pitch in response to speed. + inline Real getThrustRoll() const { return m_template->m_thrustRoll; } ///< Thrust roll + inline Real getWobbleRate() const { return m_template->m_wobbleRate; } ///< how fast thrust things "wobble" + inline Real getMaxWobble() const { return m_template->m_maxWobble; } ///< how much thrust things "wobble" + inline Real getMinWobble() const { return m_template->m_minWobble; } ///< how much thrust things "wobble" + + inline Real getForwardVelCoef() const { return m_template->m_forwardVelCoef;} ///< How much we pitch in response to speed. + inline Real getLateralVelCoef() const { return m_template->m_lateralVelCoef;} ///< How much we roll in response to speed. + inline Real getForwardAccelCoef() const { return m_template->m_forwardAccelCoef;} ///< How much we pitch in response to acceleration. + inline Real getLateralAccelCoef() const { return m_template->m_lateralAccelCoef;} ///< How much we roll in response to acceleration. + inline Real getUniformAxialDamping() const { return m_template->m_uniformAxialDamping;} ///< How much we roll in response to acceleration. + inline Real getTurnPivotOffset() const { return m_template->m_turnPivotOffset;} + inline Bool getApply2DFrictionWhenAirborne() const { return m_template->m_apply2DFrictionWhenAirborne; } + inline Bool getIsDownhillOnly() const { return m_template->m_downhillOnly; } + inline Bool getAllowMotiveForceWhileAirborne() const { return m_template->m_allowMotiveForceWhileAirborne; } + inline Int getAirborneTargetingHeight() const { return m_template->m_airborneTargetingHeight; } + inline Bool getLocomotorWorksWhenDead() const { return m_template->m_locomotorWorksWhenDead; } + inline Bool getStickToGround() const { return m_template->m_stickToGround; } + inline Real getCloseEnoughDist() const { return m_closeEnoughDist; } + inline Bool isCloseEnoughDist3D() const { return getFlag(IS_CLOSE_ENOUGH_DIST_3D); } + inline Bool hasSuspension() const {return m_template->m_hasSuspension;} + inline Bool canMoveBackwards() const {return m_template->m_canMoveBackward;} + inline Real getMaxWheelExtension() const {return m_template->m_maximumWheelExtension;} + inline Real getMaxWheelCompression() const {return m_template->m_maximumWheelCompression;} + inline Real getWheelTurnAngle() const {return m_template->m_wheelTurnAngle;} + + + inline Real getRudderCorrectionDegree() const { return m_template->m_rudderCorrectionDegree;} ///< How much we roll in response to acceleration. + inline Real getRudderCorrectionRate() const { return m_template->m_rudderCorrectionRate;} ///< How much we roll in response to acceleration. + inline Real getElevatorCorrectionDegree() const { return m_template->m_elevatorCorrectionDegree;} ///< How much we roll in response to acceleration. + inline Real getElevatorCorrectionRate() const { return m_template->m_elevatorCorrectionRate;} ///< How much we roll in response to acceleration. + + + inline Real getWanderWidthFactor() const {return m_template->m_wanderWidthFactor;} + inline Real getWanderAboutPointRadius() const {return m_template->m_wanderAboutPointRadius;} + + Real calcMinTurnRadius(BodyDamageType condition, Real* timeToTravelThatDist) const; + + /// this is handy for doing things like forcing helicopters to crash realistically: cut their lift. + inline void setMaxLift(Real lift) { m_maxLift = lift; } + inline void setMaxSpeed(Real speed) + { + DEBUG_ASSERTCRASH(!(speed <= 0.0f && m_template->m_appearance == LOCO_THRUST), ("THRUST locos may not have zero speeds!\n")); + m_maxSpeed = speed; + } + inline void setMaxAcceleration(Real accel) { m_maxAccel = accel; } + inline void setMaxBraking(Real braking) { m_maxBraking = braking; } + inline void setMaxTurnRate(Real turn) { m_maxTurnRate = turn; } + inline void setAllowInvalidPosition(Bool allow) { setFlag(ALLOW_INVALID_POSITION, allow); } + inline void setCloseEnoughDist( Real dist ) { m_closeEnoughDist = dist; } + inline void setCloseEnoughDist3D( Bool setting ) { setFlag(IS_CLOSE_ENOUGH_DIST_3D, setting); } + inline Bool isInvalidPositionAllowed() const { return getFlag( ALLOW_INVALID_POSITION ); } + + inline void setPreferredHeight( Real height ) { m_preferredHeight = height; } + +#ifdef CIRCLE_FOR_LANDING + /** + if we are climbing/diving more than this, circle as needed rather + than just diving or climbing directly. (only useful for Winged things) + */ + inline void setAltitudeChangeThresholdForCircling(Real a) { m_circleThresh = a; } +#endif + + /** + when off (the default), things get to adjust their z-pos as their + loco says (in particular, airborne things tend to try to fly at a preferred height). + + when on, they do their best to reach the specified zpos, even if it's not at their preferred height. + this is used mainly for force missiles to swoop in on their target, and to force airplane takeoff/landing + to go smoothly. + */ + inline void setUsePreciseZPos(Bool u) { setFlag(PRECISE_Z_POS, u); } + + /** + when off (the default), units slow down as they approach their target. + + when on, units go full speed till the end, and may overshoot their target. + this is useful mainly in some weird, temporary situations where we know we are + going to follow this move with another one... or for carbombs. + */ + inline void setNoSlowDownAsApproachingDest(Bool u) { setFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST, u); } + + /** + when off (the default), units do their normal stuff. + + when on, we cheat and make very precise motion, regardless of loco settings. + this is accomplished by cranking up the unit's turning rate, friction, lift (for airborne things), + and possibly other things. This is useful mainly when doing maneuvers where precision + is VITAL, such as airplane takeoff/landing. + + For ground units, it also allows units to have a destination off of a pathfing grid. + + */ + inline void setUltraAccurate(Bool u) { setFlag(ULTRA_ACCURATE, u); } + inline Bool isUltraAccurate() const { return getFlag(ULTRA_ACCURATE); } + + inline Bool isMovingBackwards(void) const {return getFlag(MOVING_BACKWARDS);} + + void startMove(void); ///< Indicates that a move is starting, primarily to reset the donut timer. jba. + + static Real getSurfaceHtAtPt(Real x, Real y); + +protected: + void moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionLegsWander(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionHover(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + + void maintainCurrentPositionThrust(Object* obj, PhysicsBehavior *physics); + void maintainCurrentPositionOther(Object* obj, PhysicsBehavior *physics); + void maintainCurrentPositionLegs(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } + void maintainCurrentPositionWheels(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } + void maintainCurrentPositionTreads(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } + void maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physics); + void maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physics); + + PhysicsTurningType rotateTowardsPosition(Object* obj, const Coord3D& goalPos, Real *relAngle=NULL); + + /* + return true if we can maintain the position without being called every frame (eg, we are + resting on the ground), false if not (eg, we are hovering or circling) + */ + Bool handleBehaviorZ(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos); + PhysicsTurningType rotateObjAroundLocoPivot(Object* obj, const Coord3D& goalPos, Real maxTurnRate, Real *relAngle = NULL); + + Real calcLiftToUseAtPt(Object* obj, PhysicsBehavior *physics, Real curZ, Real surfaceAtPt, Real preferredHeight); + + Bool fixInvalidPosition(Object* obj, PhysicsBehavior *physics); + +protected: + // snapshot methods + virtual void crc( Xfer *xfer ); + virtual void xfer( Xfer *xfer ); + virtual void loadPostProcess( void ); + +protected: + + Locomotor(const LocomotorTemplate* tmpl); + + // Note, "Law of the Big Three" applies here + //Locomotor(); -- nope, we don't have a default ctor. (srj) + Locomotor(const Locomotor& that); + Locomotor& operator=(const Locomotor& that); + //~Locomotor(); + +private: + + // + // Note: these values are saved in save files, so you MUST NOT REMOVE OR CHANGE + // existing values! + // + enum LocoFlag + { + IS_BRAKING = 0, + ALLOW_INVALID_POSITION, + MAINTAIN_POS_IS_VALID, + PRECISE_Z_POS, + NO_SLOW_DOWN_AS_APPROACHING_DEST, + OVER_WATER, // To allow things to move slower/faster over water and do special effects + ULTRA_ACCURATE, + MOVING_BACKWARDS, // If we are moving backwards. + DOING_THREE_POINT_TURN, // If we are doing a 3 pt turn. + CLIMBING, // If we are in the process of climbing. + IS_CLOSE_ENOUGH_DIST_3D, + OFFSET_INCREASING + }; + + inline Bool getFlag(LocoFlag f) const { return (m_flags & (1 << f)) != 0; } + inline void setFlag(LocoFlag f, Bool b) { if (b) m_flags |= (1< > LocomotorTemplateMap; + + LocomotorTemplateMap m_locomotorTemplates; + +}; + +// EXTERNALS ////////////////////////////////////////////////////////////////////////////////////// +extern LocomotorStore *TheLocomotorStore; + +#endif // __Locomotor_H_ + From 6ef66e33cd660c79ba7599dc466a2fb641a134c6 Mon Sep 17 00:00:00 2001 From: Andi Date: Sun, 22 Jun 2025 21:06:54 +0200 Subject: [PATCH 25/42] Work in Progress!! --- GeneralsMD/Code/GameEngine/CMakeLists.txt | 4 + .../GameEngine/Include/Common/DisabledTypes.h | 240 +-- .../Code/GameEngine/Include/Common/KindOf.h | 514 +++--- .../Include/GameLogic/Module/AIUpdate.h | 365 ++-- .../Module/TeleportMovementBehavior.h | 81 + .../GameLogic/Module/TeleporterAIUpdate.h | 99 + .../Source/Common/System/DisabledTypes.cpp | 2 + .../Source/Common/System/KindOf.cpp | 426 ++--- .../Source/Common/System/MemoryInit.cpp | 1628 +++++++++-------- .../Source/Common/Thing/ModuleFactory.cpp | 4 + .../GameClient/MessageStream/CommandXlat.cpp | 10 +- .../Source/GameLogic/AI/AIGroup.cpp | 4 + .../Behavior/TeleportMovementBehavior.cpp | 198 ++ .../Source/GameLogic/Object/Object.cpp | 3 +- .../Update/AIUpdate/TeleporterAIUpdate.cpp | 322 ++++ 15 files changed, 2314 insertions(+), 1586 deletions(-) create mode 100644 GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleportMovementBehavior.h create mode 100644 GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleporterAIUpdate.h create mode 100644 GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/TeleportMovementBehavior.cpp create mode 100644 GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index b686155477e..8a153e389dd 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -311,6 +311,7 @@ set(GAMEENGINE_SRC Include/GameLogic/Module/DozerAIUpdate.h Include/GameLogic/Module/DumbProjectileBehavior.h Include/GameLogic/Module/FreeFallProjectileBehavior.h + Include/GameLogic/Module/TeleportMovementBehavior.h Include/GameLogic/Module/DynamicGeometryInfoUpdate.h Include/GameLogic/Module/DynamicShroudClearingRangeUpdate.h Include/GameLogic/Module/EjectPilotDie.h @@ -473,6 +474,7 @@ set(GAMEENGINE_SRC Include/GameLogic/Module/VeterancyCrateCollide.h Include/GameLogic/Module/VeterancyGainCreate.h Include/GameLogic/Module/WanderAIUpdate.h + Include/GameLogic/Module/TeleporterAIUpdate.h Include/GameLogic/Module/WaveGuideUpdate.h Include/GameLogic/Module/WeaponBonusUpdate.h Include/GameLogic/Module/ArmorDamageScalarUpdate.h @@ -856,6 +858,7 @@ set(GAMEENGINE_SRC Source/GameLogic/Object/Behavior/CountermeasuresBehavior.cpp Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp Source/GameLogic/Object/Behavior/FreeFallProjectileBehavior.cpp + Source/GameLogic/Object/Behavior/TeleportMovementBehavior.cpp Source/GameLogic/Object/Behavior/FireWeaponWhenDamagedBehavior.cpp Source/GameLogic/Object/Behavior/FireWeaponWhenDeadBehavior.cpp Source/GameLogic/Object/Behavior/FlightDeckBehavior.cpp @@ -985,6 +988,7 @@ set(GAMEENGINE_SRC Source/GameLogic/Object/Update/AIUpdate/SupplyTruckAIUpdate.cpp Source/GameLogic/Object/Update/AIUpdate/TransportAIUpdate.cpp Source/GameLogic/Object/Update/AIUpdate/WanderAIUpdate.cpp + Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp Source/GameLogic/Object/Update/AIUpdate/WorkerAIUpdate.cpp Source/GameLogic/Object/Update/AnimationSteeringUpdate.cpp Source/GameLogic/Object/Update/AssistedTargetingUpdate.cpp diff --git a/GeneralsMD/Code/GameEngine/Include/Common/DisabledTypes.h b/GeneralsMD/Code/GameEngine/Include/Common/DisabledTypes.h index 840a967851e..8dca20e543a 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/DisabledTypes.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/DisabledTypes.h @@ -1,119 +1,121 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: DisabledTypes.h ////////////////////////////////////////////////////////////////////////// -// Author: Kris Morness, September 2002 -// Desc: Defines all the types of disabled statii any given object can have. -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __DISABLED_TYPES_H_ -#define __DISABLED_TYPES_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "Lib/BaseType.h" -#include "Common/BitFlags.h" -#include "Common/BitFlagsIO.h" - -//------------------------------------------------------------------------------------------------- -/** Kind of flags for determining groups of things that belong together - * NOTE: You *MUST* keep this in the same order as the DisabledNames[] below */ -//------------------------------------------------------------------------------------------------- -enum DisabledType CPP_11(: Int) -{ - DISABLED_DEFAULT, //Typical disable -- like systems, things that don't need to run. - DISABLED_HACKED, //This unit has been hacked - DISABLED_EMP, //This unit has been disabled via electro-magnetic-pulse. - DISABLED_HELD, //Special case -- held means it can fire and isHeld checks to make sure ONLY held is set! - DISABLED_PARALYZED, //Battle plans have changed, and unit is confused/paralyzed - DISABLED_UNMANNED, //Vehicle is unmanned - DISABLED_UNDERPOWERED,//Seperate from ScriptUnderpowered, the owning player has insufficient power. Energy status controls this - DISABLED_FREEFALL, //This unit has been disabled via being in free fall - - DISABLED_AWESTRUCK, - DISABLED_BRAINWASHED, - DISABLED_SUBDUED, ///< Temporarily shut down by Subdual damage - //These ones are specificially for scripts to enable/reenable! - DISABLED_SCRIPT_DISABLED, - DISABLED_SCRIPT_UNDERPOWERED, - - DISABLED_COUNT, - - DISABLED_ANY = 65535 ///< Do not use this value for setting disabled types (read-only) -}; - -typedef BitFlags DisabledMaskType; - -#define MAKE_DISABLED_MASK(k) DisabledMaskType(DisabledMaskType::kInit, (k)) -#define MAKE_DISABLED_MASK2(k,a) DisabledMaskType(DisabledMaskType::kInit, (k), (a)) -#define MAKE_DISABLED_MASK3(k,a,b) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b)) -#define MAKE_DISABLED_MASK4(k,a,b,c) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b), (c)) -#define MAKE_DISABLED_MASK5(k,a,b,c,d) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b), (c), (d)) - -inline Bool TEST_DISABLEDMASK(const DisabledMaskType& m, DisabledType t) -{ - return m.test(t); -} - -inline Bool TEST_DISABLEDMASK_ANY(const DisabledMaskType& m, const DisabledMaskType& mask) -{ - return m.anyIntersectionWith(mask); -} - -inline Bool TEST_DISABLEDMASK_MULTI(const DisabledMaskType& m, const DisabledMaskType& mustBeSet, const DisabledMaskType& mustBeClear) -{ - return m.testSetAndClear(mustBeSet, mustBeClear); -} - -inline Bool DISABLEDMASK_ANY_SET(const DisabledMaskType& m) -{ - return m.any(); -} - -inline void CLEAR_DISABLEDMASK(DisabledMaskType& m) -{ - m.clear(); -} - -inline void SET_ALL_DISABLEDMASK_BITS(DisabledMaskType& m) -{ - m.clear(); - m.flip(); -} - -inline void FLIP_DISABLEDMASK(DisabledMaskType& m) -{ - m.flip(); -} - - - -// defined in Common/System/DisabledTypes.cpp -extern DisabledMaskType DISABLEDMASK_NONE; // inits to all zeroes -extern DisabledMaskType DISABLEDMASK_ALL; // inits to all bits set. -void initDisabledMasks(); - -#endif // __DISABLED_TYPES_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: DisabledTypes.h ////////////////////////////////////////////////////////////////////////// +// Author: Kris Morness, September 2002 +// Desc: Defines all the types of disabled statii any given object can have. +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __DISABLED_TYPES_H_ +#define __DISABLED_TYPES_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Lib/BaseType.h" +#include "Common/BitFlags.h" +#include "Common/BitFlagsIO.h" + +//------------------------------------------------------------------------------------------------- +/** Kind of flags for determining groups of things that belong together + * NOTE: You *MUST* keep this in the same order as the DisabledNames[] below */ +//------------------------------------------------------------------------------------------------- +enum DisabledType CPP_11(: Int) +{ + DISABLED_DEFAULT, //Typical disable -- like systems, things that don't need to run. + DISABLED_HACKED, //This unit has been hacked + DISABLED_EMP, //This unit has been disabled via electro-magnetic-pulse. + DISABLED_HELD, //Special case -- held means it can fire and isHeld checks to make sure ONLY held is set! + DISABLED_PARALYZED, //Battle plans have changed, and unit is confused/paralyzed + DISABLED_UNMANNED, //Vehicle is unmanned + DISABLED_UNDERPOWERED,//Seperate from ScriptUnderpowered, the owning player has insufficient power. Energy status controls this + DISABLED_FREEFALL, //This unit has been disabled via being in free fall + + DISABLED_AWESTRUCK, + DISABLED_BRAINWASHED, + DISABLED_SUBDUED, ///< Temporarily shut down by Subdual damage + //These ones are specificially for scripts to enable/reenable! + DISABLED_SCRIPT_DISABLED, + DISABLED_SCRIPT_UNDERPOWERED, + + DISABLED_TELEPORT, // Chrono Legionnaire after teleporting + + DISABLED_COUNT, + + DISABLED_ANY = 65535 ///< Do not use this value for setting disabled types (read-only) +}; + +typedef BitFlags DisabledMaskType; + +#define MAKE_DISABLED_MASK(k) DisabledMaskType(DisabledMaskType::kInit, (k)) +#define MAKE_DISABLED_MASK2(k,a) DisabledMaskType(DisabledMaskType::kInit, (k), (a)) +#define MAKE_DISABLED_MASK3(k,a,b) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b)) +#define MAKE_DISABLED_MASK4(k,a,b,c) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b), (c)) +#define MAKE_DISABLED_MASK5(k,a,b,c,d) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b), (c), (d)) + +inline Bool TEST_DISABLEDMASK(const DisabledMaskType& m, DisabledType t) +{ + return m.test(t); +} + +inline Bool TEST_DISABLEDMASK_ANY(const DisabledMaskType& m, const DisabledMaskType& mask) +{ + return m.anyIntersectionWith(mask); +} + +inline Bool TEST_DISABLEDMASK_MULTI(const DisabledMaskType& m, const DisabledMaskType& mustBeSet, const DisabledMaskType& mustBeClear) +{ + return m.testSetAndClear(mustBeSet, mustBeClear); +} + +inline Bool DISABLEDMASK_ANY_SET(const DisabledMaskType& m) +{ + return m.any(); +} + +inline void CLEAR_DISABLEDMASK(DisabledMaskType& m) +{ + m.clear(); +} + +inline void SET_ALL_DISABLEDMASK_BITS(DisabledMaskType& m) +{ + m.clear(); + m.flip(); +} + +inline void FLIP_DISABLEDMASK(DisabledMaskType& m) +{ + m.flip(); +} + + + +// defined in Common/System/DisabledTypes.cpp +extern DisabledMaskType DISABLEDMASK_NONE; // inits to all zeroes +extern DisabledMaskType DISABLEDMASK_ALL; // inits to all bits set. +void initDisabledMasks(); + +#endif // __DISABLED_TYPES_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h b/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h index 3d86987e6ac..b6ca0d67417 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h @@ -1,256 +1,258 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: KindOf.h ////////////////////////////////////////////////////////////////////////// -// Author: Steven Johnson, Dec 2001 -// Desc: -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __KINDOF_H_ -#define __KINDOF_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "Lib/BaseType.h" -#include "Common/BitFlags.h" -#include "Common/BitFlagsIO.h" - -//------------------------------------------------------------------------------------------------- -/** Kind of flags for determining groups of things that belong together - * NOTE: You *MUST* keep this in the same order as the KindOfNames[] below */ -//------------------------------------------------------------------------------------------------- -enum KindOfType CPP_11(: Int) -{ - KINDOF_INVALID = -1, - KINDOF_FIRST = 0, - KINDOF_OBSTACLE = KINDOF_FIRST, ///< an obstacle to land-based pathfinders - KINDOF_SELECTABLE, ///< Actually means MOUSE-INTERACTABLE (doesn't mean you can select it!) - KINDOF_IMMOBILE, ///< fixed in location - KINDOF_CAN_ATTACK, ///< can attack - KINDOF_STICK_TO_TERRAIN_SLOPE, ///< should be stuck at ground level, aligned to terrain slope. requires that IMMOBILE bit is also set. - KINDOF_CAN_CAST_REFLECTIONS, ///< can cast reflections in water - KINDOF_SHRUBBERY, ///< tree, bush, etc. - KINDOF_STRUCTURE, ///< structure of some sort (buildable or not) - KINDOF_INFANTRY, ///< unit like soldier etc - KINDOF_VEHICLE, ///< unit like tank, jeep, plane, helicopter, etc. - KINDOF_AIRCRAFT, ///< unit like plane, helicopter, etc., that is predominantly a flyer. (hovercraft are NOT aircraft) - KINDOF_HUGE_VEHICLE, ///< unit that is, technically, a vehicle, but WAY larger than normal (eg, Overlord) - KINDOF_DOZER, ///< a dozer - KINDOF_HARVESTER, ///< a harvester - KINDOF_COMMANDCENTER, ///< a command center -#ifdef ALLOW_SURRENDER - KINDOF_PRISON, ///< a prison detention center kind of thing - KINDOF_COLLECTS_PRISON_BOUNTY, ///< when prisoners are delivered to these, the player gets money - KINDOF_POW_TRUCK, ///< a pow truck can pick up and return prisoners -#endif - KINDOF_LINEBUILD, ///< wall-type thing that is built in a line - KINDOF_SALVAGER, ///< something that can create and use Salvage Crates - KINDOF_WEAPON_SALVAGER, ///< subset of salvager that can get weapon upgrades from salvage - KINDOF_TRANSPORT, ///< a true transport (has TransportContain) - KINDOF_BRIDGE, ///< a Bridge. (special structure) - KINDOF_LANDMARK_BRIDGE, ///< a landmark bridge (special bridge that isn't resizable) - KINDOF_BRIDGE_TOWER, ///< a bridge tower that we can target for bridge destruction - KINDOF_PROJECTILE, ///< Instead of being a ground or air unit, this object is special - KINDOF_PRELOAD, ///< all model data will be preloaded even if not on map - KINDOF_NO_GARRISON, ///< unit may not garrison bldgs, even if infantry bit is set - KINDOF_WAVEGUIDE, ///< water wave object - KINDOF_WAVE_EFFECT, ///< wave effect point - KINDOF_NO_COLLIDE, ///< Never collide with or be collided with - KINDOF_REPAIR_PAD, ///< is a repair pad object that can repair other machines - KINDOF_HEAL_PAD, ///< is a heal pad object that can heal flesh and bone units - KINDOF_STEALTH_GARRISON, /** enemy teams can't tell that unit is in building.. and if they - garrison that building, they stealth unit will eject. */ - KINDOF_CASH_GENERATOR, ///< used to check if the unit generates cash... checked by cash hackers and whatever else comes up - KINDOF_DRAWABLE_ONLY, ///< template is used only to create drawables (not Objects) - KINDOF_MP_COUNT_FOR_VICTORY, ///< If a player loses all his buildings that have this kindof in a multiplayer game, he loses. - KINDOF_REBUILD_HOLE, ///< a GLA rebuild hole - KINDOF_SCORE, ///< Object counts for Multiplayer scores, and short-game calculations (for buildings) - KINDOF_SCORE_CREATE, ///< Object only counts for multiplayer score for creation. - KINDOF_SCORE_DESTROY, ///< Object only counts for multiplayer score for destruction. - KINDOF_NO_HEAL_ICON, ///< do not ever display healing icons on these objects - KINDOF_CAN_RAPPEL, ///< can rappel. duh. - KINDOF_PARACHUTABLE, ///< parachutable object -#ifdef ALLOW_SURRENDER - KINDOF_CAN_SURRENDER, ///< object that can surrender -#endif - KINDOF_CAN_BE_REPULSED, ///< object that runs away from a repulsor object. - KINDOF_MOB_NEXUS, ///< object that cooyrdinates the members of a mob (i.e. GLAInfantryAngryMob) - KINDOF_IGNORED_IN_GUI, ///< object that is the members of a mob (i.e. GLAInfantryAngryMob) - KINDOF_CRATE, ///< a bonus crate - KINDOF_CAPTURABLE, ///< is "capturable" even if not an enemy (should generally be used only for structures, eg, Tech bldgs) - KINDOF_CLEARED_BY_BUILD, ///< is auto-cleared from the map when built over via construction - KINDOF_SMALL_MISSILE, ///< Missile object: ONLY USED FOR ANTI-MISSILE TARGETTING PURPOSES! Keep using PROJECTILE! - KINDOF_ALWAYS_VISIBLE, ///< is never obscured by fog of war or shroud. mostly for UI feedback objects. - KINDOF_UNATTACKABLE, ///< You cannot target this thing, it probably doesn't really exist - KINDOF_MINE, ///< a landmine. (possibly also extend to Col. Burton timed charges?) - KINDOF_CLEANUP_HAZARD, ///< radiation and bio-poison are samples of area conditions that can be cleaned up (or avoided) - KINDOF_PORTABLE_STRUCTURE, ///< Flag to identify building like subobjects an Overlord is allowed to Contain. - KINDOF_ALWAYS_SELECTABLE, ///< is never unselectable (even if effectively dead). mostly for UI feedback objects. - KINDOF_ATTACK_NEEDS_LINE_OF_SIGHT, ///< Unit has to have clear line of sight (los) to attack. - KINDOF_WALK_ON_TOP_OF_WALL, ///< Units can walk on top of a wall made of these kind of objects. - KINDOF_DEFENSIVE_WALL, ///< wall can't be driven thru, even if crusher, so pathfinder must path around it - KINDOF_FS_POWER, ///< Faction structure power building - KINDOF_FS_FACTORY, ///< Faction structure power building - KINDOF_FS_BASE_DEFENSE, ///< Faction structure base defense - KINDOF_FS_TECHNOLOGY, ///< Faction structure technology building - KINDOF_AIRCRAFT_PATH_AROUND, ///< Tall enough that aircraft need to path around this. - KINDOF_LOW_OVERLAPPABLE, ///< When overlapped, things always overlap at a 'low' height rather than our object geom - KINDOF_FORCEATTACKABLE, ///< unit is always attackable via force-attack, even if not selectable - KINDOF_AUTO_RALLYPOINT, ///< When immobile-structure-object is selected, left clicking on ground will set new rally point without requiring command button. - KINDOF_TECH_BUILDING, ///< Neutral tech building - Oil derrick, Hospital, Radio Station, Refinery. - KINDOF_POWERED, ///< This object gets the Underpowered disabled condition when its owning player has power consumption exceed supply - KINDOF_PRODUCED_AT_HELIPAD, ///< ugh... hacky fix for comanche. (srj) - KINDOF_DRONE, ///< Object drone type -- used for filtering them out of battle plan bonuses, making un-snipable, and whatever else may come up. - KINDOF_CAN_SEE_THROUGH_STRUCTURE,///< Structure does not block line of sight. - KINDOF_BALLISTIC_MISSILE, ///< Large ballistic missiles that are specifically large enough to be targetted by base defenses. - KINDOF_CLICK_THROUGH, ///< Objects with this will never be picked by mouse interactions! - KINDOF_SUPPLY_SOURCE_ON_PREVIEW,///< Any thing that we can get "supplies" from that we want to show up on the map preview - KINDOF_PARACHUTE, ///< it's a parachute - KINDOF_GARRISONABLE_UNTIL_DESTROYED, ///< Object is capable of garrisoning troops until completely destroyed. - KINDOF_BOAT, ///< It's a boat! - KINDOF_IMMUNE_TO_CAPTURE, ///< Under no circumstances can this building ever be captured. - KINDOF_HULK, ///< Hulk types so we can do special things to them via scripts or other things that may come up. - KINDOF_SHOW_PORTRAIT_WHEN_CONTROLLED, ///< Only shows portraits when controlled. - KINDOF_SPAWNS_ARE_THE_WEAPONS, ///< Evaluate the spawn slaves as this object's weapons. - KINDOF_CANNOT_BUILD_NEAR_SUPPLIES, ///< you can't be built "too close" to anything that provides supplies - KINDOF_SUPPLY_SOURCE, ///< this object provides supplies - KINDOF_REVEAL_TO_ALL, ///< this object reveals shroud for all players - KINDOF_DISGUISER, ///< This object has the ability to disguise. - KINDOF_INERT, ///< this object shouldn't be considered for any sort of interaction with any player. - KINDOF_HERO, ///< Any of the single-instance infantry, JarmenKell, BlackLotus, ColonelBurton - KINDOF_IGNORES_SELECT_ALL, ///< Too late to figure out intelligently if something should respond to a Select All command - KINDOF_DONT_AUTO_CRUSH_INFANTRY, ///< These units don't try to crush the infantry if ai. - KINDOF_CLIFF_JUMPER, ///< Can't climb cliffs, but can jump off of them. - KINDOF_FS_SUPPLY_DROPZONE, ///< A supply dropzone. - KINDOF_FS_SUPERWEAPON, ///< A superweapon structure like a nuke silo, particle uplink cannon, scudstorm. - KINDOF_FS_BLACK_MARKET, ///< Is this object a black market? - KINDOF_FS_SUPPLY_CENTER, ///< Is this object a supply center? - KINDOF_FS_STRATEGY_CENTER, ///< Is this object a strategy center? - KINDOF_MONEY_HACKER, ///< Unit that generates money from air. Needed for things that directly power them up. - KINDOF_ARMOR_SALVAGER, ///< subset of salvager that can get armor upgrades from salvage - KINDOF_REVEALS_ENEMY_PATHS, ///< like the listening outpost... when selected, any enemy drawable will draw show paths when moused over - KINDOF_BOOBY_TRAP, ///< A sticky bomb that gets set off by 5 random and unrelated events. - KINDOF_FS_FAKE, ///< Fake structure! - KINDOF_FS_INTERNET_CENTER, ///< Internet Center. - KINDOF_BLAST_CRATER, ///< deeply gouges out the terrain under object footprint - KINDOF_PROP, ///< A prop, visual only, doesn't interact with other objects (rock, street sign, inert fire hydrant) - KINDOF_OPTIMIZED_TREE, ///< An optimized, client side only tree. (The only good kind of tree. jba) - KINDOF_FS_ADVANCED_TECH, ///< Represents each faction's advanced techtree building -- strategy center, propaganda center, and palace. - KINDOF_FS_BARRACKS, ///< A barracks - KINDOF_FS_WARFACTORY, ///< A war factory or arms dealer. - KINDOF_FS_AIRFIELD, ///< An airfield. - KINDOF_AIRCRAFT_CARRIER, ///< An aircraft carrier. - KINDOF_NO_SELECT, ///< Can't select it but you can mouse over it to see it's health (drones!) - KINDOF_REJECT_UNMANNED, ///< Unit cannot enter an unmanned vehicle. - KINDOF_CANNOT_RETALIATE, ///< Unit will not retaliate if asked. - KINDOF_TECH_BASE_DEFENSE, ///< Tech Building that acts as base defence when captured - KINDOF_EMP_HARDENED, ///< Like a delivery plane (B52, B3, CargoPlane,etc.) or a SpectreGunship, which sort-of IS the weapon... - KINDOF_DEMOTRAP, ///< Added strictly only for disarming purposes. They don't act like mines which have rendering and selection implications! - KINDOF_CONSERVATIVE_BUILDING, ///< Conservative structures aren't considered part of your base for sneak attack boundary calculations... - KINDOF_IGNORE_DOCKING_BONES, ///< Structure will not look up docking bones. Patch 1.03 hack. - - // NEW KINDOFs - - KINDOF_VTOL, - KINDOF_LARGE_AIRCRAFT, - KINDOF_MEDIUM_AIRCRAFT, - KINDOF_SMALL_AIRCRAFT, - KINDOF_ARTILLERY, - KINDOF_HEAVY_ARTILLERY, - KINDOF_ANTI_AIR, - KINDOF_SCOUT, - KINDOF_COMMANDO, - KINDOF_HEAVY_INFANTRY, - KINDOF_SUPERHEAVY_VEHICLE, - - KINDOF_EXTRA1, - KINDOF_EXTRA2, - KINDOF_EXTRA3, - KINDOF_EXTRA4, - KINDOF_EXTRA5, - KINDOF_EXTRA6, - KINDOF_EXTRA7, - KINDOF_EXTRA8, - KINDOF_EXTRA9, - KINDOF_EXTRA10, - KINDOF_EXTRA11, - KINDOF_EXTRA12, - KINDOF_EXTRA13, - KINDOF_EXTRA14, - KINDOF_EXTRA15, - KINDOF_EXTRA16, - - - KINDOF_COUNT // total number of kindofs - -}; - -typedef BitFlags KindOfMaskType; - -#define MAKE_KINDOF_MASK(k) KindOfMaskType(KindOfMaskType::kInit, (k)) - -inline Bool TEST_KINDOFMASK(const KindOfMaskType& m, KindOfType t) -{ - return m.test(t); -} - -inline Bool TEST_KINDOFMASK_ANY(const KindOfMaskType& m, const KindOfMaskType& mask) -{ - return m.anyIntersectionWith(mask); -} - -inline Bool TEST_KINDOFMASK_MULTI(const KindOfMaskType& m, const KindOfMaskType& mustBeSet, const KindOfMaskType& mustBeClear) -{ - return m.testSetAndClear(mustBeSet, mustBeClear); -} - -inline Bool KINDOFMASK_ANY_SET(const KindOfMaskType& m) -{ - return m.any(); -} - -inline void CLEAR_KINDOFMASK(KindOfMaskType& m) -{ - m.clear(); -} - -inline void SET_ALL_KINDOFMASK_BITS(KindOfMaskType& m) -{ - m.clear(); - m.flip(); -} - -inline void FLIP_KINDOFMASK(KindOfMaskType& m) -{ - m.flip(); -} - -// defined in Common/System/Kindof.cpp -extern KindOfMaskType KINDOFMASK_NONE; // inits to all zeroes -extern KindOfMaskType KINDOFMASK_FS; // Initializes all FS types for faction structures. -void initKindOfMasks(); - -#endif // __KINDOF_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: KindOf.h ////////////////////////////////////////////////////////////////////////// +// Author: Steven Johnson, Dec 2001 +// Desc: +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __KINDOF_H_ +#define __KINDOF_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Lib/BaseType.h" +#include "Common/BitFlags.h" +#include "Common/BitFlagsIO.h" + +//------------------------------------------------------------------------------------------------- +/** Kind of flags for determining groups of things that belong together + * NOTE: You *MUST* keep this in the same order as the KindOfNames[] below */ +//------------------------------------------------------------------------------------------------- +enum KindOfType CPP_11(: Int) +{ + KINDOF_INVALID = -1, + KINDOF_FIRST = 0, + KINDOF_OBSTACLE = KINDOF_FIRST, ///< an obstacle to land-based pathfinders + KINDOF_SELECTABLE, ///< Actually means MOUSE-INTERACTABLE (doesn't mean you can select it!) + KINDOF_IMMOBILE, ///< fixed in location + KINDOF_CAN_ATTACK, ///< can attack + KINDOF_STICK_TO_TERRAIN_SLOPE, ///< should be stuck at ground level, aligned to terrain slope. requires that IMMOBILE bit is also set. + KINDOF_CAN_CAST_REFLECTIONS, ///< can cast reflections in water + KINDOF_SHRUBBERY, ///< tree, bush, etc. + KINDOF_STRUCTURE, ///< structure of some sort (buildable or not) + KINDOF_INFANTRY, ///< unit like soldier etc + KINDOF_VEHICLE, ///< unit like tank, jeep, plane, helicopter, etc. + KINDOF_AIRCRAFT, ///< unit like plane, helicopter, etc., that is predominantly a flyer. (hovercraft are NOT aircraft) + KINDOF_HUGE_VEHICLE, ///< unit that is, technically, a vehicle, but WAY larger than normal (eg, Overlord) + KINDOF_DOZER, ///< a dozer + KINDOF_HARVESTER, ///< a harvester + KINDOF_COMMANDCENTER, ///< a command center +#ifdef ALLOW_SURRENDER + KINDOF_PRISON, ///< a prison detention center kind of thing + KINDOF_COLLECTS_PRISON_BOUNTY, ///< when prisoners are delivered to these, the player gets money + KINDOF_POW_TRUCK, ///< a pow truck can pick up and return prisoners +#endif + KINDOF_LINEBUILD, ///< wall-type thing that is built in a line + KINDOF_SALVAGER, ///< something that can create and use Salvage Crates + KINDOF_WEAPON_SALVAGER, ///< subset of salvager that can get weapon upgrades from salvage + KINDOF_TRANSPORT, ///< a true transport (has TransportContain) + KINDOF_BRIDGE, ///< a Bridge. (special structure) + KINDOF_LANDMARK_BRIDGE, ///< a landmark bridge (special bridge that isn't resizable) + KINDOF_BRIDGE_TOWER, ///< a bridge tower that we can target for bridge destruction + KINDOF_PROJECTILE, ///< Instead of being a ground or air unit, this object is special + KINDOF_PRELOAD, ///< all model data will be preloaded even if not on map + KINDOF_NO_GARRISON, ///< unit may not garrison bldgs, even if infantry bit is set + KINDOF_WAVEGUIDE, ///< water wave object + KINDOF_WAVE_EFFECT, ///< wave effect point + KINDOF_NO_COLLIDE, ///< Never collide with or be collided with + KINDOF_REPAIR_PAD, ///< is a repair pad object that can repair other machines + KINDOF_HEAL_PAD, ///< is a heal pad object that can heal flesh and bone units + KINDOF_STEALTH_GARRISON, /** enemy teams can't tell that unit is in building.. and if they + garrison that building, they stealth unit will eject. */ + KINDOF_CASH_GENERATOR, ///< used to check if the unit generates cash... checked by cash hackers and whatever else comes up + KINDOF_DRAWABLE_ONLY, ///< template is used only to create drawables (not Objects) + KINDOF_MP_COUNT_FOR_VICTORY, ///< If a player loses all his buildings that have this kindof in a multiplayer game, he loses. + KINDOF_REBUILD_HOLE, ///< a GLA rebuild hole + KINDOF_SCORE, ///< Object counts for Multiplayer scores, and short-game calculations (for buildings) + KINDOF_SCORE_CREATE, ///< Object only counts for multiplayer score for creation. + KINDOF_SCORE_DESTROY, ///< Object only counts for multiplayer score for destruction. + KINDOF_NO_HEAL_ICON, ///< do not ever display healing icons on these objects + KINDOF_CAN_RAPPEL, ///< can rappel. duh. + KINDOF_PARACHUTABLE, ///< parachutable object +#ifdef ALLOW_SURRENDER + KINDOF_CAN_SURRENDER, ///< object that can surrender +#endif + KINDOF_CAN_BE_REPULSED, ///< object that runs away from a repulsor object. + KINDOF_MOB_NEXUS, ///< object that cooyrdinates the members of a mob (i.e. GLAInfantryAngryMob) + KINDOF_IGNORED_IN_GUI, ///< object that is the members of a mob (i.e. GLAInfantryAngryMob) + KINDOF_CRATE, ///< a bonus crate + KINDOF_CAPTURABLE, ///< is "capturable" even if not an enemy (should generally be used only for structures, eg, Tech bldgs) + KINDOF_CLEARED_BY_BUILD, ///< is auto-cleared from the map when built over via construction + KINDOF_SMALL_MISSILE, ///< Missile object: ONLY USED FOR ANTI-MISSILE TARGETTING PURPOSES! Keep using PROJECTILE! + KINDOF_ALWAYS_VISIBLE, ///< is never obscured by fog of war or shroud. mostly for UI feedback objects. + KINDOF_UNATTACKABLE, ///< You cannot target this thing, it probably doesn't really exist + KINDOF_MINE, ///< a landmine. (possibly also extend to Col. Burton timed charges?) + KINDOF_CLEANUP_HAZARD, ///< radiation and bio-poison are samples of area conditions that can be cleaned up (or avoided) + KINDOF_PORTABLE_STRUCTURE, ///< Flag to identify building like subobjects an Overlord is allowed to Contain. + KINDOF_ALWAYS_SELECTABLE, ///< is never unselectable (even if effectively dead). mostly for UI feedback objects. + KINDOF_ATTACK_NEEDS_LINE_OF_SIGHT, ///< Unit has to have clear line of sight (los) to attack. + KINDOF_WALK_ON_TOP_OF_WALL, ///< Units can walk on top of a wall made of these kind of objects. + KINDOF_DEFENSIVE_WALL, ///< wall can't be driven thru, even if crusher, so pathfinder must path around it + KINDOF_FS_POWER, ///< Faction structure power building + KINDOF_FS_FACTORY, ///< Faction structure power building + KINDOF_FS_BASE_DEFENSE, ///< Faction structure base defense + KINDOF_FS_TECHNOLOGY, ///< Faction structure technology building + KINDOF_AIRCRAFT_PATH_AROUND, ///< Tall enough that aircraft need to path around this. + KINDOF_LOW_OVERLAPPABLE, ///< When overlapped, things always overlap at a 'low' height rather than our object geom + KINDOF_FORCEATTACKABLE, ///< unit is always attackable via force-attack, even if not selectable + KINDOF_AUTO_RALLYPOINT, ///< When immobile-structure-object is selected, left clicking on ground will set new rally point without requiring command button. + KINDOF_TECH_BUILDING, ///< Neutral tech building - Oil derrick, Hospital, Radio Station, Refinery. + KINDOF_POWERED, ///< This object gets the Underpowered disabled condition when its owning player has power consumption exceed supply + KINDOF_PRODUCED_AT_HELIPAD, ///< ugh... hacky fix for comanche. (srj) + KINDOF_DRONE, ///< Object drone type -- used for filtering them out of battle plan bonuses, making un-snipable, and whatever else may come up. + KINDOF_CAN_SEE_THROUGH_STRUCTURE,///< Structure does not block line of sight. + KINDOF_BALLISTIC_MISSILE, ///< Large ballistic missiles that are specifically large enough to be targetted by base defenses. + KINDOF_CLICK_THROUGH, ///< Objects with this will never be picked by mouse interactions! + KINDOF_SUPPLY_SOURCE_ON_PREVIEW,///< Any thing that we can get "supplies" from that we want to show up on the map preview + KINDOF_PARACHUTE, ///< it's a parachute + KINDOF_GARRISONABLE_UNTIL_DESTROYED, ///< Object is capable of garrisoning troops until completely destroyed. + KINDOF_BOAT, ///< It's a boat! + KINDOF_IMMUNE_TO_CAPTURE, ///< Under no circumstances can this building ever be captured. + KINDOF_HULK, ///< Hulk types so we can do special things to them via scripts or other things that may come up. + KINDOF_SHOW_PORTRAIT_WHEN_CONTROLLED, ///< Only shows portraits when controlled. + KINDOF_SPAWNS_ARE_THE_WEAPONS, ///< Evaluate the spawn slaves as this object's weapons. + KINDOF_CANNOT_BUILD_NEAR_SUPPLIES, ///< you can't be built "too close" to anything that provides supplies + KINDOF_SUPPLY_SOURCE, ///< this object provides supplies + KINDOF_REVEAL_TO_ALL, ///< this object reveals shroud for all players + KINDOF_DISGUISER, ///< This object has the ability to disguise. + KINDOF_INERT, ///< this object shouldn't be considered for any sort of interaction with any player. + KINDOF_HERO, ///< Any of the single-instance infantry, JarmenKell, BlackLotus, ColonelBurton + KINDOF_IGNORES_SELECT_ALL, ///< Too late to figure out intelligently if something should respond to a Select All command + KINDOF_DONT_AUTO_CRUSH_INFANTRY, ///< These units don't try to crush the infantry if ai. + KINDOF_CLIFF_JUMPER, ///< Can't climb cliffs, but can jump off of them. + KINDOF_FS_SUPPLY_DROPZONE, ///< A supply dropzone. + KINDOF_FS_SUPERWEAPON, ///< A superweapon structure like a nuke silo, particle uplink cannon, scudstorm. + KINDOF_FS_BLACK_MARKET, ///< Is this object a black market? + KINDOF_FS_SUPPLY_CENTER, ///< Is this object a supply center? + KINDOF_FS_STRATEGY_CENTER, ///< Is this object a strategy center? + KINDOF_MONEY_HACKER, ///< Unit that generates money from air. Needed for things that directly power them up. + KINDOF_ARMOR_SALVAGER, ///< subset of salvager that can get armor upgrades from salvage + KINDOF_REVEALS_ENEMY_PATHS, ///< like the listening outpost... when selected, any enemy drawable will draw show paths when moused over + KINDOF_BOOBY_TRAP, ///< A sticky bomb that gets set off by 5 random and unrelated events. + KINDOF_FS_FAKE, ///< Fake structure! + KINDOF_FS_INTERNET_CENTER, ///< Internet Center. + KINDOF_BLAST_CRATER, ///< deeply gouges out the terrain under object footprint + KINDOF_PROP, ///< A prop, visual only, doesn't interact with other objects (rock, street sign, inert fire hydrant) + KINDOF_OPTIMIZED_TREE, ///< An optimized, client side only tree. (The only good kind of tree. jba) + KINDOF_FS_ADVANCED_TECH, ///< Represents each faction's advanced techtree building -- strategy center, propaganda center, and palace. + KINDOF_FS_BARRACKS, ///< A barracks + KINDOF_FS_WARFACTORY, ///< A war factory or arms dealer. + KINDOF_FS_AIRFIELD, ///< An airfield. + KINDOF_AIRCRAFT_CARRIER, ///< An aircraft carrier. + KINDOF_NO_SELECT, ///< Can't select it but you can mouse over it to see it's health (drones!) + KINDOF_REJECT_UNMANNED, ///< Unit cannot enter an unmanned vehicle. + KINDOF_CANNOT_RETALIATE, ///< Unit will not retaliate if asked. + KINDOF_TECH_BASE_DEFENSE, ///< Tech Building that acts as base defence when captured + KINDOF_EMP_HARDENED, ///< Like a delivery plane (B52, B3, CargoPlane,etc.) or a SpectreGunship, which sort-of IS the weapon... + KINDOF_DEMOTRAP, ///< Added strictly only for disarming purposes. They don't act like mines which have rendering and selection implications! + KINDOF_CONSERVATIVE_BUILDING, ///< Conservative structures aren't considered part of your base for sneak attack boundary calculations... + KINDOF_IGNORE_DOCKING_BONES, ///< Structure will not look up docking bones. Patch 1.03 hack. + + // NEW KINDOFs + + KINDOF_VTOL, + KINDOF_LARGE_AIRCRAFT, + KINDOF_MEDIUM_AIRCRAFT, + KINDOF_SMALL_AIRCRAFT, + KINDOF_ARTILLERY, + KINDOF_HEAVY_ARTILLERY, + KINDOF_ANTI_AIR, + KINDOF_SCOUT, + KINDOF_COMMANDO, + KINDOF_HEAVY_INFANTRY, + KINDOF_SUPERHEAVY_VEHICLE, + + KINDOF_TELEPORTER, + + KINDOF_EXTRA1, + KINDOF_EXTRA2, + KINDOF_EXTRA3, + KINDOF_EXTRA4, + KINDOF_EXTRA5, + KINDOF_EXTRA6, + KINDOF_EXTRA7, + KINDOF_EXTRA8, + KINDOF_EXTRA9, + KINDOF_EXTRA10, + KINDOF_EXTRA11, + KINDOF_EXTRA12, + KINDOF_EXTRA13, + KINDOF_EXTRA14, + KINDOF_EXTRA15, + KINDOF_EXTRA16, + + + KINDOF_COUNT // total number of kindofs + +}; + +typedef BitFlags KindOfMaskType; + +#define MAKE_KINDOF_MASK(k) KindOfMaskType(KindOfMaskType::kInit, (k)) + +inline Bool TEST_KINDOFMASK(const KindOfMaskType& m, KindOfType t) +{ + return m.test(t); +} + +inline Bool TEST_KINDOFMASK_ANY(const KindOfMaskType& m, const KindOfMaskType& mask) +{ + return m.anyIntersectionWith(mask); +} + +inline Bool TEST_KINDOFMASK_MULTI(const KindOfMaskType& m, const KindOfMaskType& mustBeSet, const KindOfMaskType& mustBeClear) +{ + return m.testSetAndClear(mustBeSet, mustBeClear); +} + +inline Bool KINDOFMASK_ANY_SET(const KindOfMaskType& m) +{ + return m.any(); +} + +inline void CLEAR_KINDOFMASK(KindOfMaskType& m) +{ + m.clear(); +} + +inline void SET_ALL_KINDOFMASK_BITS(KindOfMaskType& m) +{ + m.clear(); + m.flip(); +} + +inline void FLIP_KINDOFMASK(KindOfMaskType& m) +{ + m.flip(); +} + +// defined in Common/System/Kindof.cpp +extern KindOfMaskType KINDOFMASK_NONE; // inits to all zeroes +extern KindOfMaskType KINDOFMASK_FS; // Initializes all FS types for faction structures. +void initKindOfMasks(); + +#endif // __KINDOF_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h index 1d9b761a3b0..a35e35b2731 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h @@ -240,87 +240,87 @@ enum AIFreeToExitType CPP_11(: Int) // Note - written out in save/load xfer, don class AIUpdateInterface : public UpdateModule, public AICommandInterface { - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( AIUpdateInterface, "AIUpdateInterface" ) - MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( AIUpdateInterface, AIUpdateModuleData ) + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(AIUpdateInterface, "AIUpdateInterface") + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA(AIUpdateInterface, AIUpdateModuleData) protected: // yes, protected, NOT public. - virtual void privateMoveToPosition( const Coord3D *pos, CommandSourceType cmdSource ); ///< move to given position(s) tightening the formation. - virtual void privateMoveToObject( Object *obj, CommandSourceType cmdSource ); ///< move to given object - virtual void privateMoveToAndEvacuate( const Coord3D *pos, CommandSourceType cmdSource ); ///< move to given position(s) - virtual void privateMoveToAndEvacuateAndExit( const Coord3D *pos, CommandSourceType cmdSource ); ///< move to given position & unload transport. + virtual void privateMoveToPosition(const Coord3D* pos, CommandSourceType cmdSource); ///< move to given position(s) tightening the formation. + virtual void privateMoveToObject(Object* obj, CommandSourceType cmdSource); ///< move to given object + virtual void privateMoveToAndEvacuate(const Coord3D* pos, CommandSourceType cmdSource); ///< move to given position(s) + virtual void privateMoveToAndEvacuateAndExit(const Coord3D* pos, CommandSourceType cmdSource); ///< move to given position & unload transport. virtual void privateIdle(CommandSourceType cmdSource); ///< Enter idle state. - virtual void privateTightenToPosition( const Coord3D *pos, CommandSourceType cmdSource ); ///< move to given position(s) tightening the formation. - virtual void privateFollowWaypointPath( const Waypoint *way, CommandSourceType cmdSource );///< start following the path from the given point - virtual void privateFollowWaypointPathAsTeam( const Waypoint *way, CommandSourceType cmdSource );///< start following the path from the given point - virtual void privateFollowWaypointPathExact( const Waypoint *way, CommandSourceType cmdSource );///< start following the path from the given point - virtual void privateFollowWaypointPathAsTeamExact( const Waypoint *way, CommandSourceType cmdSource );///< start following the path from the given point - virtual void privateFollowPath( const std::vector* path, Object *ignoreObject, CommandSourceType cmdSource, Bool exitProduction );///< follow the path defined by the given array of points - virtual void privateFollowPathAppend( const Coord3D *pos, CommandSourceType cmdSource ); - virtual void privateAttackObject( Object *victim, Int maxShotsToFire, CommandSourceType cmdSource ); ///< attack given object - virtual void privateForceAttackObject( Object *victim, Int maxShotsToFire, CommandSourceType cmdSource ); ///< attack given object - virtual void privateGuardRetaliate( Object *victim, const Coord3D *pos, Int maxShotsToFire, CommandSourceType cmdSource ); ///< retaliate and attack attacker -- but with guard restrictions - virtual void privateAttackTeam( const Team *team, Int maxShotsToFire, CommandSourceType cmdSource ); ///< attack the given team - virtual void privateAttackPosition( const Coord3D *pos, Int maxShotsToFire, CommandSourceType cmdSource ); ///< attack given spot - virtual void privateAttackMoveToPosition( const Coord3D *pos, Int maxShotsToFire, CommandSourceType cmdSource ); ///< attack move to the given location - virtual void privateAttackFollowWaypointPath( const Waypoint *way, Int maxShotsToFire, Bool asTeam, CommandSourceType cmdSource ); ///< attack move along the following waypoint path, potentially as a team - virtual void privateHunt( CommandSourceType cmdSource ); ///< begin "seek and destroy" - virtual void privateRepair( Object *obj, CommandSourceType cmdSource ); ///< repair the given object + virtual void privateTightenToPosition(const Coord3D* pos, CommandSourceType cmdSource); ///< move to given position(s) tightening the formation. + virtual void privateFollowWaypointPath(const Waypoint* way, CommandSourceType cmdSource);///< start following the path from the given point + virtual void privateFollowWaypointPathAsTeam(const Waypoint* way, CommandSourceType cmdSource);///< start following the path from the given point + virtual void privateFollowWaypointPathExact(const Waypoint* way, CommandSourceType cmdSource);///< start following the path from the given point + virtual void privateFollowWaypointPathAsTeamExact(const Waypoint* way, CommandSourceType cmdSource);///< start following the path from the given point + virtual void privateFollowPath(const std::vector* path, Object* ignoreObject, CommandSourceType cmdSource, Bool exitProduction);///< follow the path defined by the given array of points + virtual void privateFollowPathAppend(const Coord3D* pos, CommandSourceType cmdSource); + virtual void privateAttackObject(Object* victim, Int maxShotsToFire, CommandSourceType cmdSource); ///< attack given object + virtual void privateForceAttackObject(Object* victim, Int maxShotsToFire, CommandSourceType cmdSource); ///< attack given object + virtual void privateGuardRetaliate(Object* victim, const Coord3D* pos, Int maxShotsToFire, CommandSourceType cmdSource); ///< retaliate and attack attacker -- but with guard restrictions + virtual void privateAttackTeam(const Team* team, Int maxShotsToFire, CommandSourceType cmdSource); ///< attack the given team + virtual void privateAttackPosition(const Coord3D* pos, Int maxShotsToFire, CommandSourceType cmdSource); ///< attack given spot + virtual void privateAttackMoveToPosition(const Coord3D* pos, Int maxShotsToFire, CommandSourceType cmdSource); ///< attack move to the given location + virtual void privateAttackFollowWaypointPath(const Waypoint* way, Int maxShotsToFire, Bool asTeam, CommandSourceType cmdSource); ///< attack move along the following waypoint path, potentially as a team + virtual void privateHunt(CommandSourceType cmdSource); ///< begin "seek and destroy" + virtual void privateRepair(Object* obj, CommandSourceType cmdSource); ///< repair the given object #ifdef ALLOW_SURRENDER - virtual void privatePickUpPrisoner( Object *prisoner, CommandSourceType cmdSource ); ///< pick up prisoner - virtual void privateReturnPrisoners( Object *prison, CommandSourceType cmdSource ); ///< return picked up prisoners to the 'prison' + virtual void privatePickUpPrisoner(Object* prisoner, CommandSourceType cmdSource); ///< pick up prisoner + virtual void privateReturnPrisoners(Object* prison, CommandSourceType cmdSource); ///< return picked up prisoners to the 'prison' #endif - virtual void privateResumeConstruction( Object *obj, CommandSourceType cmdSource ); ///< resume construction of object - virtual void privateGetHealed( Object *healDepot, CommandSourceType cmdSource ); ///< get healed at heal depot - virtual void privateGetRepaired( Object *repairDepot, CommandSourceType cmdSource );///< get repaired at repair depot - virtual void privateEnter( Object *obj, CommandSourceType cmdSource ); ///< enter the given object - virtual void privateDock( Object *obj, CommandSourceType cmdSource ); ///< get near given object and wait for enter clearance - virtual void privateExit( Object *objectToExit, CommandSourceType cmdSource ); ///< get out of this Object - virtual void privateExitInstantly( Object *objectToExit, CommandSourceType cmdSource ); ///< get out of this Object this frame - virtual void privateEvacuate( Int exposeStealthUnits, CommandSourceType cmdSource ); ///< empty its contents - virtual void privateEvacuateInstantly( Int exposeStealthUnits, CommandSourceType cmdSource ); ///< empty its contents this frame - virtual void privateExecuteRailedTransport( CommandSourceType cmdSource ); ///< execute next leg in railed transport sequence - virtual void privateGoProne( const DamageInfo *damageInfo, CommandSourceType cmdSource ); ///< life altering state change, if this AI can do it - virtual void privateGuardTunnelNetwork( GuardMode guardMode, CommandSourceType cmdSource ); ///< guard the given spot - virtual void privateGuardPosition( const Coord3D *pos, GuardMode guardMode, CommandSourceType cmdSource ); ///< guard the given spot - virtual void privateGuardObject( Object *objectToGuard, GuardMode guardMode, CommandSourceType cmdSource ); ///< guard the given object - virtual void privateGuardArea( const PolygonTrigger *areaToGuard, GuardMode guardMode, CommandSourceType cmdSource ); ///< guard the given area - virtual void privateAttackArea( const PolygonTrigger *areaToGuard, CommandSourceType cmdSource ); ///< guard the given area - virtual void privateHackInternet( CommandSourceType cmdSource ); ///< Hack money from the heavens (free money) - virtual void privateFaceObject( Object *target, CommandSourceType cmdSource ); - virtual void privateFacePosition( const Coord3D *pos, CommandSourceType cmdSource ); - virtual void privateRappelInto( Object *target, const Coord3D& pos, CommandSourceType cmdSource ); - virtual void privateCombatDrop( Object *target, const Coord3D& pos, CommandSourceType cmdSource ); - virtual void privateCommandButton( const CommandButton *commandButton, CommandSourceType cmdSource ); - virtual void privateCommandButtonPosition( const CommandButton *commandButton, const Coord3D *pos, CommandSourceType cmdSource ); - virtual void privateCommandButtonObject( const CommandButton *commandButton, Object *obj, CommandSourceType cmdSource ); - virtual void privateWander( const Waypoint *way, CommandSourceType cmdSource ); ///< Wander around the waypoint path. - virtual void privateWanderInPlace( CommandSourceType cmdSource ); ///< Wander around the current position. - virtual void privatePanic( const Waypoint *way, CommandSourceType cmdSource ); ///< Run screaming down the waypoint path. - virtual void privateBusy( CommandSourceType cmdSource ); ///< Transition to the busy state - virtual void privateMoveAwayFromUnit( Object *unit, CommandSourceType cmdSource ); ///< Move out of the way of a unit. + virtual void privateResumeConstruction(Object* obj, CommandSourceType cmdSource); ///< resume construction of object + virtual void privateGetHealed(Object* healDepot, CommandSourceType cmdSource); ///< get healed at heal depot + virtual void privateGetRepaired(Object* repairDepot, CommandSourceType cmdSource);///< get repaired at repair depot + virtual void privateEnter(Object* obj, CommandSourceType cmdSource); ///< enter the given object + virtual void privateDock(Object* obj, CommandSourceType cmdSource); ///< get near given object and wait for enter clearance + virtual void privateExit(Object* objectToExit, CommandSourceType cmdSource); ///< get out of this Object + virtual void privateExitInstantly(Object* objectToExit, CommandSourceType cmdSource); ///< get out of this Object this frame + virtual void privateEvacuate(Int exposeStealthUnits, CommandSourceType cmdSource); ///< empty its contents + virtual void privateEvacuateInstantly(Int exposeStealthUnits, CommandSourceType cmdSource); ///< empty its contents this frame + virtual void privateExecuteRailedTransport(CommandSourceType cmdSource); ///< execute next leg in railed transport sequence + virtual void privateGoProne(const DamageInfo* damageInfo, CommandSourceType cmdSource); ///< life altering state change, if this AI can do it + virtual void privateGuardTunnelNetwork(GuardMode guardMode, CommandSourceType cmdSource); ///< guard the given spot + virtual void privateGuardPosition(const Coord3D* pos, GuardMode guardMode, CommandSourceType cmdSource); ///< guard the given spot + virtual void privateGuardObject(Object* objectToGuard, GuardMode guardMode, CommandSourceType cmdSource); ///< guard the given object + virtual void privateGuardArea(const PolygonTrigger* areaToGuard, GuardMode guardMode, CommandSourceType cmdSource); ///< guard the given area + virtual void privateAttackArea(const PolygonTrigger* areaToGuard, CommandSourceType cmdSource); ///< guard the given area + virtual void privateHackInternet(CommandSourceType cmdSource); ///< Hack money from the heavens (free money) + virtual void privateFaceObject(Object* target, CommandSourceType cmdSource); + virtual void privateFacePosition(const Coord3D* pos, CommandSourceType cmdSource); + virtual void privateRappelInto(Object* target, const Coord3D& pos, CommandSourceType cmdSource); + virtual void privateCombatDrop(Object* target, const Coord3D& pos, CommandSourceType cmdSource); + virtual void privateCommandButton(const CommandButton* commandButton, CommandSourceType cmdSource); + virtual void privateCommandButtonPosition(const CommandButton* commandButton, const Coord3D* pos, CommandSourceType cmdSource); + virtual void privateCommandButtonObject(const CommandButton* commandButton, Object* obj, CommandSourceType cmdSource); + virtual void privateWander(const Waypoint* way, CommandSourceType cmdSource); ///< Wander around the waypoint path. + virtual void privateWanderInPlace(CommandSourceType cmdSource); ///< Wander around the current position. + virtual void privatePanic(const Waypoint* way, CommandSourceType cmdSource); ///< Run screaming down the waypoint path. + virtual void privateBusy(CommandSourceType cmdSource); ///< Transition to the busy state + virtual void privateMoveAwayFromUnit(Object* unit, CommandSourceType cmdSource); ///< Move out of the way of a unit. public: - AIUpdateInterface( Thing *thing, const ModuleData* moduleData ); + AIUpdateInterface(Thing* thing, const ModuleData* moduleData); // virtual destructor prototype provided by memory pool declaration virtual AIUpdateInterface* getAIUpdateInterface() { return this; } // Disabled conditions to process (AI will still process held status) - virtual DisabledMaskType getDisabledTypesToProcess() const { return MAKE_DISABLED_MASK( DISABLED_HELD ); } - + virtual DisabledMaskType getDisabledTypesToProcess() const { return MAKE_DISABLED_MASK(DISABLED_HELD); } + // Some very specific, complex behaviors are used by more than one AIUpdate. Here are their interfaces. - virtual DozerAIInterface* getDozerAIInterface() {return NULL;} - virtual SupplyTruckAIInterface* getSupplyTruckAIInterface() {return NULL;} - virtual const DozerAIInterface* getDozerAIInterface() const {return NULL;} - virtual const SupplyTruckAIInterface* getSupplyTruckAIInterface() const {return NULL;} + virtual DozerAIInterface* getDozerAIInterface() { return NULL; } + virtual SupplyTruckAIInterface* getSupplyTruckAIInterface() { return NULL; } + virtual const DozerAIInterface* getDozerAIInterface() const { return NULL; } + virtual const SupplyTruckAIInterface* getSupplyTruckAIInterface() const { return NULL; } #ifdef ALLOW_SURRENDER - virtual POWTruckAIUpdateInterface *getPOWTruckAIUpdateInterface( void ) { return NULL; } + virtual POWTruckAIUpdateInterface* getPOWTruckAIUpdateInterface(void) { return NULL; } #endif - virtual WorkerAIInterface* getWorkerAIInterface( void ) { return NULL; } - virtual const WorkerAIInterface* getWorkerAIInterface( void ) const { return NULL; } + virtual WorkerAIInterface* getWorkerAIInterface(void) { return NULL; } + virtual const WorkerAIInterface* getWorkerAIInterface(void) const { return NULL; } virtual HackInternetAIInterface* getHackInternetAIInterface() { return NULL; } virtual const HackInternetAIInterface* getHackInternetAIInterface() const { return NULL; } virtual AssaultTransportAIInterface* getAssaultTransportAIInterface() { return NULL; } @@ -329,13 +329,13 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface virtual const JetAIUpdate* getJetAIUpdate() const { return NULL; } #ifdef ALLOW_SURRENDER - void setSurrendered( const Object *objWeSurrenderedTo, Bool surrendered ); - inline Bool isSurrendered( void ) const { return m_surrenderedFramesLeft > 0; } + void setSurrendered(const Object* objWeSurrenderedTo, Bool surrendered); + inline Bool isSurrendered(void) const { return m_surrenderedFramesLeft > 0; } inline Int getSurrenderedPlayerIndex() const { return m_surrenderedPlayerIndex; } #endif - virtual void joinTeam( void ); ///< This unit just got added to a team & needs to catch up. - + virtual void joinTeam(void); ///< This unit just got added to a team & needs to catch up. + Bool areTurretsLinked() const { return getAIUpdateModuleData()->m_turretsLinked; } Real getAttackAngle() const { return getAIUpdateModuleData()->m_attackAngle; } @@ -345,7 +345,7 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface // this is present solely for some transports to override, so that they can land before // allowing people to exit... virtual AIFreeToExitType getAiFreeToExit(const Object* exiter) const { return FREE_TO_EXIT; } - + // this is present solely to allow some special-case things to override, like landed choppers. virtual Bool isAllowedToAdjustDestination() const { return true; } virtual Bool isAllowedToMoveAwayFromUnit() const { return true; } @@ -362,26 +362,28 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface virtual Bool isBusy() const; virtual void onObjectCreated(); - virtual void doQuickExit( const std::vector* path ); ///< get out of this Object - + virtual void doQuickExit(const std::vector* path); ///< get out of this Object + virtual void aiDoCommand(const AICommandParms* parms); - - virtual const Coord3D *getGuardLocation( void ) const { return &m_locationToGuard; } - virtual const ObjectID getGuardObject( void ) const { return m_objectToGuard; } - virtual const PolygonTrigger *getAreaToGuard( void ) const { return m_areaToGuard; } + + virtual const Coord3D* getGuardLocation(void) const { return &m_locationToGuard; } + virtual const ObjectID getGuardObject(void) const { return m_objectToGuard; } + virtual const PolygonTrigger* getAreaToGuard(void) const { return m_areaToGuard; } virtual GuardTargetType getGuardTargetType() const { return m_guardTargetType[1]; } virtual void clearGuardTargetType() { m_guardTargetType[1] = m_guardTargetType[0]; m_guardTargetType[0] = GUARDTARGET_NONE; } virtual GuardMode getGuardMode() const { return m_guardMode; } - virtual Object* construct( const ThingTemplate *what, - const Coord3D *pos, Real angle, - Player *owningPlayer, - Bool isRebuild ) { return NULL; }///< construct a building + virtual Object* construct(const ThingTemplate* what, + const Coord3D* pos, Real angle, + Player* owningPlayer, + Bool isRebuild) { + return NULL; + }///< construct a building - void ignoreObstacle( const Object *obj ); ///< tell the pathfinder to ignore the given object as an obstacle - void ignoreObstacleID( ObjectID id ); ///< tell the pathfinder to ignore the given object as an obstacle - + void ignoreObstacle(const Object* obj); ///< tell the pathfinder to ignore the given object as an obstacle + void ignoreObstacleID(ObjectID id); ///< tell the pathfinder to ignore the given object as an obstacle + AIStateType getAIStateType() const; ///< What general state is the AIState Machine in? AsciiString getCurrentStateName(void) const { return m_stateMachine->getCurrentStateName(); } @@ -402,25 +404,25 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface virtual void addTargeter(ObjectID id, Bool add) { return; } virtual Bool isTemporarilyPreventingAimSuccess() const { return false; } - - void setPriorWaypointID( UnsignedInt id ) { m_priorWaypointID = id; }; - void setCurrentWaypointID( UnsignedInt id ) { m_currentWaypointID = id; }; + + void setPriorWaypointID(UnsignedInt id) { m_priorWaypointID = id; }; + void setCurrentWaypointID(UnsignedInt id) { m_currentWaypointID = id; }; // Group ---------------------------------------------------------------------------------------------- // these three methods allow a group leader's path to be communicated to the other group members - AIGroup *getGroup(void); + AIGroup* getGroup(void); // it's VERY RARE you want to call this function; you should normally use Object::isEffectivelyDead() // instead. the exception would be for things that need to know whether to call markIsDead or not. - Bool isAiInDeadState( void ) const { return m_isAiDead; } ///< return true if we are dead - void markAsDead( void ); + Bool isAiInDeadState(void) const { return m_isAiDead; } ///< return true if we are dead + void markAsDead(void); - Bool isRecruitable(void) const {return m_isRecruitable;} - void setIsRecruitable(Bool isRecruitable) {m_isRecruitable = isRecruitable;} + Bool isRecruitable(void) const { return m_isRecruitable; } + void setIsRecruitable(Bool isRecruitable) { m_isRecruitable = isRecruitable; } Real getDesiredSpeed() const { return m_desiredSpeed; } - void setDesiredSpeed( Real speed ) { m_desiredSpeed = speed; } ///< how fast we want to go + void setDesiredSpeed(Real speed) { m_desiredSpeed = speed; } ///< how fast we want to go // these are virtual because subclasses might need to override them. (srj) virtual void setLocomotorGoalPositionOnPath(); @@ -433,8 +435,8 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface Bool isAircraftThatAdjustsDestination(void) const; ///< True if is aircraft that doesn't stack destinations (missles for example do stack destinations.) Real getCurLocomotorSpeed() const; Real getLocomotorDistanceToGoal(); - const Locomotor *getCurLocomotor() const {return m_curLocomotor;} - Locomotor *getCurLocomotor() { return m_curLocomotor; } + const Locomotor* getCurLocomotor() const { return m_curLocomotor; } + Locomotor* getCurLocomotor() { return m_curLocomotor; } LocomotorSetType getCurLocomotorSetType() const { return m_curLocomotorSet; } Bool hasLocomotorForSurface(LocomotorSurfaceType surfaceType); @@ -442,7 +444,7 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface WhichTurretType getWhichTurretForWeaponSlot(WeaponSlotType wslot, Real* turretAngle, Real* turretPitch = NULL) const; WhichTurretType getWhichTurretForCurWeapon() const; /** - return true iff the weapon is on a turret, that turret is trying to aim at the victim, + return true iff the weapon is on a turret, that turret is trying to aim at the victim, BUT is not yet pointing in the right dir. */ Bool isWeaponSlotOnTurretAndAimingAtTarget(WeaponSlotType wslot, const Object* victim) const; @@ -454,49 +456,49 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface Bool hasLimitedTurretAngle(WhichTurretType tur) const; void setTurretTargetObject(WhichTurretType tur, Object* o, Bool isForceAttacking = FALSE); - Object *getTurretTargetObject( WhichTurretType tur, Bool clearDeadTargets = TRUE ); + Object* getTurretTargetObject(WhichTurretType tur, Bool clearDeadTargets = TRUE); void setTurretTargetPosition(WhichTurretType tur, const Coord3D* pos); void setTurretEnabled(WhichTurretType tur, Bool enabled); void recenterTurret(WhichTurretType tur); - Bool isTurretEnabled( WhichTurretType tur ) const; + Bool isTurretEnabled(WhichTurretType tur) const; Bool isTurretInNaturalPosition(WhichTurretType tur) const; // "Planning Mode" ----------------------------------------------------------------------------------- - Bool queueWaypoint( const Coord3D *pos ); ///< add waypoint to end of move list. return true if success, false if queue was full and those the waypoint not added - void clearWaypointQueue( void ); ///< reset the waypoint queue to empty - void executeWaypointQueue( void ); ///< start moving along queued waypoints + Bool queueWaypoint(const Coord3D* pos); ///< add waypoint to end of move list. return true if success, false if queue was full and those the waypoint not added + void clearWaypointQueue(void); ///< reset the waypoint queue to empty + void executeWaypointQueue(void); ///< start moving along queued waypoints // Pathfinding --------------------------------------------------------------------------------------- private: - Bool computePath( PathfindServicesInterface *pathfinder, Coord3D *destination ); ///< computes path to destination, returns false if no path - Bool computeAttackPath(PathfindServicesInterface *pathfinder, const Object *victim, const Coord3D* victimPos ); ///< computes path to attack the current target, returns false if no path + Bool computePath(PathfindServicesInterface* pathfinder, Coord3D* destination); ///< computes path to destination, returns false if no path + Bool computeAttackPath(PathfindServicesInterface* pathfinder, const Object* victim, const Coord3D* victimPos); ///< computes path to attack the current target, returns false if no path #ifdef ALLOW_SURRENDER void doSurrenderUpdateStuff(); #endif public: - void doPathfind( PathfindServicesInterface *pathfinder ); - void requestPath( Coord3D *destination, Bool isGoalDestination ); ///< Queues a request to pathfind to destination. - void requestAttackPath( ObjectID victimID, const Coord3D* victimPos ); ///< computes path to attack the current target, returns false if no path - void requestApproachPath( Coord3D *destination ); ///< computes path to attack the current target, returns false if no path - void requestSafePath( ObjectID repulsor1 ); ///< computes path to attack the current target, returns false if no path - - Bool isWaitingForPath(void) const {return m_waitingForPath;} - Bool isAttackPath(void) const {return m_isAttackPath;} ///< True if we have a path to an attack location. + virtual void doPathfind(PathfindServicesInterface* pathfinder); + virtual void requestPath(Coord3D* destination, Bool isGoalDestination); ///< Queues a request to pathfind to destination. + virtual void requestAttackPath(ObjectID victimID, const Coord3D* victimPos); ///< computes path to attack the current target, returns false if no path + virtual void requestApproachPath(Coord3D* destination); ///< computes path to attack the current target, returns false if no path + virtual void requestSafePath(ObjectID repulsor1); ///< computes path to attack the current target, returns false if no path + + Bool isWaitingForPath(void) const { return m_waitingForPath; } + Bool isAttackPath(void) const { return m_isAttackPath; } ///< True if we have a path to an attack location. void cancelPath(void); ///< Called if we no longer need the path. - Path* getPath( void ) { return m_path; } ///< return the agent's current path - const Path* getPath( void ) const { return m_path; } ///< return the agent's current path - void destroyPath( void ); ///< destroy the current path, setting it to NULL - UnsignedInt getPathAge( void ) const { return TheGameLogic->getFrame() - m_pathTimestamp; } ///< return the "age" of the path - Bool isPathAvailable( const Coord3D *destination ) const; ///< does a path exist between us and the destination - Bool isQuickPathAvailable( const Coord3D *destination ) const; ///< does a path (using quick pathfind) exist between us and the destination - Int getNumFramesBlocked(void) const {return m_blockedFrames;} - 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. + Path* getPath(void) { return m_path; } ///< return the agent's current path + const Path* getPath(void) const { return m_path; } ///< return the agent's current path + void destroyPath(void); ///< destroy the current path, setting it to NULL + UnsignedInt getPathAge(void) const { return TheGameLogic->getFrame() - m_pathTimestamp; } ///< return the "age" of the path + Bool isPathAvailable(const Coord3D* destination) const; ///< does a path exist between us and the destination + Bool isQuickPathAvailable(const Coord3D* destination) const; ///< does a path (using quick pathfind) exist between us and the destination + Int getNumFramesBlocked(void) const { return m_blockedFrames; } + Bool isBlockedAndStuck(void) const { return m_isBlockedAndStuck; } + virtual Bool canComputeQuickPath(void); ///< Returns true if we can quickly comput a path. Usually missiles & the like that just move straight to the destination. + virtual Bool computeQuickPath(const Coord3D* destination); ///< Computes a quick path to the destination. Bool isMoving() const; - Bool isMovingAwayFrom(Object *obj) const; + Bool isMovingAwayFrom(Object* obj) const; // the following routines should only be called by the AIInternalMoveToState. // They are used to determine when we are really through moving. Due to the nature of the beast, @@ -505,88 +507,88 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface void friend_startingMove(void); void friend_endingMove(void); - void friend_setPath(Path *newPath); + void friend_setPath(Path* newPath); Path* friend_getPath() { return m_path; } - void friend_setGoalObject(Object *obj); + void friend_setGoalObject(Object* obj); - virtual Bool processCollision(PhysicsBehavior *physics, Object *other); ///< Returns true if the physics collide should apply the force. Normally not. jba. - ObjectID getIgnoredObstacleID( void ) const; + virtual Bool processCollision(PhysicsBehavior* physics, Object* other); ///< Returns true if the physics collide should apply the force. Normally not. jba. + ObjectID getIgnoredObstacleID(void) const; // "Waypoint Mode" ----------------------------------------------------------------------------------- - const Waypoint *getCompletedWaypoint(void) const {return m_completedWaypoint;} - void setCompletedWaypoint(const Waypoint *pWay) {m_completedWaypoint = pWay;} + const Waypoint* getCompletedWaypoint(void) const { return m_completedWaypoint; } + void setCompletedWaypoint(const Waypoint* pWay) { m_completedWaypoint = pWay; } - const LocomotorSet& getLocomotorSet(void) const {return m_locomotorSet;} - void setPathExtraDistance(Real dist) {m_pathExtraDistance = dist;} + const LocomotorSet& getLocomotorSet(void) const { return m_locomotorSet; } + void setPathExtraDistance(Real dist) { m_pathExtraDistance = dist; } inline Real getPathExtraDistance() const { return m_pathExtraDistance; } virtual Bool chooseLocomotorSet(LocomotorSetType wst); virtual CommandSourceType getLastCommandSource() const { return m_lastCommandSource; } - const AttackPriorityInfo *getAttackInfo(void) {return m_attackInfo;} - void setAttackInfo(const AttackPriorityInfo *info) {m_attackInfo = info;} + const AttackPriorityInfo* getAttackInfo(void) { return m_attackInfo; } + void setAttackInfo(const AttackPriorityInfo* info) { m_attackInfo = info; } - void setCurPathfindCell(const ICoord2D &cell) {m_pathfindCurCell = cell;} - void setPathfindGoalCell(const ICoord2D &cell) {m_pathfindGoalCell = cell;} - - void setPathFromWaypoint(const Waypoint *way, const Coord2D *offset); - - const ICoord2D *getCurPathfindCell(void) const {return &m_pathfindCurCell;} - const ICoord2D *getPathfindGoalCell(void) const {return &m_pathfindGoalCell;} + void setCurPathfindCell(const ICoord2D& cell) { m_pathfindCurCell = cell; } + void setPathfindGoalCell(const ICoord2D& cell) { m_pathfindGoalCell = cell; } + + void setPathFromWaypoint(const Waypoint* way, const Coord2D* offset); + + const ICoord2D* getCurPathfindCell(void) const { return &m_pathfindCurCell; } + const ICoord2D* getPathfindGoalCell(void) const { return &m_pathfindGoalCell; } /// Return true if our path has higher priority. - Bool hasHigherPathPriority(AIUpdateInterface *otherAI) const; - void setFinalPosition(const Coord3D *pos) { m_finalPosition = *pos; m_doFinalPosition = false;} + Bool hasHigherPathPriority(AIUpdateInterface* otherAI) const; + void setFinalPosition(const Coord3D* pos) { m_finalPosition = *pos; m_doFinalPosition = false; } - virtual UpdateSleepTime update( void ); ///< update this object's AI + virtual UpdateSleepTime update(void); ///< update this object's AI /// if we are attacking "fromID", stop that and attack "toID" instead void transferAttack(ObjectID fromID, ObjectID toID); - void setCurrentVictim( const Object *nemesis ); ///< Current victim. - Object *getCurrentVictim( void ) const; - virtual void notifyVictimIsDead() { } + void setCurrentVictim(const Object* nemesis); ///< Current victim. + Object* getCurrentVictim(void) const; + virtual void notifyVictimIsDead() {} // if we are attacking a position (and NOT an object), return it. otherwise return null. - const Coord3D *getCurrentVictimPos( void ) const; + const Coord3D* getCurrentVictimPos(void) const; void setLocomotorUpgrade(Bool set); // This function is used to notify the unit that it may have a target of opportunity to attack. - void wakeUpAndAttemptToTarget( void ); - - void resetNextMoodCheckTime( void ); + void wakeUpAndAttemptToTarget(void); + + void resetNextMoodCheckTime(void); //Specifically set a frame to check next mood time -- added for the purpose of ordering a stealth combat unit that can't //autoacquire while stealthed, but isn't stealthed and can stealth and is not detected, and the player specifically orders //that unit to stop. In this case, instead of the unit autoacquiring another unit, and preventing him from stealthing, //we will instead delay the autoacquire until later to give him enough time to stealth properly. - void setNextMoodCheckTime( UnsignedInt frame ); + void setNextMoodCheckTime(UnsignedInt frame); ///< States should call this with calledByAI set true to prevent them from checking every frame ///< States that are doing idle checks should call with calledDuringIdle set true so that they check their - Object *getNextMoodTarget( Bool calledByAI, Bool calledDuringIdle ); + Object* getNextMoodTarget(Bool calledByAI, Bool calledDuringIdle); UnsignedInt getNextMoodCheckTime() const { return m_nextMoodCheckTime; } // This function will return a combination of MoodMatrixParameter flags. - UnsignedInt getMoodMatrixValue( void ) const; - UnsignedInt getMoodMatrixActionAdjustment( MoodMatrixAction action ) const; - void setAttitude( AttitudeType tude ); ///< set the behavior modifier for this agent + UnsignedInt getMoodMatrixValue(void) const; + UnsignedInt getMoodMatrixActionAdjustment(MoodMatrixAction action) const; + void setAttitude(AttitudeType tude); ///< set the behavior modifier for this agent // Common AI "status" effects ------------------------------------------------------------------- - void evaluateMoraleBonus( void ); + void evaluateMoraleBonus(void); #ifdef ALLOW_DEMORALIZE // demoralization ... what a nifty word to write. - Bool isDemoralized( void ) const { return m_demoralizedFramesLeft > 0; } - void setDemoralized( UnsignedInt durationInFrames ); + Bool isDemoralized(void) const { return m_demoralizedFramesLeft > 0; } + void setDemoralized(UnsignedInt durationInFrames); #endif - - Bool canPathThroughUnits( void ) const { return m_canPathThroughUnits; } - void setCanPathThroughUnits( Bool canPath ) { m_canPathThroughUnits = canPath; if (canPath) m_isBlockedAndStuck=false;} + + Bool canPathThroughUnits(void) const { return m_canPathThroughUnits; } + void setCanPathThroughUnits(Bool canPath) { m_canPathThroughUnits = canPath; if (canPath) m_isBlockedAndStuck = false; } // Notify the ai that it has caused a crate to be created (usually by killing something.) void notifyCrate(ObjectID id) { m_crateCreated = id; } @@ -599,17 +601,17 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface // For the attack move, that switches from move to attack, and the attack is CMD_FROM_AI, // while the move is the original command source. John A. - void friend_setLastCommandSource( CommandSourceType source ) {m_lastCommandSource = source;} + void friend_setLastCommandSource(CommandSourceType source) { m_lastCommandSource = source; } Bool canAutoAcquire() const { return getAIUpdateModuleData()->m_autoAcquireEnemiesWhenIdle; } - Bool canAutoAcquireWhileStealthed() const ; + Bool canAutoAcquireWhileStealthed() const; protected: - + /* - AIUpdates run in the initial phase. + AIUpdates run in the initial phase. It's actually quite important that AI (the thing that drives Locomotors) and Physics run in the same order, relative to each other, for a given object; otherwise, @@ -623,16 +625,16 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface virtual Bool isAllowedToRespondToAiCommands(const AICommandParms* parms) const; // getAttitude is protected because other places should call getMoodMatrixValue to get all the facts they need to consider. - AttitudeType getAttitude( void ) const; ///< get the current behavior modifier state. + AttitudeType getAttitude(void) const; ///< get the current behavior modifier state. - Bool blockedBy(Object *other); ///< Returns true if we are blocked by "other" + Bool blockedBy(Object* other); ///< Returns true if we are blocked by "other" Bool needToRotate(void); ///< Returns true if we are not pointing in the right direction for movement. - Real calculateMaxBlockedSpeed(Object *other) const; + Real calculateMaxBlockedSpeed(Object* other) const; virtual UpdateSleepTime doLocomotor(); // virtual so subclasses can override - void chooseGoodLocomotorFromCurrentSet(); + virtual void chooseGoodLocomotorFromCurrentSet(); - void setLastCommandSource( CommandSourceType source ); + void setLastCommandSource(CommandSourceType source); // subclasses may want to override this, to use a subclass of AIStateMachine. virtual AIStateMachine* makeStateMachine(); @@ -648,11 +650,11 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface public: inline StateID getCurrentStateID() const { return getStateMachine()->getCurrentStateID(); } ///< return the id of the current state of the machine -/// @ todo -- srj sez: JBA NUKE THIS CODE, IT IS EVIL - inline void friend_addToWaypointGoalPath( const Coord3D *pathPoint ) { getStateMachine()->addToGoalPath(pathPoint); } + /// @ todo -- srj sez: JBA NUKE THIS CODE, IT IS EVIL + inline void friend_addToWaypointGoalPath(const Coord3D* pathPoint) { getStateMachine()->addToGoalPath(pathPoint); } // this is intended for use ONLY by W3dWaypointBuffer and AIFollowPathState. - inline const Coord3D* friend_getGoalPathPosition( Int index ) const { return getStateMachine()->getGoalPathPosition( index ); } + inline const Coord3D* friend_getGoalPathPosition(Int index) const { return getStateMachine()->getGoalPathPosition(index); } // this is intended for use ONLY by W3dWaypointBuffer. Int friend_getWaypointGoalPathSize() const; @@ -661,10 +663,10 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface inline Int friend_getCurrentGoalPathIndex() const { return m_nextGoalPathIndex; } // this is intended for use ONLY by AIFollowPathState. - inline void friend_setCurrentGoalPathIndex( Int index ) { m_nextGoalPathIndex = index; } + inline void friend_setCurrentGoalPathIndex(Int index) { m_nextGoalPathIndex = index; } #ifdef DEBUG_LOGGING - inline const Coord3D *friend_getRequestedDestination() const { return &m_requestedDestination; } - inline const Coord3D *friend_getRequestedDestination2() const { return &m_requestedDestination2; } + inline const Coord3D* friend_getRequestedDestination() const { return &m_requestedDestination; } + inline const Coord3D* friend_getRequestedDestination2() const { return &m_requestedDestination2; } #endif inline Object* getGoalObject() { return getStateMachine()->getGoalObject(); } ///< return the id of the current state of the machine @@ -673,22 +675,25 @@ class AIUpdateInterface : public UpdateModule, public AICommandInterface inline WhichTurretType friend_getTurretSync() const { return m_turretSyncFlag; } inline void friend_setTurretSync(WhichTurretType t) { m_turretSyncFlag = t; } - inline UnsignedInt getPriorWaypointID ( void ) { return m_priorWaypointID; }; - inline UnsignedInt getCurrentWaypointID ( void ) { return m_currentWaypointID; }; + inline UnsignedInt getPriorWaypointID(void) { return m_priorWaypointID; }; + inline UnsignedInt getCurrentWaypointID(void) { return m_currentWaypointID; }; - inline void clearMoveOutOfWay(void) {m_moveOutOfWay1 = INVALID_ID; m_moveOutOfWay2 = INVALID_ID;} + inline void clearMoveOutOfWay(void) { m_moveOutOfWay1 = INVALID_ID; m_moveOutOfWay2 = INVALID_ID; } - inline void setTmpValue(Int val) {m_tmpInt = val;} - inline Int getTmpValue(void) {return m_tmpInt;} + inline void setTmpValue(Int val) { m_tmpInt = val; } + inline Int getTmpValue(void) { return m_tmpInt; } - inline Bool getRetryPath(void) {return m_retryPath;} - - inline void setAllowedToChase( Bool allow ) { m_allowedToChase = allow; } + inline Bool getRetryPath(void) { return m_retryPath; } + + inline void setAllowedToChase(Bool allow) { m_allowedToChase = allow; } inline Bool isAllowedToChase() const { return m_allowedToChase; } // only for AIStateMachine. virtual void friend_notifyStateMachineChanged(); + //TEMP + inline int getLocomotorGoalType(void) { return m_locomotorGoalType; } + private: // this should only be called by load/save, or by chooseLocomotorSet. // it does no sanity checking; it just jams it in. diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleportMovementBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleportMovementBehavior.h new file mode 100644 index 00000000000..36e209b314d --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleportMovementBehavior.h @@ -0,0 +1,81 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: TeleportMovementBehavior.h ///////////////////////////////////////////////////////////////////////// +// Author: Graham Smallwood, July 2002 +// Desc: Behavior that reacts to poison Damage by continuously damaging us further in an Update +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __TeleportMovement_Behavior_H_ +#define __TeleportMovement_Behavior_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "GameLogic/Module/BehaviorModule.h" +#include "GameLogic/Module/UpdateModule.h" + + +//------------------------------------------------------------------------------------------------- +class TeleportMovementBehaviorModuleData : public UpdateModuleData +{ +public: + + TeleportMovementBehaviorModuleData(); + + static void buildFieldParse(MultiIniFieldParse& p); + + Real m_minDistance; + Real m_disabledDuration; + +private: + +}; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class TeleportMovementBehavior : public UpdateModule +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(TeleportMovementBehavior, "TeleportMovementBehavior") + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA(TeleportMovementBehavior, TeleportMovementBehaviorModuleData) + +public: + TeleportMovementBehavior(Thing* thing, const ModuleData* moduleData); + + // UpdateInterface + virtual UpdateSleepTime update(); + + void doTeleport(Coord3D targetPos, Real angle, Real dist); + +protected: + + +private: + + +}; + +#endif // __TeleportMovement_Behavior_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleporterAIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleporterAIUpdate.h new file mode 100644 index 00000000000..924337a1076 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleporterAIUpdate.h @@ -0,0 +1,99 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// TeleporterAIUpdate.h ////////// +// Will give self random move commands +// Author: Graham Smallwood, April 2002 + +#pragma once + +#ifndef _TELEPORTER_AI_UPDATE_H_ +#define _TELEPORTER_AI_UPDATE_H_ + +#include "GameLogic/Module/AIUpdate.h" + + +//------------------------------------------------------------------------------------------------- +class TeleporterAIUpdateModuleData : public AIUpdateModuleData +{ +public: + Real m_minDistance; + Real m_disabledDuration; + + TeleporterAIUpdateModuleData(); + + static void buildFieldParse(MultiIniFieldParse& p); + +private: + +}; +// ------------------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------- +class TeleporterAIUpdate : public AIUpdateInterface +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( TeleporterAIUpdate, "TeleporterAIUpdate" ) + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA(TeleporterAIUpdate, TeleporterAIUpdateModuleData) + + /* + IMPORTANT NOTE: if you ever add module data to this, you must have it inherit from + AIUpdateModuleData to allow locomotors to work correctly. (see SupplyTruckAIUpdate + for an example.) + */ + +public: + + TeleporterAIUpdate( Thing *thing, const ModuleData* moduleData ); + // virtual destructor prototype provided by memory pool declaration + + virtual UpdateSleepTime update(); + +protected: + + void doTeleport(Coord3D targetPos, Real angle, Real dist); + + Bool findAttackLocation(Object* victim, Coord3D* victimPos, Coord3D* targetPos); + + virtual UpdateSleepTime doLocomotor(); + + //virtual Bool getTreatAsAircraftForLocoDistToGoal() const; + + //virtual void chooseGoodLocomotorFromCurrentSet(); + + //virtual void doPathfind(PathfindServicesInterface* pathfinder); + //virtual void requestPath(Coord3D* destination, Bool isGoalDestination); ///< Queues a request to pathfind to destination. + //virtual void requestAttackPath(ObjectID victimID, const Coord3D* victimPos); ///< computes path to attack the current target, returns false if no path + //virtual void requestApproachPath(Coord3D* destination); ///< computes path to attack the current target, returns false if no path + //virtual void requestSafePath(ObjectID repulsor1); ///< computes path to attack the current target, returns false if no path + + virtual Bool canComputeQuickPath(void); ///< Returns true if we can quickly comput a path. Usually missiles & the like that just move straight to the destination. + virtual Bool computeQuickPath(const Coord3D* destination); ///< Computes a quick path to the destination. + + + virtual AIStateMachine* makeStateMachine(); + +}; + +#endif + diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/DisabledTypes.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/DisabledTypes.cpp index 65797c548b5..a85984e6744 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/DisabledTypes.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/DisabledTypes.cpp @@ -48,6 +48,8 @@ const char* DisabledMaskType::s_bitNameList[] = "DISABLED_SCRIPT_DISABLED", "DISABLED_SCRIPT_UNDERPOWERED", + "DISABLED_TELEPORT", + NULL }; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp index 6057df6810a..a05408e3058 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp @@ -1,212 +1,214 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// Kindof.cpp ///////////////////////////////////////////////////////////////////////////////////// -// Part of header detangling -// John McDonald, Aug 2002 - -#include "PreRTS.h" - -#include "Common/KindOf.h" -#include "Common/BitFlagsIO.h" - -const char* KindOfMaskType::s_bitNameList[] = -{ - "OBSTACLE", - "SELECTABLE", - "IMMOBILE", - "CAN_ATTACK", - "STICK_TO_TERRAIN_SLOPE", - "CAN_CAST_REFLECTIONS", - "SHRUBBERY", - "STRUCTURE", - "INFANTRY", - "VEHICLE", - "AIRCRAFT", - "HUGE_VEHICLE", - "DOZER", - "HARVESTER", - "COMMANDCENTER", -#ifdef ALLOW_SURRENDER - "PRISON", - "COLLECTS_PRISON_BOUNTY", - "POW_TRUCK", -#endif - "LINEBUILD", - "SALVAGER", - "WEAPON_SALVAGER", - "TRANSPORT", - "BRIDGE", - "LANDMARK_BRIDGE", - "BRIDGE_TOWER", - "PROJECTILE", - "PRELOAD", - "NO_GARRISON", - "WAVEGUIDE", - "WAVE_EFFECT", - "NO_COLLIDE", - "REPAIR_PAD", - "HEAL_PAD", - "STEALTH_GARRISON", - "CASH_GENERATOR", - "DRAWABLE_ONLY", - "MP_COUNT_FOR_VICTORY", - "REBUILD_HOLE", - "SCORE", - "SCORE_CREATE", - "SCORE_DESTROY", - "NO_HEAL_ICON", - "CAN_RAPPEL", - "PARACHUTABLE", -#ifdef ALLOW_SURRENDER - "CAN_SURRENDER", -#endif - "CAN_BE_REPULSED", - "MOB_NEXUS", - "IGNORED_IN_GUI", - "CRATE", - "CAPTURABLE", - "CLEARED_BY_BUILD", - "SMALL_MISSILE", - "ALWAYS_VISIBLE", - "UNATTACKABLE", - "MINE", - "CLEANUP_HAZARD", - "PORTABLE_STRUCTURE", - "ALWAYS_SELECTABLE", - "ATTACK_NEEDS_LINE_OF_SIGHT", - "WALK_ON_TOP_OF_WALL", - "DEFENSIVE_WALL", - "FS_POWER", - "FS_FACTORY", - "FS_BASE_DEFENSE", - "FS_TECHNOLOGY", - "AIRCRAFT_PATH_AROUND", - "LOW_OVERLAPPABLE", - "FORCEATTACKABLE", - "AUTO_RALLYPOINT", - "TECH_BUILDING", - "POWERED", - "PRODUCED_AT_HELIPAD", - "DRONE", - "CAN_SEE_THROUGH_STRUCTURE", - "BALLISTIC_MISSILE", - "CLICK_THROUGH", - "SUPPLY_SOURCE_ON_PREVIEW", - "PARACHUTE", - "GARRISONABLE_UNTIL_DESTROYED", - "BOAT", - "IMMUNE_TO_CAPTURE", - "HULK", - "SHOW_PORTRAIT_WHEN_CONTROLLED", - "SPAWNS_ARE_THE_WEAPONS", - "CANNOT_BUILD_NEAR_SUPPLIES", - "SUPPLY_SOURCE", - "REVEAL_TO_ALL", - "DISGUISER", - "INERT", - "HERO", - "IGNORES_SELECT_ALL", - "DONT_AUTO_CRUSH_INFANTRY", - "CLIFF_JUMPER", - "FS_SUPPLY_DROPZONE", - "FS_SUPERWEAPON", - "FS_BLACK_MARKET", - "FS_SUPPLY_CENTER", - "FS_STRATEGY_CENTER", - "MONEY_HACKER", - "ARMOR_SALVAGER", - "REVEALS_ENEMY_PATHS", - "BOOBY_TRAP", - "FS_FAKE", - "FS_INTERNET_CENTER", - "BLAST_CRATER", - "PROP", - "OPTIMIZED_TREE", - "FS_ADVANCED_TECH", - "FS_BARRACKS", - "FS_WARFACTORY", - "FS_AIRFIELD", - "AIRCRAFT_CARRIER", - "NO_SELECT", - "REJECT_UNMANNED", - "CANNOT_RETALIATE", - "TECH_BASE_DEFENSE", - "EMP_HARDENED", - "DEMOTRAP", - "CONSERVATIVE_BUILDING", - "IGNORE_DOCKING_BONES", - - "VTOL", - "LARGE_AIRCRAFT", - "MEDIUM_AIRCRAFT", - "SMALL_AIRCRAFT", - "ARTILLERY", - "HEAVY_ARTILLERY", - "ANTI_AIR", - "SCOUT", - "COMMANDO", - "HEAVY_INFANTRY", - "SUPERHEAVY_VEHICLE", - - "EXTRA1", - "EXTRA2", - "EXTRA3", - "EXTRA4", - "EXTRA5", - "EXTRA6", - "EXTRA7", - "EXTRA8", - "EXTRA9", - "EXTRA10", - "EXTRA11", - "EXTRA12", - "EXTRA13", - "EXTRA14", - "EXTRA15", - "EXTRA16", - - NULL -}; - -KindOfMaskType KINDOFMASK_NONE; // inits to all zeroes -KindOfMaskType KINDOFMASK_FS; // inits to all zeroes - -void initKindOfMasks() -{ - KINDOFMASK_FS.set( KINDOF_FS_FACTORY ); - KINDOFMASK_FS.set( KINDOF_FS_BASE_DEFENSE ); - KINDOFMASK_FS.set( KINDOF_FS_TECHNOLOGY ); - KINDOFMASK_FS.set( KINDOF_FS_SUPPLY_DROPZONE ); - KINDOFMASK_FS.set( KINDOF_FS_SUPERWEAPON ); - KINDOFMASK_FS.set( KINDOF_FS_BLACK_MARKET ); - KINDOFMASK_FS.set( KINDOF_FS_SUPPLY_CENTER ); - KINDOFMASK_FS.set( KINDOF_FS_STRATEGY_CENTER ); - KINDOFMASK_FS.set( KINDOF_FS_FAKE ); - KINDOFMASK_FS.set( KINDOF_FS_INTERNET_CENTER ); - KINDOFMASK_FS.set( KINDOF_FS_ADVANCED_TECH ); - KINDOFMASK_FS.set( KINDOF_FS_BARRACKS ); - KINDOFMASK_FS.set( KINDOF_FS_WARFACTORY ); - KINDOFMASK_FS.set( KINDOF_FS_AIRFIELD ); -} +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// Kindof.cpp ///////////////////////////////////////////////////////////////////////////////////// +// Part of header detangling +// John McDonald, Aug 2002 + +#include "PreRTS.h" + +#include "Common/KindOf.h" +#include "Common/BitFlagsIO.h" + +const char* KindOfMaskType::s_bitNameList[] = +{ + "OBSTACLE", + "SELECTABLE", + "IMMOBILE", + "CAN_ATTACK", + "STICK_TO_TERRAIN_SLOPE", + "CAN_CAST_REFLECTIONS", + "SHRUBBERY", + "STRUCTURE", + "INFANTRY", + "VEHICLE", + "AIRCRAFT", + "HUGE_VEHICLE", + "DOZER", + "HARVESTER", + "COMMANDCENTER", +#ifdef ALLOW_SURRENDER + "PRISON", + "COLLECTS_PRISON_BOUNTY", + "POW_TRUCK", +#endif + "LINEBUILD", + "SALVAGER", + "WEAPON_SALVAGER", + "TRANSPORT", + "BRIDGE", + "LANDMARK_BRIDGE", + "BRIDGE_TOWER", + "PROJECTILE", + "PRELOAD", + "NO_GARRISON", + "WAVEGUIDE", + "WAVE_EFFECT", + "NO_COLLIDE", + "REPAIR_PAD", + "HEAL_PAD", + "STEALTH_GARRISON", + "CASH_GENERATOR", + "DRAWABLE_ONLY", + "MP_COUNT_FOR_VICTORY", + "REBUILD_HOLE", + "SCORE", + "SCORE_CREATE", + "SCORE_DESTROY", + "NO_HEAL_ICON", + "CAN_RAPPEL", + "PARACHUTABLE", +#ifdef ALLOW_SURRENDER + "CAN_SURRENDER", +#endif + "CAN_BE_REPULSED", + "MOB_NEXUS", + "IGNORED_IN_GUI", + "CRATE", + "CAPTURABLE", + "CLEARED_BY_BUILD", + "SMALL_MISSILE", + "ALWAYS_VISIBLE", + "UNATTACKABLE", + "MINE", + "CLEANUP_HAZARD", + "PORTABLE_STRUCTURE", + "ALWAYS_SELECTABLE", + "ATTACK_NEEDS_LINE_OF_SIGHT", + "WALK_ON_TOP_OF_WALL", + "DEFENSIVE_WALL", + "FS_POWER", + "FS_FACTORY", + "FS_BASE_DEFENSE", + "FS_TECHNOLOGY", + "AIRCRAFT_PATH_AROUND", + "LOW_OVERLAPPABLE", + "FORCEATTACKABLE", + "AUTO_RALLYPOINT", + "TECH_BUILDING", + "POWERED", + "PRODUCED_AT_HELIPAD", + "DRONE", + "CAN_SEE_THROUGH_STRUCTURE", + "BALLISTIC_MISSILE", + "CLICK_THROUGH", + "SUPPLY_SOURCE_ON_PREVIEW", + "PARACHUTE", + "GARRISONABLE_UNTIL_DESTROYED", + "BOAT", + "IMMUNE_TO_CAPTURE", + "HULK", + "SHOW_PORTRAIT_WHEN_CONTROLLED", + "SPAWNS_ARE_THE_WEAPONS", + "CANNOT_BUILD_NEAR_SUPPLIES", + "SUPPLY_SOURCE", + "REVEAL_TO_ALL", + "DISGUISER", + "INERT", + "HERO", + "IGNORES_SELECT_ALL", + "DONT_AUTO_CRUSH_INFANTRY", + "CLIFF_JUMPER", + "FS_SUPPLY_DROPZONE", + "FS_SUPERWEAPON", + "FS_BLACK_MARKET", + "FS_SUPPLY_CENTER", + "FS_STRATEGY_CENTER", + "MONEY_HACKER", + "ARMOR_SALVAGER", + "REVEALS_ENEMY_PATHS", + "BOOBY_TRAP", + "FS_FAKE", + "FS_INTERNET_CENTER", + "BLAST_CRATER", + "PROP", + "OPTIMIZED_TREE", + "FS_ADVANCED_TECH", + "FS_BARRACKS", + "FS_WARFACTORY", + "FS_AIRFIELD", + "AIRCRAFT_CARRIER", + "NO_SELECT", + "REJECT_UNMANNED", + "CANNOT_RETALIATE", + "TECH_BASE_DEFENSE", + "EMP_HARDENED", + "DEMOTRAP", + "CONSERVATIVE_BUILDING", + "IGNORE_DOCKING_BONES", + + "VTOL", + "LARGE_AIRCRAFT", + "MEDIUM_AIRCRAFT", + "SMALL_AIRCRAFT", + "ARTILLERY", + "HEAVY_ARTILLERY", + "ANTI_AIR", + "SCOUT", + "COMMANDO", + "HEAVY_INFANTRY", + "SUPERHEAVY_VEHICLE", + + "TELEPORTER", + + "EXTRA1", + "EXTRA2", + "EXTRA3", + "EXTRA4", + "EXTRA5", + "EXTRA6", + "EXTRA7", + "EXTRA8", + "EXTRA9", + "EXTRA10", + "EXTRA11", + "EXTRA12", + "EXTRA13", + "EXTRA14", + "EXTRA15", + "EXTRA16", + + NULL +}; + +KindOfMaskType KINDOFMASK_NONE; // inits to all zeroes +KindOfMaskType KINDOFMASK_FS; // inits to all zeroes + +void initKindOfMasks() +{ + KINDOFMASK_FS.set( KINDOF_FS_FACTORY ); + KINDOFMASK_FS.set( KINDOF_FS_BASE_DEFENSE ); + KINDOFMASK_FS.set( KINDOF_FS_TECHNOLOGY ); + KINDOFMASK_FS.set( KINDOF_FS_SUPPLY_DROPZONE ); + KINDOFMASK_FS.set( KINDOF_FS_SUPERWEAPON ); + KINDOFMASK_FS.set( KINDOF_FS_BLACK_MARKET ); + KINDOFMASK_FS.set( KINDOF_FS_SUPPLY_CENTER ); + KINDOFMASK_FS.set( KINDOF_FS_STRATEGY_CENTER ); + KINDOFMASK_FS.set( KINDOF_FS_FAKE ); + KINDOFMASK_FS.set( KINDOF_FS_INTERNET_CENTER ); + KINDOFMASK_FS.set( KINDOF_FS_ADVANCED_TECH ); + KINDOFMASK_FS.set( KINDOF_FS_BARRACKS ); + KINDOFMASK_FS.set( KINDOF_FS_WARFACTORY ); + KINDOFMASK_FS.set( KINDOF_FS_AIRFIELD ); +} diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp index 03b13b8c8fa..decb84fa74c 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp @@ -1,813 +1,815 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: MemoryInit.cpp -//----------------------------------------------------------------------------- -// -// Westwood Studios Pacific. -// -// Confidential Information -// Copyright (C) 2001 - All Rights Reserved -// -//----------------------------------------------------------------------------- -// -// Project: RTS3 -// -// File name: MemoryInit.cpp -// -// Created: Steven Johnson, August 2001 -// -// Desc: Memory manager -// -// ---------------------------------------------------------------------------- -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -// SYSTEM INCLUDES - -// USER INCLUDES -#include "Lib/BaseType.h" -#include "Common/GameMemory.h" - -#ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -//----------------------------------------------------------------------------- -void userMemoryManagerGetDmaParms(Int *numSubPools, const PoolInitRec **pParms) -{ - static const PoolInitRec defaultDMA[7] = - { - // name, allocsize, initialcount, overflowcount - { "dmaPool_16", 16, 130000, 10000 }, - { "dmaPool_32", 32, 250000, 10000 }, - { "dmaPool_64", 64, 100000, 10000 }, - { "dmaPool_128", 128, 80000, 10000 }, - { "dmaPool_256", 256, 20000, 5000 }, - { "dmaPool_512", 512, 16000, 5000 }, - { "dmaPool_1024", 1024, 6000, 1024} - }; - - *numSubPools = 7; - *pParms = defaultDMA; -} - -//----------------------------------------------------------------------------- -struct PoolSizeRec -{ - const char* name; - Int initial; - Int overflow; -}; - -//----------------------------------------------------------------------------- -// And please be careful of duplicates. They are not rejected. -// not const -- we might override from INI -static PoolSizeRec sizes[] = -{ - { "PartitionContactListNode", 2048, 512 }, - { "BattleshipUpdate", 32, 32 }, - { "FlyToDestAndDestroyUpdate", 32, 32 }, - { "MusicTrack", 32, 32 }, - { "PositionalSoundPool", 32, 32 }, - { "GameMessage", 2048, 32 }, - { "NameKeyBucketPool", 9000, 1024 }, - { "ObjectSellInfo", 16, 16 }, - { "ProductionPrerequisitePool", 1024, 32 }, - { "RadarObject", 512, 32 }, - { "ResourceGatheringManager", 16, 16 }, - { "SightingInfo", 8192, 2048 },// Looks big, but all 3000 objects used to have 4 just built in. - { "SpecialPowerTemplate", 84, 32 }, - { "StateMachinePool", 32, 32 }, - { "TeamPool", 128, 32 }, // if you increase this, increase player/team relation map pools - { "PlayerRelationMapPool", 128, 32 }, - { "TeamRelationMapPool", 128, 32 }, - { "TeamPrototypePool", 256, 32 }, - { "TerrainType", 256, 32 }, - { "ThingTemplatePool", 2120, 32 }, - { "TunnelTracker", 16, 16 }, - { "Upgrade", 16, 16 }, - { "UpgradeTemplate", 128, 16 }, - { "Anim2D", 32, 32 }, - { "CommandButton", 1024, 256 }, - { "CommandSet", 820, 16 }, - { "DisplayString", 32, 32 }, - { "WebBrowserURL", 16, 16 }, - { "Drawable", 4096, 32 }, - { "Image", 2048, 32 }, - { "ParticlePool", 1400, 1024 }, - { "ParticleSystemTemplatePool", 1100, 32 }, - { "ParticleSystemPool", 1024, 32 }, - { "TerrainRoadType", 100, 32, }, - { "WindowLayoutPool", 32, 32 }, - { "AnimatedParticleSysBoneClientUpdate", 16, 16 }, - { "SwayClientUpdate", 32, 32 }, - { "BeaconClientUpdate", 64, 32 }, - { "AIGroupPool", 64, 32 }, - { "AIDockMachinePool", 256, 32 }, - { "AIGuardMachinePool", 32, 32 }, - { "AIGuardRetaliateMachinePool", 32, 32 }, - { "AITNGuardMachinePool", 32, 32 }, - { "PathNodePool", 8192, 1024 }, - { "PathPool", 256, 16 }, - { "WorkOrder", 32, 32 }, - { "TeamInQueue", 32, 32 }, - { "AIPlayer", 12, 4 }, - { "AISkirmishPlayer", 8, 8 }, - { "AIStateMachine", 600, 32 }, - { "JetAIStateMachine", 64, 32 }, - { "HeliAIStateMachine", 64, 32 }, - { "VtolAIStateMachine", 64, 32 }, - { "AIAttackMoveStateMachine", 2048, 32 }, - { "AIAttackThenIdleStateMachine", 512, 32 }, - { "AttackStateMachine", 512, 32 }, - { "CrateTemplate", 32, 32 }, - { "ExperienceTrackerPool", 2048, 512 }, - { "FiringTrackerPool", 4096, 256 }, - { "ObjectRepulsorHelper", 1024, 256 }, - { "ObjectSMCHelperPool", 2048, 256 }, - { "ObjectWeaponStatusHelperPool", 4096, 256 }, - { "ObjectDefectionHelperPool", 2048, 256 }, - { "StatusDamageHelper", 1500, 256 }, - { "SubdualDamageHelper", 1500, 256 }, - { "TempWeaponBonusHelper", 4096, 256 }, - { "Locomotor", 2048, 32 }, - { "LocomotorTemplate", 192, 32 }, - { "ObjectPool", 1500, 256 }, - { "SimpleObjectIteratorPool", 32, 32 }, - { "SimpleObjectIteratorClumpPool", 4096, 32 }, - { "PartitionDataPool", 2048, 512 }, - { "BuildEntry", 32, 32 }, - { "Weapon", 4096, 32 }, - { "WeaponTemplate", 360, 32 }, - { "AIUpdateInterface", 600, 32 }, - { "ActiveBody", 1024, 32 }, - { "ActiveShroudUpgrade", 32, 32 }, - { "AssistedTargetingUpdate", 32, 32 }, - { "AudioEventInfo", 4096, 64 }, - { "AudioRequest", 256, 8 }, - { "AutoHealBehavior", 1024, 256 }, - { "WeaponBonusUpdate", 16, 16 }, - { "GrantStealthBehavior", 4096, 32 }, - { "NeutronBlastBehavior", 4096, 32 }, - { "CountermeasuresBehavior", 256, 32 }, - { "BaseRegenerateUpdate", 128, 32 }, - { "BoneFXDamage", 64, 32 }, - { "BoneFXUpdate", 64, 32 }, - { "BridgeBehavior", 4, 4 }, - { "BridgeTowerBehavior", 32, 32 }, - { "BridgeScaffoldBehavior", 32, 32 }, - { "CaveContain", 16, 16 }, - { "HealContain", 32, 32 }, - { "CreateCrateDie", 256, 128 }, - { "CreateObjectDie", 1024, 32 }, - { "EjectPilotDie", 1024, 32 }, - { "CrushDie", 1024, 32 }, - { "DamDie", 8, 8 }, - { "DeliverPayloadStateMachine", 32, 32 }, - { "DeliverPayloadAIUpdate", 32, 32 }, - { "DeletionUpdate", 128, 32 }, - { "SmartBombTargetHomingUpdate", 8, 8 }, - { "DynamicAudioEventInfo", 16, 256 }, // Note: some levels have none, some have lots. Since all are allocated at level load time, we can set this low for the levels with none. - { "HackInternetStateMachine", 32, 32 }, - { "HackInternetAIUpdate", 32, 32 }, - { "MissileAIUpdate", 512, 32 }, - { "DumbProjectileBehavior", 64, 32 }, - { "FreeFallProjectileBehavior", 32, 32 }, - { "DestroyDie", 1024, 32 }, - { "UpgradeDie", 128, 32 }, - { "KeepObjectDie", 128, 32 }, - { "DozerAIUpdate", 32, 32 }, - { "DynamicGeometryInfoUpdate", 16, 16 }, - { "DynamicShroudClearingRangeUpdate", 128, 16 }, - { "FXListDie", 1024, 32 }, - { "FireSpreadUpdate", 2048, 128 }, - { "FirestormDynamicGeometryInfoUpdate", 16, 16 }, - { "FireWeaponCollide", 2048, 32 }, - { "FireWeaponUpdate", 32, 32 }, - { "FlammableUpdate", 512, 256 }, - { "FloatUpdate", 512, 128 }, - { "TensileFormationUpdate", 256, 32 }, - { "GarrisonContain", 256, 32 }, - { "HealCrateCollide", 32, 32 }, - { "HeightDieUpdate", 32, 32 }, - { "ScatterShotUpdate", 128, 64 }, - { "FireWeaponWhenDamagedBehavior", 32, 32 }, - { "FireWeaponWhenDeadBehavior", 128, 64 }, - { "GenerateMinefieldBehavior", 32, 32 }, - { "HelicopterSlowDeathBehavior", 64, 32 }, - { "ParkingPlaceBehavior", 32, 32 }, - { "FlightDeckBehavior", 8, 8 }, -#ifdef ALLOW_SURRENDER - { "POWTruckAIUpdate", 32, 32, }, - { "POWTruckBehavior", 32, 32, }, - { "PrisonBehavior", 32, 32 }, - { "PrisonVisual", 32, 32 }, - { "PropagandaCenterBehavior", 16, 16 }, -#endif - { "PropagandaTowerBehavior", 16, 16 }, - { "BunkerBusterBehavior", 16, 16 }, - { "ObjectTracker", 128, 32 }, - { "OCLUpdate", 16, 16 }, - { "BodyParticleSystem", 196, 64 }, - { "HighlanderBody", 2048, 128 }, - { "UndeadBody", 32, 32 }, - { "HordeUpdate", 128, 32 }, - { "ImmortalBody", 128, 256 }, - { "InactiveBody", 2048, 32 }, - { "InstantDeathBehavior", 512, 32 }, - { "LaserUpdate", 32, 32 }, - { "PointDefenseLaserUpdate", 32, 32 }, - { "CleanupHazardUpdate", 32, 32 }, - { "AutoFindHealingUpdate", 256, 32 }, - { "CommandButtonHuntUpdate", 512, 8 }, - { "PilotFindVehicleUpdate", 256, 32 }, - { "DemoTrapUpdate", 32, 32 }, - { "ParticleUplinkCannonUpdate", 16, 16 }, - { "SpectreGunshipUpdate", 8, 8 }, - { "SpectreGunshipDeploymentUpdate", 8, 8 }, - { "BaikonurLaunchPower", 4, 4 }, - { "RadiusDecalUpdate", 16, 16 }, - { "RadiusDecalBehavior", 32, 32 }, - { "BattlePlanUpdate", 32, 32 }, - { "LifetimeUpdate", 32, 32 }, - { "LocomotorSetUpgrade", 512, 128 }, - { "LockWeaponCreate", 64, 128 }, - { "AutoDepositUpdate", 256, 32 }, - { "NeutronMissileUpdate", 512, 32 }, - { "MoneyCrateCollide", 48, 16 }, - { "NeutronMissileSlowDeathBehavior", 8, 8 }, - { "OpenContain", 128, 32 }, - { "OverchargeBehavior", 32, 32 }, - { "OverlordContain", 32, 32 }, - { "HelixContain", 32, 32 }, - { "ParachuteContain", 128, 32 }, - { "PhysicsBehavior", 600, 32 }, - { "PoisonedBehavior", 512, 64 }, - { "ProductionEntry", 32, 32 }, - { "ProductionUpdate", 256, 32 }, - { "ProjectileStreamUpdate", 32, 32 }, - { "ProneUpdate", 128, 32 }, - { "QueueProductionExitUpdate", 32, 32 }, - { "RadarUpdate", 16, 16 }, - { "RadarUpgrade", 16, 16 }, - { "AnimationSteeringUpdate", 1024, 32 }, - { "SupplyWarehouseCripplingBehavior", 16, 16 }, - { "CostModifierUpgrade", 32, 32 }, - { "ProductionTimeModifierUpgrade", 32, 32 }, - { "UnitProductionBonusUpgrade", 64, 32 }, - { "CashBountyPower", 32, 32 }, - { "CleanupAreaPower", 32, 32 }, - { "ObjectCreationUpgrade", 196, 32 }, - { "MinefieldBehavior", 256, 32 }, - { "JetSlowDeathBehavior", 64, 32 }, - { "BattleBusSlowDeathBehavior", 64, 32 }, - { "RebuildHoleBehavior", 64, 32 }, - { "RebuildHoleExposeDie", 64, 32 }, - { "RepairDockUpdate", 32, 32 }, -#ifdef ALLOW_SURRENDER - { "PrisonDockUpdate", 32, 32 }, -#endif - { "RailedTransportDockUpdate", 16, 16 }, - { "RailedTransportAIUpdate", 16, 16 }, - { "RailedTransportContain", 16, 16 }, - { "RailroadBehavior", 16, 16 }, - { "SalvageCrateCollide", 32, 32 }, - { "ShroudCrateCollide", 32, 32 }, - { "SlavedUpdate", 64, 32 }, - { "SlowDeathBehavior", 1400, 256 }, - { "SpyVisionUpdate", 16, 16 }, - { "DefaultProductionExitUpdate", 32, 32 }, - { "SpawnPointProductionExitUpdate", 32, 32 }, - { "SpawnBehavior", 32, 32 }, - { "SpecialPowerCompletionDie", 32, 32 }, - { "SpecialPowerCreate", 32, 32 }, - { "PreorderCreate", 32, 32 }, - { "SpecialAbility", 512, 32 }, - { "SpecialAbilityUpdate", 512, 32 }, - { "MissileLauncherBuildingUpdate", 32, 32 }, - { "SquishCollide", 512, 32 }, - { "StructureBody", 512, 64 }, - { "HiveStructureBody", 64, 32 }, //Stinger sites - { "StructureCollapseUpdate", 32, 32 }, - { "StructureToppleUpdate", 32, 32 }, - { "SupplyCenterCreate", 32, 32 }, - { "SupplyCenterDockUpdate", 32, 32 }, - { "SupplyCenterProductionExitUpdate", 32, 32 }, - { "SupplyTruckStateMachine", 256, 32 }, - { "SupplyTruckAIUpdate", 32, 32 }, - { "SupplyWarehouseCreate", 48, 16 }, - { "SupplyWarehouseDockUpdate", 48, 16 }, - { "EnemyNearUpdate", 1024, 32 }, - { "TechBuildingBehavior", 32, 32 }, - { "ToppleUpdate", 256, 128 }, - { "TransitionDamageFX", 384, 128 }, - { "TransportAIUpdate", 64, 32 }, - { "TransportContain", 128, 32 }, - { "RiderChangeContain", 128, 32 }, - { "InternetHackContain", 16, 16 }, - { "TunnelContain", 8, 8 }, - { "TunnelContainDie", 32, 32 }, - { "TunnelCreate", 32, 32 }, - { "TurretAI", 256, 32 }, - { "TurretStateMachine", 128, 32 }, - { "TurretSwapUpgrade", 512, 128 }, - { "UnitCrateCollide", 32, 32 }, - { "UnpauseSpecialPowerUpgrade", 32, 32 }, - { "VeterancyCrateCollide", 32, 32 }, - { "VeterancyGainCreate", 512, 128 }, - { "ConvertToCarBombCrateCollide", 256, 128 }, - { "ConvertToHijackedVehicleCrateCollide", 256, 128 }, - { "SabotageCommandCenterCrateCollide", 256, 128 }, - { "SabotageFakeBuildingCrateCollide", 256, 128 }, - { "SabotageInternetCenterCrateCollide", 256, 128 }, - { "SabotageMilitaryFactoryCrateCollide", 256, 128 }, - { "SabotagePowerPlantCrateCollide", 256, 128 }, - { "SabotageSuperweaponCrateCollide", 256, 128 }, - { "SabotageSupplyCenterCrateCollide", 256, 128 }, - { "SabotageSupplyDropzoneCrateCollide", 256, 128 }, - { "JetAIUpdate", 64, 32 }, - { "ChinookAIUpdate", 32, 32 }, - { "WanderAIUpdate", 32, 32 }, - { "WaveGuideUpdate", 16, 16 }, - { "ArmorDamageScalarUpdate", 256, 32 }, - { "WeaponBonusUpgrade", 512, 128 }, - { "WeaponSetUpgrade", 512, 128 }, - { "ArmorUpgrade", 512, 128 }, - { "WorkerAIUpdate", 128, 128 }, - { "WorkerStateMachine", 128, 128 }, - { "ChinookAIStateMachine", 32, 32 }, - { "DeployStyleAIUpdate", 32, 32 }, - { "AssaultTransportAIUpdate", 64, 32 }, - { "StreamingArchiveFile", 8, 8 }, - - { "DozerActionStateMachine", 256, 32 }, - { "DozerPrimaryStateMachine", 256, 32 }, - { "W3DDisplayString", 1400, 128 }, - { "W3DDefaultDraw", 1024, 128 }, - { "W3DDebrisDraw", 128, 128 }, - { "W3DDependencyModelDraw", 64, 64 }, - { "W3DLaserDraw", 32, 32 }, - { "W3DModelDraw", 2048, 512 }, - { "W3DOverlordTankDraw", 64, 64 }, - { "W3DOverlordTruckDraw", 64, 64 }, - { "W3DOverlordAircraftDraw", 64, 64 }, - { "W3DPoliceCarDraw", 32, 32 }, - { "W3DProjectileStreamDraw", 32, 32 }, - { "W3DRopeDraw", 32, 32 }, - { "W3DScienceModelDraw", 32, 32 }, - { "W3DSupplyDraw", 40, 16 }, - { "W3DTankDraw", 256, 32 }, - { "W3DTreeDraw", 16, 16 }, - { "W3DPropDraw", 16, 16 }, - { "W3DTracerDraw", 64, 32 }, - { "W3DTruckDraw", 128, 32 }, - { "W3DTankTruckDraw", 32, 16 }, - { "W3DTreeTextureClass", 4, 4 }, - { "DefaultSpecialPower", 32, 32 }, - { "OCLSpecialPower", 96, 32 }, - { "FireWeaponPower", 32, 32 }, -#ifdef ALLOW_DEMORALIZE - { "DemoralizeSpecialPower", 16, 16, }, -#endif - { "CashHackSpecialPower", 32, 32 }, - { "CommandSetUpgrade", 32, 32 }, - { "PassengersFireUpgrade", 32, 32 }, - { "GrantUpgradeCreate", 256, 32 }, - { "GrantScienceUpgrade", 256, 32 }, - { "ReplaceObjectUpgrade", 32, 32 }, - { "ModelConditionUpgrade", 32, 32 }, - { "SpyVisionSpecialPower", 256, 32 }, - { "StealthDetectorUpdate", 256, 32 }, - { "StealthUpdate", 512, 128 }, - { "StealthUpgrade", 256, 32 }, - { "StatusBitsUpgrade", 128, 128 }, - { "SubObjectsUpgrade", 128, 128 }, - { "ExperienceScalarUpgrade", 256, 128 }, - { "MaxHealthUpgrade", 128, 128 }, - { "WeaponBonusUpgrade", 128, 64 }, - { "StickyBombUpdate", 64, 32 }, - { "FireOCLAfterWeaponCooldownUpdate", 64, 32 }, - { "HijackerUpdate", 64, 32 }, - { "ChinaMinesUpgrade", 64, 32 }, - { "PowerPlantUpdate", 48, 16 }, - { "PowerPlantUpgrade", 48, 16 }, - { "DefectorSpecialPower", 16, 16 }, - { "CheckpointUpdate", 16, 16 }, - { "MobNexusContain", 128, 32 }, - { "MobMemberSlavedUpdate", 64, 32 }, - { "EMPUpdate", 64, 32 }, - { "LeafletDropBehavior", 64, 32 }, - { "Overridable", 32, 32 }, - - { "W3DGameWindow", 700, 256 }, - { "SuccessState", 32, 32 }, - { "FailureState", 32, 32 }, - { "ContinueState", 32, 32 }, - { "SleepState", 32, 32 }, - - { "AIDockWaitForClearanceState", 256, 32 }, - { "AIDockProcessDockState", 256, 32 }, - { "AIGuardInnerState", 32, 32 }, - { "AIGuardIdleState", 32, 32 }, - { "AIGuardOuterState", 32, 32 }, - { "AIGuardReturnState", 32, 32 }, - { "AIGuardPickUpCrateState", 32, 32 }, - { "AIGuardAttackAggressorState", 32, 32 }, - { "AIGuardRetaliateInnerState", 32, 32 }, - { "AIGuardRetaliateIdleState", 32, 32 }, - { "AIGuardRetaliateOuterState", 32, 32 }, - { "AIGuardRetaliateReturnState", 32, 32 }, - { "AIGuardRetaliatePickUpCrateState", 32, 32 }, - { "AIGuardRetaliateAttackAggressorState", 32, 32 }, - { "AITNGuardInnerState", 32, 32 }, - { "AITNGuardIdleState", 32, 32 }, - { "AITNGuardOuterState", 32, 32 }, - { "AITNGuardReturnState", 32, 32 }, - { "AITNGuardPickUpCrateState", 32, 32 }, - { "AITNGuardAttackAggressorState", 32, 32 }, - { "AIIdleState", 2400, 32 }, - { "AIRappelState", 600, 32 }, - { "AIBusyState", 600, 32 }, - { "AIWaitState", 600, 32 }, - { "AIAttackState", 4096, 32 }, - { "AIAttackSquadState", 600, 32 }, - { "AIDeadState", 600, 32 }, - { "AIDockState", 600, 32 }, - { "AIExitState", 600, 32 }, - { "AIExitInstantlyState", 600, 32 }, - { "AIGuardState", 600, 32 }, - { "AIGuardRetaliateState", 600, 32 }, - { "AITunnelNetworkGuardState", 600, 32 }, - { "AIHuntState", 600, 32 }, - { "AIAttackAreaState", 600, 32 }, - { "AIFaceState", 1200, 32 }, - { "ApproachState", 600, 32 }, - { "DeliveringState", 600, 32 }, - { "ConsiderNewApproachState", 600, 32 }, - { "RecoverFromOffMapState", 600, 32 }, - { "HeadOffMapState", 600, 32 }, - { "CleanUpState", 600, 32 }, - { "HackInternetState", 600, 32 }, - { "PackingState", 600, 32 }, - { "UnpackingState", 600, 32 }, - { "SupplyTruckWantsToPickUpOrDeliverBoxesState", 600, 32 }, - { "RegroupingState", 600, 32 }, - { "DockingState", 600, 32 }, - { "ChinookEvacuateState", 32, 32 }, - { "ChinookHeadOffMapState", 32, 32 }, - { "ChinookTakeoffOrLandingState", 32, 32 }, - { "ChinookCombatDropState", 32, 32 }, - { "DozerActionPickActionPosState", 256, 32 }, - { "DozerActionMoveToActionPosState", 256, 32 }, - { "DozerActionDoActionState", 256, 32 }, - { "DozerPrimaryIdleState", 256, 32 }, - { "DozerActionState", 256, 32 }, - { "DozerPrimaryGoingHomeState", 256, 32 }, - { "JetAwaitingRunwayState", 64, 32 }, - { "JetOrHeliCirclingDeadAirfieldState", 64, 32 }, - { "HeliTakeoffOrLandingState", 64, 32 }, - { "VtolTakeoffOrLandingState", 64, 32 }, - { "JetOrHeliParkOrientState", 64, 32 }, - { "VtolParkOrientState", 64, 32 }, - { "JetOrHeliReloadAmmoState", 64, 32 }, - { "SupplyTruckBusyState", 600, 32 }, - { "SupplyTruckIdleState", 600, 32 }, - { "ActAsDozerState", 600, 32 }, - { "ActAsSupplyTruckState", 600, 32 }, - { "AIDockApproachState", 256, 32 }, - { "AIDockAdvancePositionState", 256, 32 }, - { "AIDockMoveToEntryState", 256, 32 }, - { "AIDockMoveToDockState", 256, 32 }, - { "AIDockMoveToExitState", 256, 32 }, - { "AIDockMoveToRallyState", 256, 32 }, - { "AIMoveToState", 600, 32 }, - { "AIMoveOutOfTheWayState", 600, 32 }, - { "AIMoveAndTightenState", 600, 32 }, - { "AIMoveAwayFromRepulsorsState", 600, 32 }, - { "AIAttackApproachTargetState", 96, 32 }, - { "AIAttackPursueTargetState", 96, 32 }, - { "AIAttackAimAtTargetState", 96, 32 }, - { "AIAttackFireWeaponState", 256, 32 }, - { "AIPickUpCrateState", 4096, 32 }, - { "AIFollowWaypointPathState", 1200, 32 }, - { "AIFollowWaypointPathExactState", 1200, 32 }, - { "AIWanderInPlaceState", 600, 32 }, - { "AIFollowPathState", 1200, 32 }, - { "AIMoveAndEvacuateState", 1200, 32 }, - { "AIMoveAndDeleteState", 600, 32 }, - { "AIEnterState", 600, 32 }, - { "JetOrHeliReturningToDeadAirfieldState", 64, 32 }, - { "JetOrHeliReturnForLandingState", 64, 32 }, - { "TurretAIIdleState", 600, 32 }, - { "TurretAIIdleScanState", 600, 32 }, - { "TurretAIAimTurretState", 600, 32 }, - { "TurretAIRecenterTurretState", 600, 32 }, - { "TurretAIHoldTurretState", 600, 32 }, - { "JetOrHeliTaxiState", 64, 32 }, - { "JetTakeoffOrLandingState", 64, 32 }, - { "JetPauseBeforeTakeoffState", 64, 32 }, - { "AIAttackMoveToState", 600, 32 }, - { "AIAttackFollowWaypointPathState", 1200, 32 }, - { "AIWanderState", 600, 32 }, - { "AIPanicState", 600, 32 }, - { "ChinookMoveToBldgState", 32, 32 }, - { "ChinookRecordCreationState", 32, 32 }, - { "ScienceInfo", 96, 32 }, - { "RankInfo", 32, 32 }, - - { "FireWeaponNugget", 32, 32 }, - { "AttackNugget", 32, 32 }, - { "DeliverPayloadNugget", 48, 32 }, - { "ApplyRandomForceNugget", 32, 32 }, - { "GenericObjectCreationNugget", 632, 32 }, - { "SoundFXNugget", 320, 32 }, - { "TracerFXNugget", 32, 32 }, - { "RayEffectFXNugget", 32, 32 }, - { "LightPulseFXNugget", 68, 32 }, - { "ViewShakeFXNugget", 140, 32 }, - { "TerrainScorchFXNugget", 48, 32 }, - { "ParticleSystemFXNugget", 832, 32 }, - { "FXListAtBonePosFXNugget", 32, 32 }, - { "Squad", 256, 32 }, - { "BuildListInfo", 400, 64 }, - - { "ScriptGroup", 128, 32 }, - { "OrCondition", 1024, 256 }, - { "ScriptAction", 2600, 512 }, - { "Script", 1024, 256 }, - { "Parameter", 8192, 1024 }, - { "Condition", 2048, 256 }, - { "Template", 32, 32 }, - { "ScriptList", 32, 32 }, - { "AttackPriorityInfo", 32, 32 }, - { "SequentialScript", 32, 32 }, - { "Win32LocalFile", 1024, 256 }, - { "StdLocalFile", 1024, 256 }, - { "RAMFile", 32, 32 }, - { "BattlePlanBonuses", 32, 32 }, - { "KindOfPercentProductionChange", 32, 32 }, - { "UserParser", 4096, 256 }, - { "XferBlockData", 32, 32 }, - { "EvaCheckInfo", 52, 16 }, - { "SuperweaponInfo", 32, 32 }, - { "NamedTimerInfo", 32, 32 }, - { "PopupMessageData", 32, 32 }, - { "FloatingTextData", 32, 32 }, - { "MapObject", 5000, 1024 }, - { "Waypoint", 1024, 32 }, - { "PolygonTrigger", 64, 64 }, - { "Bridge", 32, 32 }, - { "Mapping", 384, 64 }, - { "OutputChunk", 32, 32 }, - { "InputChunk", 32, 32 }, - { "AnimateWindow", 32, 32 }, - { "GameFont", 32, 32 }, - { "NetCommandRef", 256, 32 }, - { "GameMessageArgument", 1024, 256 }, - { "GameMessageParserArgumentType", 32, 32 }, - { "GameMessageParser", 32, 32 }, - { "WeaponBonusSet", 96, 32 }, - { "Campaign", 32, 32 }, - { "Mission", 88, 32 }, - { "ModalWindow", 32, 32 }, - { "NetPacket", 32, 32 }, - { "AISideInfo", 32, 32 }, - { "AISideBuildList", 32, 32 }, - { "MetaMapRec", 256, 32 }, - { "TransportStatus", 32, 32 }, - { "Anim2DTemplate", 32, 32 }, - { "ObjectTypes", 32, 32 }, - { "NetCommandList", 512, 32 }, - { "TurretAIData", 256, 32 }, - { "NetCommandMsg", 32, 32 }, - { "NetGameCommandMsg", 64, 32 }, - { "NetAckBothCommandMsg", 32, 32 }, - { "NetAckStage1CommandMsg", 32, 32 }, - { "NetAckStage2CommandMsg", 32, 32 }, - { "NetFrameCommandMsg", 32, 32 }, - { "NetPlayerLeaveCommandMsg", 32, 32 }, - { "NetRunAheadMetricsCommandMsg", 32, 32 }, - { "NetRunAheadCommandMsg", 32, 32 }, - { "NetDestroyPlayerCommandMsg", 32, 32 }, - { "NetDisconnectFrameCommandMsg", 32, 32 }, - { "NetDisconnectScreenOffCommandMsg", 32, 32 }, - { "NetFrameResendRequestCommandMsg", 32, 32 }, - { "NetKeepAliveCommandMsg", 32, 32 }, - { "NetDisconnectKeepAliveCommandMsg", 32, 32 }, - { "NetDisconnectPlayerCommandMsg", 32, 32 }, - { "NetPacketRouterQueryCommandMsg", 32, 32 }, - { "NetPacketRouterAckCommandMsg", 32, 32 }, - { "NetDisconnectChatCommandMsg", 32, 32 }, - { "NetChatCommandMsg", 32, 32 }, - { "NetDisconnectVoteCommandMsg", 32, 32 }, - { "NetProgressCommandMsg", 32, 32 }, - { "NetWrapperCommandMsg", 32, 32 }, - { "NetFileCommandMsg", 32, 32 }, - { "NetFileAnnounceCommandMsg", 32, 32 }, - { "NetFileProgressCommandMsg", 32, 32 }, - { "NetCommandWrapperListNode", 32, 32 }, - { "NetCommandWrapperList", 32, 32 }, - { "Connection", 32, 32 }, - { "User", 32, 32 }, - { "FrameDataManager", 32, 32 }, - { "DrawableIconInfo", 32, 32 }, - { "TintEnvelope", 128, 32 }, - { "DynamicAudioEventRTS", 4000, 256 }, - { "DrawableLocoInfo", 128, 32 }, - { "W3DPrototypeClass", 512, 256 }, - { "EnumeratedIP", 32, 32 }, - { "WaterTransparencySetting", 4, 4 }, - { "WeatherSetting", 4, 4 }, - - // W3D pools! - { "BoxPrototypeClass", 128, 128 }, - { "SpherePrototypeClass", 32, 32 }, - { "SoundRenderObjPrototypeClass", 32, 32 }, - { "RingPrototypeClass", 32, 32 }, - { "PrimitivePrototypeClass", 8192, 32 }, - { "HModelPrototypeClass", 256, 32 }, - { "ParticleEmitterPrototypeClass", 32, 32 }, - { "NullPrototypeClass", 32, 32 }, - { "HLodPrototypeClass", 700, 128 }, - { "HLodDefClass", 700, 128 }, - { "DistLODPrototypeClass", 32, 32 }, - { "DazzlePrototypeClass", 32, 32 }, - { "CollectionPrototypeClass", 32, 32 }, - { "BoxPrototypeClass", 256, 32 }, - { "AggregatePrototypeClass", 32, 32 }, - { "OBBoxRenderObjClass", 512, 128 }, - { "AABoxRenderObjClass", 32, 32 }, - { "VertexMaterialClass", 6000, 2048 }, - { "TextureClass", 1200, 256 }, - { "CloudMapTerrainTextureClass", 4, 4 }, - { "ScorchTextureClass", 4, 4 }, - { "LightMapTerrainTextureClass", 4, 4 }, - { "AlphaEdgeTextureClass", 4, 4 }, - { "AlphaTerrainTextureClass", 4, 4 }, - { "TerrainTextureClass", 4, 4 }, - { "MeshClass", 14000, 2000 }, - { "HTreeClass", 2048, 512 }, - { "HLodClass", 2048, 512 }, - { "MeshModelClass", 8192, 32 }, - { "ShareBufferClass", 32768, 1024 }, - { "AABTreeClass", 300, 128 }, - { "MotionChannelClass", 16384, 32 }, - { "BitChannelClass", 84, 32 }, - { "TimeCodedMotionChannelClass", 116, 32 }, - { "AdaptiveDeltaMotionChannelClass", 32, 32 }, - { "TimeCodedBitChannelClass", 32, 32 }, - { "UVBufferClass", 8192, 32 }, - { "TexBufferClass", 384, 128 }, - { "MatBufferClass", 256, 128 }, - { "MatrixMapperClass", 32, 32 }, - { "ScaleTextureMapperClass", 32, 32 }, - { "LinearOffsetTextureMapperClass", 96, 32 }, - { "GridTextureMapperClass", 32, 32 }, - { "RotateTextureMapperClass", 32, 32 }, - { "SineLinearOffsetTextureMapperClass", 32, 32 }, - { "StepLinearOffsetTextureMapperClass", 32, 32 }, - { "ZigZagLinearOffsetTextureMapperClass", 32, 32 }, - { "ClassicEnvironmentMapperClass", 32, 32 }, - { "EnvironmentMapperClass", 256, 32 }, - { "EdgeMapperClass", 32, 32 }, - { "WSClassicEnvironmentMapperClass", 32, 32 }, - { "WSEnvironmentMapperClass", 32, 32 }, - { "GridClassicEnvironmentMapperClass", 32, 32 }, - { "GridEnvironmentMapperClass", 32, 32 }, - { "ScreenMapperClass", 32, 32 }, - { "RandomTextureMapperClass", 32, 32 }, - { "BumpEnvTextureMapperClass", 32, 32 }, - { "MeshLoadContextClass", 4, 4 }, - { "MaterialInfoClass", 8192, 32 }, - { "MeshMatDescClass", 8192, 32 }, - { "TextureLoadTaskClass", 256, 32 }, - { "SortingNodeStruct", 288, 32 }, - { "ProxyArrayClass", 32, 32 }, - { "Line3DClass", 8, 8 }, - { "Render2DClass", 64, 32 }, - { "SurfaceClass", 128, 32 }, - { "FontCharsClassCharDataStruct", 1024, 32 }, - { "FontCharsBuffer", 16, 4 }, - { "FVFInfoClass", 152, 64 }, - { "TerrainTracksRenderObjClass", 128, 32 }, - { "DynamicIBAccessClass", 32, 32 }, - { "DX8IndexBufferClass", 128, 32 }, - { "SortingIndexBufferClass", 32, 32 }, - { "DX8VertexBufferClass", 128, 32 }, - { "SortingVertexBufferClass", 32, 32 }, - { "DynD3DMATERIAL8", 8192, 32 }, - { "DynamicMatrix3D", 512, 32 }, - { "MeshGeometryClass", 32, 32 }, - { "DynamicMeshModel", 32, 32 }, - { "GapFillerClass", 32, 32 }, - { "FontCharsClass", 64, 32 }, - { "ThumbnailManagerClass", 32, 32}, - { "SmudgeSet", 32, 32}, - { "Smudge", 128, 32}, - { 0, 0, 0 } -}; - -//----------------------------------------------------------------------------- -void userMemoryAdjustPoolSize(const char *poolName, Int& initialAllocationCount, Int& overflowAllocationCount) -{ - if (initialAllocationCount > 0) - return; - - for (const PoolSizeRec* p = sizes; p->name != NULL; ++p) - { - if (strcmp(p->name, poolName) == 0) - { - initialAllocationCount = p->initial; - overflowAllocationCount = p->overflow; - return; - } - } - - DEBUG_CRASH(("Initial size for pool %s not found -- you should add it to MemoryInit.cpp\n",poolName)); -} - -//----------------------------------------------------------------------------- -static Int roundUpMemBound(Int i) -{ - const int MEM_BOUND_ALIGNMENT = 4; - - if (i < MEM_BOUND_ALIGNMENT) - return MEM_BOUND_ALIGNMENT; - else - return (i + (MEM_BOUND_ALIGNMENT-1)) & ~(MEM_BOUND_ALIGNMENT-1); -} - -//----------------------------------------------------------------------------- -void userMemoryManagerInitPools() -{ - // note that we MUST use stdio stuff here, and not the normal game file system - // (with bigfile support, etc), because that relies on memory pools, which - // aren't yet initialized properly! so rely ONLY on straight stdio stuff here. - // (not even AsciiString. thanks.) - - // since we're called prior to main, the cur dir might not be what - // we expect. so do it the hard way. - char buf[_MAX_PATH]; - ::GetModuleFileName(NULL, buf, sizeof(buf)); - char* pEnd = buf + strlen(buf); - while (pEnd != buf) - { - if (*pEnd == '\\') - { - *pEnd = 0; - break; - } - --pEnd; - } - strcat(buf, "\\Data\\INI\\MemoryPools.ini"); - - FILE* fp = fopen(buf, "r"); - if (fp) - { - char poolName[256]; - int initial, overflow; - while (fgets(buf, _MAX_PATH, fp)) - { - if (buf[0] == ';') - continue; - if (sscanf(buf, "%s %d %d", poolName, &initial, &overflow ) == 3) - { - for (PoolSizeRec* p = sizes; p->name != NULL; ++p) - { - if (stricmp(p->name, poolName) == 0) - { - // currently, these must be multiples of 4. so round up. - p->initial = roundUpMemBound(initial); - p->overflow = roundUpMemBound(overflow); - break; // from for-p - } - } - } - } - fclose(fp); - } -} - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: MemoryInit.cpp +//----------------------------------------------------------------------------- +// +// Westwood Studios Pacific. +// +// Confidential Information +// Copyright (C) 2001 - All Rights Reserved +// +//----------------------------------------------------------------------------- +// +// Project: RTS3 +// +// File name: MemoryInit.cpp +// +// Created: Steven Johnson, August 2001 +// +// Desc: Memory manager +// +// ---------------------------------------------------------------------------- +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +// SYSTEM INCLUDES + +// USER INCLUDES +#include "Lib/BaseType.h" +#include "Common/GameMemory.h" + +#ifdef RTS_INTERNAL +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +//----------------------------------------------------------------------------- +void userMemoryManagerGetDmaParms(Int *numSubPools, const PoolInitRec **pParms) +{ + static const PoolInitRec defaultDMA[7] = + { + // name, allocsize, initialcount, overflowcount + { "dmaPool_16", 16, 130000, 10000 }, + { "dmaPool_32", 32, 250000, 10000 }, + { "dmaPool_64", 64, 100000, 10000 }, + { "dmaPool_128", 128, 80000, 10000 }, + { "dmaPool_256", 256, 20000, 5000 }, + { "dmaPool_512", 512, 16000, 5000 }, + { "dmaPool_1024", 1024, 6000, 1024} + }; + + *numSubPools = 7; + *pParms = defaultDMA; +} + +//----------------------------------------------------------------------------- +struct PoolSizeRec +{ + const char* name; + Int initial; + Int overflow; +}; + +//----------------------------------------------------------------------------- +// And please be careful of duplicates. They are not rejected. +// not const -- we might override from INI +static PoolSizeRec sizes[] = +{ + { "PartitionContactListNode", 2048, 512 }, + { "BattleshipUpdate", 32, 32 }, + { "FlyToDestAndDestroyUpdate", 32, 32 }, + { "MusicTrack", 32, 32 }, + { "PositionalSoundPool", 32, 32 }, + { "GameMessage", 2048, 32 }, + { "NameKeyBucketPool", 9000, 1024 }, + { "ObjectSellInfo", 16, 16 }, + { "ProductionPrerequisitePool", 1024, 32 }, + { "RadarObject", 512, 32 }, + { "ResourceGatheringManager", 16, 16 }, + { "SightingInfo", 8192, 2048 },// Looks big, but all 3000 objects used to have 4 just built in. + { "SpecialPowerTemplate", 84, 32 }, + { "StateMachinePool", 32, 32 }, + { "TeamPool", 128, 32 }, // if you increase this, increase player/team relation map pools + { "PlayerRelationMapPool", 128, 32 }, + { "TeamRelationMapPool", 128, 32 }, + { "TeamPrototypePool", 256, 32 }, + { "TerrainType", 256, 32 }, + { "ThingTemplatePool", 2120, 32 }, + { "TunnelTracker", 16, 16 }, + { "Upgrade", 16, 16 }, + { "UpgradeTemplate", 128, 16 }, + { "Anim2D", 32, 32 }, + { "CommandButton", 1024, 256 }, + { "CommandSet", 820, 16 }, + { "DisplayString", 32, 32 }, + { "WebBrowserURL", 16, 16 }, + { "Drawable", 4096, 32 }, + { "Image", 2048, 32 }, + { "ParticlePool", 1400, 1024 }, + { "ParticleSystemTemplatePool", 1100, 32 }, + { "ParticleSystemPool", 1024, 32 }, + { "TerrainRoadType", 100, 32, }, + { "WindowLayoutPool", 32, 32 }, + { "AnimatedParticleSysBoneClientUpdate", 16, 16 }, + { "SwayClientUpdate", 32, 32 }, + { "BeaconClientUpdate", 64, 32 }, + { "AIGroupPool", 64, 32 }, + { "AIDockMachinePool", 256, 32 }, + { "AIGuardMachinePool", 32, 32 }, + { "AIGuardRetaliateMachinePool", 32, 32 }, + { "AITNGuardMachinePool", 32, 32 }, + { "PathNodePool", 8192, 1024 }, + { "PathPool", 256, 16 }, + { "WorkOrder", 32, 32 }, + { "TeamInQueue", 32, 32 }, + { "AIPlayer", 12, 4 }, + { "AISkirmishPlayer", 8, 8 }, + { "AIStateMachine", 600, 32 }, + { "JetAIStateMachine", 64, 32 }, + { "HeliAIStateMachine", 64, 32 }, + { "VtolAIStateMachine", 64, 32 }, + { "AIAttackMoveStateMachine", 2048, 32 }, + { "AIAttackThenIdleStateMachine", 512, 32 }, + { "AttackStateMachine", 512, 32 }, + { "CrateTemplate", 32, 32 }, + { "ExperienceTrackerPool", 2048, 512 }, + { "FiringTrackerPool", 4096, 256 }, + { "ObjectRepulsorHelper", 1024, 256 }, + { "ObjectSMCHelperPool", 2048, 256 }, + { "ObjectWeaponStatusHelperPool", 4096, 256 }, + { "ObjectDefectionHelperPool", 2048, 256 }, + { "StatusDamageHelper", 1500, 256 }, + { "SubdualDamageHelper", 1500, 256 }, + { "TempWeaponBonusHelper", 4096, 256 }, + { "Locomotor", 2048, 32 }, + { "LocomotorTemplate", 192, 32 }, + { "ObjectPool", 1500, 256 }, + { "SimpleObjectIteratorPool", 32, 32 }, + { "SimpleObjectIteratorClumpPool", 4096, 32 }, + { "PartitionDataPool", 2048, 512 }, + { "BuildEntry", 32, 32 }, + { "Weapon", 4096, 32 }, + { "WeaponTemplate", 360, 32 }, + { "AIUpdateInterface", 600, 32 }, + { "ActiveBody", 1024, 32 }, + { "ActiveShroudUpgrade", 32, 32 }, + { "AssistedTargetingUpdate", 32, 32 }, + { "AudioEventInfo", 4096, 64 }, + { "AudioRequest", 256, 8 }, + { "AutoHealBehavior", 1024, 256 }, + { "WeaponBonusUpdate", 16, 16 }, + { "GrantStealthBehavior", 4096, 32 }, + { "NeutronBlastBehavior", 4096, 32 }, + { "CountermeasuresBehavior", 256, 32 }, + { "BaseRegenerateUpdate", 128, 32 }, + { "BoneFXDamage", 64, 32 }, + { "BoneFXUpdate", 64, 32 }, + { "BridgeBehavior", 4, 4 }, + { "BridgeTowerBehavior", 32, 32 }, + { "BridgeScaffoldBehavior", 32, 32 }, + { "CaveContain", 16, 16 }, + { "HealContain", 32, 32 }, + { "CreateCrateDie", 256, 128 }, + { "CreateObjectDie", 1024, 32 }, + { "EjectPilotDie", 1024, 32 }, + { "CrushDie", 1024, 32 }, + { "DamDie", 8, 8 }, + { "DeliverPayloadStateMachine", 32, 32 }, + { "DeliverPayloadAIUpdate", 32, 32 }, + { "DeletionUpdate", 128, 32 }, + { "SmartBombTargetHomingUpdate", 8, 8 }, + { "DynamicAudioEventInfo", 16, 256 }, // Note: some levels have none, some have lots. Since all are allocated at level load time, we can set this low for the levels with none. + { "HackInternetStateMachine", 32, 32 }, + { "HackInternetAIUpdate", 32, 32 }, + { "MissileAIUpdate", 512, 32 }, + { "DumbProjectileBehavior", 64, 32 }, + { "FreeFallProjectileBehavior", 32, 32 }, + { "TeleportMovementBehavior", 32, 32 }, + { "DestroyDie", 1024, 32 }, + { "UpgradeDie", 128, 32 }, + { "KeepObjectDie", 128, 32 }, + { "DozerAIUpdate", 32, 32 }, + { "DynamicGeometryInfoUpdate", 16, 16 }, + { "DynamicShroudClearingRangeUpdate", 128, 16 }, + { "FXListDie", 1024, 32 }, + { "FireSpreadUpdate", 2048, 128 }, + { "FirestormDynamicGeometryInfoUpdate", 16, 16 }, + { "FireWeaponCollide", 2048, 32 }, + { "FireWeaponUpdate", 32, 32 }, + { "FlammableUpdate", 512, 256 }, + { "FloatUpdate", 512, 128 }, + { "TensileFormationUpdate", 256, 32 }, + { "GarrisonContain", 256, 32 }, + { "HealCrateCollide", 32, 32 }, + { "HeightDieUpdate", 32, 32 }, + { "ScatterShotUpdate", 128, 64 }, + { "FireWeaponWhenDamagedBehavior", 32, 32 }, + { "FireWeaponWhenDeadBehavior", 128, 64 }, + { "GenerateMinefieldBehavior", 32, 32 }, + { "HelicopterSlowDeathBehavior", 64, 32 }, + { "ParkingPlaceBehavior", 32, 32 }, + { "FlightDeckBehavior", 8, 8 }, +#ifdef ALLOW_SURRENDER + { "POWTruckAIUpdate", 32, 32, }, + { "POWTruckBehavior", 32, 32, }, + { "PrisonBehavior", 32, 32 }, + { "PrisonVisual", 32, 32 }, + { "PropagandaCenterBehavior", 16, 16 }, +#endif + { "PropagandaTowerBehavior", 16, 16 }, + { "BunkerBusterBehavior", 16, 16 }, + { "ObjectTracker", 128, 32 }, + { "OCLUpdate", 16, 16 }, + { "BodyParticleSystem", 196, 64 }, + { "HighlanderBody", 2048, 128 }, + { "UndeadBody", 32, 32 }, + { "HordeUpdate", 128, 32 }, + { "ImmortalBody", 128, 256 }, + { "InactiveBody", 2048, 32 }, + { "InstantDeathBehavior", 512, 32 }, + { "LaserUpdate", 32, 32 }, + { "PointDefenseLaserUpdate", 32, 32 }, + { "CleanupHazardUpdate", 32, 32 }, + { "AutoFindHealingUpdate", 256, 32 }, + { "CommandButtonHuntUpdate", 512, 8 }, + { "PilotFindVehicleUpdate", 256, 32 }, + { "DemoTrapUpdate", 32, 32 }, + { "ParticleUplinkCannonUpdate", 16, 16 }, + { "SpectreGunshipUpdate", 8, 8 }, + { "SpectreGunshipDeploymentUpdate", 8, 8 }, + { "BaikonurLaunchPower", 4, 4 }, + { "RadiusDecalUpdate", 16, 16 }, + { "RadiusDecalBehavior", 32, 32 }, + { "BattlePlanUpdate", 32, 32 }, + { "LifetimeUpdate", 32, 32 }, + { "LocomotorSetUpgrade", 512, 128 }, + { "LockWeaponCreate", 64, 128 }, + { "AutoDepositUpdate", 256, 32 }, + { "NeutronMissileUpdate", 512, 32 }, + { "MoneyCrateCollide", 48, 16 }, + { "NeutronMissileSlowDeathBehavior", 8, 8 }, + { "OpenContain", 128, 32 }, + { "OverchargeBehavior", 32, 32 }, + { "OverlordContain", 32, 32 }, + { "HelixContain", 32, 32 }, + { "ParachuteContain", 128, 32 }, + { "PhysicsBehavior", 600, 32 }, + { "PoisonedBehavior", 512, 64 }, + { "ProductionEntry", 32, 32 }, + { "ProductionUpdate", 256, 32 }, + { "ProjectileStreamUpdate", 32, 32 }, + { "ProneUpdate", 128, 32 }, + { "QueueProductionExitUpdate", 32, 32 }, + { "RadarUpdate", 16, 16 }, + { "RadarUpgrade", 16, 16 }, + { "AnimationSteeringUpdate", 1024, 32 }, + { "SupplyWarehouseCripplingBehavior", 16, 16 }, + { "CostModifierUpgrade", 32, 32 }, + { "ProductionTimeModifierUpgrade", 32, 32 }, + { "UnitProductionBonusUpgrade", 64, 32 }, + { "CashBountyPower", 32, 32 }, + { "CleanupAreaPower", 32, 32 }, + { "ObjectCreationUpgrade", 196, 32 }, + { "MinefieldBehavior", 256, 32 }, + { "JetSlowDeathBehavior", 64, 32 }, + { "BattleBusSlowDeathBehavior", 64, 32 }, + { "RebuildHoleBehavior", 64, 32 }, + { "RebuildHoleExposeDie", 64, 32 }, + { "RepairDockUpdate", 32, 32 }, +#ifdef ALLOW_SURRENDER + { "PrisonDockUpdate", 32, 32 }, +#endif + { "RailedTransportDockUpdate", 16, 16 }, + { "RailedTransportAIUpdate", 16, 16 }, + { "RailedTransportContain", 16, 16 }, + { "RailroadBehavior", 16, 16 }, + { "SalvageCrateCollide", 32, 32 }, + { "ShroudCrateCollide", 32, 32 }, + { "SlavedUpdate", 64, 32 }, + { "SlowDeathBehavior", 1400, 256 }, + { "SpyVisionUpdate", 16, 16 }, + { "DefaultProductionExitUpdate", 32, 32 }, + { "SpawnPointProductionExitUpdate", 32, 32 }, + { "SpawnBehavior", 32, 32 }, + { "SpecialPowerCompletionDie", 32, 32 }, + { "SpecialPowerCreate", 32, 32 }, + { "PreorderCreate", 32, 32 }, + { "SpecialAbility", 512, 32 }, + { "SpecialAbilityUpdate", 512, 32 }, + { "MissileLauncherBuildingUpdate", 32, 32 }, + { "SquishCollide", 512, 32 }, + { "StructureBody", 512, 64 }, + { "HiveStructureBody", 64, 32 }, //Stinger sites + { "StructureCollapseUpdate", 32, 32 }, + { "StructureToppleUpdate", 32, 32 }, + { "SupplyCenterCreate", 32, 32 }, + { "SupplyCenterDockUpdate", 32, 32 }, + { "SupplyCenterProductionExitUpdate", 32, 32 }, + { "SupplyTruckStateMachine", 256, 32 }, + { "SupplyTruckAIUpdate", 32, 32 }, + { "SupplyWarehouseCreate", 48, 16 }, + { "SupplyWarehouseDockUpdate", 48, 16 }, + { "EnemyNearUpdate", 1024, 32 }, + { "TechBuildingBehavior", 32, 32 }, + { "ToppleUpdate", 256, 128 }, + { "TransitionDamageFX", 384, 128 }, + { "TransportAIUpdate", 64, 32 }, + { "TransportContain", 128, 32 }, + { "RiderChangeContain", 128, 32 }, + { "InternetHackContain", 16, 16 }, + { "TunnelContain", 8, 8 }, + { "TunnelContainDie", 32, 32 }, + { "TunnelCreate", 32, 32 }, + { "TurretAI", 256, 32 }, + { "TurretStateMachine", 128, 32 }, + { "TurretSwapUpgrade", 512, 128 }, + { "UnitCrateCollide", 32, 32 }, + { "UnpauseSpecialPowerUpgrade", 32, 32 }, + { "VeterancyCrateCollide", 32, 32 }, + { "VeterancyGainCreate", 512, 128 }, + { "ConvertToCarBombCrateCollide", 256, 128 }, + { "ConvertToHijackedVehicleCrateCollide", 256, 128 }, + { "SabotageCommandCenterCrateCollide", 256, 128 }, + { "SabotageFakeBuildingCrateCollide", 256, 128 }, + { "SabotageInternetCenterCrateCollide", 256, 128 }, + { "SabotageMilitaryFactoryCrateCollide", 256, 128 }, + { "SabotagePowerPlantCrateCollide", 256, 128 }, + { "SabotageSuperweaponCrateCollide", 256, 128 }, + { "SabotageSupplyCenterCrateCollide", 256, 128 }, + { "SabotageSupplyDropzoneCrateCollide", 256, 128 }, + { "JetAIUpdate", 64, 32 }, + { "ChinookAIUpdate", 32, 32 }, + { "WanderAIUpdate", 32, 32 }, + { "TeleporterAIUpdate", 64, 32 }, + { "WaveGuideUpdate", 16, 16 }, + { "ArmorDamageScalarUpdate", 256, 32 }, + { "WeaponBonusUpgrade", 512, 128 }, + { "WeaponSetUpgrade", 512, 128 }, + { "ArmorUpgrade", 512, 128 }, + { "WorkerAIUpdate", 128, 128 }, + { "WorkerStateMachine", 128, 128 }, + { "ChinookAIStateMachine", 32, 32 }, + { "DeployStyleAIUpdate", 32, 32 }, + { "AssaultTransportAIUpdate", 64, 32 }, + { "StreamingArchiveFile", 8, 8 }, + + { "DozerActionStateMachine", 256, 32 }, + { "DozerPrimaryStateMachine", 256, 32 }, + { "W3DDisplayString", 1400, 128 }, + { "W3DDefaultDraw", 1024, 128 }, + { "W3DDebrisDraw", 128, 128 }, + { "W3DDependencyModelDraw", 64, 64 }, + { "W3DLaserDraw", 32, 32 }, + { "W3DModelDraw", 2048, 512 }, + { "W3DOverlordTankDraw", 64, 64 }, + { "W3DOverlordTruckDraw", 64, 64 }, + { "W3DOverlordAircraftDraw", 64, 64 }, + { "W3DPoliceCarDraw", 32, 32 }, + { "W3DProjectileStreamDraw", 32, 32 }, + { "W3DRopeDraw", 32, 32 }, + { "W3DScienceModelDraw", 32, 32 }, + { "W3DSupplyDraw", 40, 16 }, + { "W3DTankDraw", 256, 32 }, + { "W3DTreeDraw", 16, 16 }, + { "W3DPropDraw", 16, 16 }, + { "W3DTracerDraw", 64, 32 }, + { "W3DTruckDraw", 128, 32 }, + { "W3DTankTruckDraw", 32, 16 }, + { "W3DTreeTextureClass", 4, 4 }, + { "DefaultSpecialPower", 32, 32 }, + { "OCLSpecialPower", 96, 32 }, + { "FireWeaponPower", 32, 32 }, +#ifdef ALLOW_DEMORALIZE + { "DemoralizeSpecialPower", 16, 16, }, +#endif + { "CashHackSpecialPower", 32, 32 }, + { "CommandSetUpgrade", 32, 32 }, + { "PassengersFireUpgrade", 32, 32 }, + { "GrantUpgradeCreate", 256, 32 }, + { "GrantScienceUpgrade", 256, 32 }, + { "ReplaceObjectUpgrade", 32, 32 }, + { "ModelConditionUpgrade", 32, 32 }, + { "SpyVisionSpecialPower", 256, 32 }, + { "StealthDetectorUpdate", 256, 32 }, + { "StealthUpdate", 512, 128 }, + { "StealthUpgrade", 256, 32 }, + { "StatusBitsUpgrade", 128, 128 }, + { "SubObjectsUpgrade", 128, 128 }, + { "ExperienceScalarUpgrade", 256, 128 }, + { "MaxHealthUpgrade", 128, 128 }, + { "WeaponBonusUpgrade", 128, 64 }, + { "StickyBombUpdate", 64, 32 }, + { "FireOCLAfterWeaponCooldownUpdate", 64, 32 }, + { "HijackerUpdate", 64, 32 }, + { "ChinaMinesUpgrade", 64, 32 }, + { "PowerPlantUpdate", 48, 16 }, + { "PowerPlantUpgrade", 48, 16 }, + { "DefectorSpecialPower", 16, 16 }, + { "CheckpointUpdate", 16, 16 }, + { "MobNexusContain", 128, 32 }, + { "MobMemberSlavedUpdate", 64, 32 }, + { "EMPUpdate", 64, 32 }, + { "LeafletDropBehavior", 64, 32 }, + { "Overridable", 32, 32 }, + + { "W3DGameWindow", 700, 256 }, + { "SuccessState", 32, 32 }, + { "FailureState", 32, 32 }, + { "ContinueState", 32, 32 }, + { "SleepState", 32, 32 }, + + { "AIDockWaitForClearanceState", 256, 32 }, + { "AIDockProcessDockState", 256, 32 }, + { "AIGuardInnerState", 32, 32 }, + { "AIGuardIdleState", 32, 32 }, + { "AIGuardOuterState", 32, 32 }, + { "AIGuardReturnState", 32, 32 }, + { "AIGuardPickUpCrateState", 32, 32 }, + { "AIGuardAttackAggressorState", 32, 32 }, + { "AIGuardRetaliateInnerState", 32, 32 }, + { "AIGuardRetaliateIdleState", 32, 32 }, + { "AIGuardRetaliateOuterState", 32, 32 }, + { "AIGuardRetaliateReturnState", 32, 32 }, + { "AIGuardRetaliatePickUpCrateState", 32, 32 }, + { "AIGuardRetaliateAttackAggressorState", 32, 32 }, + { "AITNGuardInnerState", 32, 32 }, + { "AITNGuardIdleState", 32, 32 }, + { "AITNGuardOuterState", 32, 32 }, + { "AITNGuardReturnState", 32, 32 }, + { "AITNGuardPickUpCrateState", 32, 32 }, + { "AITNGuardAttackAggressorState", 32, 32 }, + { "AIIdleState", 2400, 32 }, + { "AIRappelState", 600, 32 }, + { "AIBusyState", 600, 32 }, + { "AIWaitState", 600, 32 }, + { "AIAttackState", 4096, 32 }, + { "AIAttackSquadState", 600, 32 }, + { "AIDeadState", 600, 32 }, + { "AIDockState", 600, 32 }, + { "AIExitState", 600, 32 }, + { "AIExitInstantlyState", 600, 32 }, + { "AIGuardState", 600, 32 }, + { "AIGuardRetaliateState", 600, 32 }, + { "AITunnelNetworkGuardState", 600, 32 }, + { "AIHuntState", 600, 32 }, + { "AIAttackAreaState", 600, 32 }, + { "AIFaceState", 1200, 32 }, + { "ApproachState", 600, 32 }, + { "DeliveringState", 600, 32 }, + { "ConsiderNewApproachState", 600, 32 }, + { "RecoverFromOffMapState", 600, 32 }, + { "HeadOffMapState", 600, 32 }, + { "CleanUpState", 600, 32 }, + { "HackInternetState", 600, 32 }, + { "PackingState", 600, 32 }, + { "UnpackingState", 600, 32 }, + { "SupplyTruckWantsToPickUpOrDeliverBoxesState", 600, 32 }, + { "RegroupingState", 600, 32 }, + { "DockingState", 600, 32 }, + { "ChinookEvacuateState", 32, 32 }, + { "ChinookHeadOffMapState", 32, 32 }, + { "ChinookTakeoffOrLandingState", 32, 32 }, + { "ChinookCombatDropState", 32, 32 }, + { "DozerActionPickActionPosState", 256, 32 }, + { "DozerActionMoveToActionPosState", 256, 32 }, + { "DozerActionDoActionState", 256, 32 }, + { "DozerPrimaryIdleState", 256, 32 }, + { "DozerActionState", 256, 32 }, + { "DozerPrimaryGoingHomeState", 256, 32 }, + { "JetAwaitingRunwayState", 64, 32 }, + { "JetOrHeliCirclingDeadAirfieldState", 64, 32 }, + { "HeliTakeoffOrLandingState", 64, 32 }, + { "VtolTakeoffOrLandingState", 64, 32 }, + { "JetOrHeliParkOrientState", 64, 32 }, + { "VtolParkOrientState", 64, 32 }, + { "JetOrHeliReloadAmmoState", 64, 32 }, + { "SupplyTruckBusyState", 600, 32 }, + { "SupplyTruckIdleState", 600, 32 }, + { "ActAsDozerState", 600, 32 }, + { "ActAsSupplyTruckState", 600, 32 }, + { "AIDockApproachState", 256, 32 }, + { "AIDockAdvancePositionState", 256, 32 }, + { "AIDockMoveToEntryState", 256, 32 }, + { "AIDockMoveToDockState", 256, 32 }, + { "AIDockMoveToExitState", 256, 32 }, + { "AIDockMoveToRallyState", 256, 32 }, + { "AIMoveToState", 600, 32 }, + { "AIMoveOutOfTheWayState", 600, 32 }, + { "AIMoveAndTightenState", 600, 32 }, + { "AIMoveAwayFromRepulsorsState", 600, 32 }, + { "AIAttackApproachTargetState", 96, 32 }, + { "AIAttackPursueTargetState", 96, 32 }, + { "AIAttackAimAtTargetState", 96, 32 }, + { "AIAttackFireWeaponState", 256, 32 }, + { "AIPickUpCrateState", 4096, 32 }, + { "AIFollowWaypointPathState", 1200, 32 }, + { "AIFollowWaypointPathExactState", 1200, 32 }, + { "AIWanderInPlaceState", 600, 32 }, + { "AIFollowPathState", 1200, 32 }, + { "AIMoveAndEvacuateState", 1200, 32 }, + { "AIMoveAndDeleteState", 600, 32 }, + { "AIEnterState", 600, 32 }, + { "JetOrHeliReturningToDeadAirfieldState", 64, 32 }, + { "JetOrHeliReturnForLandingState", 64, 32 }, + { "TurretAIIdleState", 600, 32 }, + { "TurretAIIdleScanState", 600, 32 }, + { "TurretAIAimTurretState", 600, 32 }, + { "TurretAIRecenterTurretState", 600, 32 }, + { "TurretAIHoldTurretState", 600, 32 }, + { "JetOrHeliTaxiState", 64, 32 }, + { "JetTakeoffOrLandingState", 64, 32 }, + { "JetPauseBeforeTakeoffState", 64, 32 }, + { "AIAttackMoveToState", 600, 32 }, + { "AIAttackFollowWaypointPathState", 1200, 32 }, + { "AIWanderState", 600, 32 }, + { "AIPanicState", 600, 32 }, + { "ChinookMoveToBldgState", 32, 32 }, + { "ChinookRecordCreationState", 32, 32 }, + { "ScienceInfo", 96, 32 }, + { "RankInfo", 32, 32 }, + + { "FireWeaponNugget", 32, 32 }, + { "AttackNugget", 32, 32 }, + { "DeliverPayloadNugget", 48, 32 }, + { "ApplyRandomForceNugget", 32, 32 }, + { "GenericObjectCreationNugget", 632, 32 }, + { "SoundFXNugget", 320, 32 }, + { "TracerFXNugget", 32, 32 }, + { "RayEffectFXNugget", 32, 32 }, + { "LightPulseFXNugget", 68, 32 }, + { "ViewShakeFXNugget", 140, 32 }, + { "TerrainScorchFXNugget", 48, 32 }, + { "ParticleSystemFXNugget", 832, 32 }, + { "FXListAtBonePosFXNugget", 32, 32 }, + { "Squad", 256, 32 }, + { "BuildListInfo", 400, 64 }, + + { "ScriptGroup", 128, 32 }, + { "OrCondition", 1024, 256 }, + { "ScriptAction", 2600, 512 }, + { "Script", 1024, 256 }, + { "Parameter", 8192, 1024 }, + { "Condition", 2048, 256 }, + { "Template", 32, 32 }, + { "ScriptList", 32, 32 }, + { "AttackPriorityInfo", 32, 32 }, + { "SequentialScript", 32, 32 }, + { "Win32LocalFile", 1024, 256 }, + { "StdLocalFile", 1024, 256 }, + { "RAMFile", 32, 32 }, + { "BattlePlanBonuses", 32, 32 }, + { "KindOfPercentProductionChange", 32, 32 }, + { "UserParser", 4096, 256 }, + { "XferBlockData", 32, 32 }, + { "EvaCheckInfo", 52, 16 }, + { "SuperweaponInfo", 32, 32 }, + { "NamedTimerInfo", 32, 32 }, + { "PopupMessageData", 32, 32 }, + { "FloatingTextData", 32, 32 }, + { "MapObject", 5000, 1024 }, + { "Waypoint", 1024, 32 }, + { "PolygonTrigger", 64, 64 }, + { "Bridge", 32, 32 }, + { "Mapping", 384, 64 }, + { "OutputChunk", 32, 32 }, + { "InputChunk", 32, 32 }, + { "AnimateWindow", 32, 32 }, + { "GameFont", 32, 32 }, + { "NetCommandRef", 256, 32 }, + { "GameMessageArgument", 1024, 256 }, + { "GameMessageParserArgumentType", 32, 32 }, + { "GameMessageParser", 32, 32 }, + { "WeaponBonusSet", 96, 32 }, + { "Campaign", 32, 32 }, + { "Mission", 88, 32 }, + { "ModalWindow", 32, 32 }, + { "NetPacket", 32, 32 }, + { "AISideInfo", 32, 32 }, + { "AISideBuildList", 32, 32 }, + { "MetaMapRec", 256, 32 }, + { "TransportStatus", 32, 32 }, + { "Anim2DTemplate", 32, 32 }, + { "ObjectTypes", 32, 32 }, + { "NetCommandList", 512, 32 }, + { "TurretAIData", 256, 32 }, + { "NetCommandMsg", 32, 32 }, + { "NetGameCommandMsg", 64, 32 }, + { "NetAckBothCommandMsg", 32, 32 }, + { "NetAckStage1CommandMsg", 32, 32 }, + { "NetAckStage2CommandMsg", 32, 32 }, + { "NetFrameCommandMsg", 32, 32 }, + { "NetPlayerLeaveCommandMsg", 32, 32 }, + { "NetRunAheadMetricsCommandMsg", 32, 32 }, + { "NetRunAheadCommandMsg", 32, 32 }, + { "NetDestroyPlayerCommandMsg", 32, 32 }, + { "NetDisconnectFrameCommandMsg", 32, 32 }, + { "NetDisconnectScreenOffCommandMsg", 32, 32 }, + { "NetFrameResendRequestCommandMsg", 32, 32 }, + { "NetKeepAliveCommandMsg", 32, 32 }, + { "NetDisconnectKeepAliveCommandMsg", 32, 32 }, + { "NetDisconnectPlayerCommandMsg", 32, 32 }, + { "NetPacketRouterQueryCommandMsg", 32, 32 }, + { "NetPacketRouterAckCommandMsg", 32, 32 }, + { "NetDisconnectChatCommandMsg", 32, 32 }, + { "NetChatCommandMsg", 32, 32 }, + { "NetDisconnectVoteCommandMsg", 32, 32 }, + { "NetProgressCommandMsg", 32, 32 }, + { "NetWrapperCommandMsg", 32, 32 }, + { "NetFileCommandMsg", 32, 32 }, + { "NetFileAnnounceCommandMsg", 32, 32 }, + { "NetFileProgressCommandMsg", 32, 32 }, + { "NetCommandWrapperListNode", 32, 32 }, + { "NetCommandWrapperList", 32, 32 }, + { "Connection", 32, 32 }, + { "User", 32, 32 }, + { "FrameDataManager", 32, 32 }, + { "DrawableIconInfo", 32, 32 }, + { "TintEnvelope", 128, 32 }, + { "DynamicAudioEventRTS", 4000, 256 }, + { "DrawableLocoInfo", 128, 32 }, + { "W3DPrototypeClass", 512, 256 }, + { "EnumeratedIP", 32, 32 }, + { "WaterTransparencySetting", 4, 4 }, + { "WeatherSetting", 4, 4 }, + + // W3D pools! + { "BoxPrototypeClass", 128, 128 }, + { "SpherePrototypeClass", 32, 32 }, + { "SoundRenderObjPrototypeClass", 32, 32 }, + { "RingPrototypeClass", 32, 32 }, + { "PrimitivePrototypeClass", 8192, 32 }, + { "HModelPrototypeClass", 256, 32 }, + { "ParticleEmitterPrototypeClass", 32, 32 }, + { "NullPrototypeClass", 32, 32 }, + { "HLodPrototypeClass", 700, 128 }, + { "HLodDefClass", 700, 128 }, + { "DistLODPrototypeClass", 32, 32 }, + { "DazzlePrototypeClass", 32, 32 }, + { "CollectionPrototypeClass", 32, 32 }, + { "BoxPrototypeClass", 256, 32 }, + { "AggregatePrototypeClass", 32, 32 }, + { "OBBoxRenderObjClass", 512, 128 }, + { "AABoxRenderObjClass", 32, 32 }, + { "VertexMaterialClass", 6000, 2048 }, + { "TextureClass", 1200, 256 }, + { "CloudMapTerrainTextureClass", 4, 4 }, + { "ScorchTextureClass", 4, 4 }, + { "LightMapTerrainTextureClass", 4, 4 }, + { "AlphaEdgeTextureClass", 4, 4 }, + { "AlphaTerrainTextureClass", 4, 4 }, + { "TerrainTextureClass", 4, 4 }, + { "MeshClass", 14000, 2000 }, + { "HTreeClass", 2048, 512 }, + { "HLodClass", 2048, 512 }, + { "MeshModelClass", 8192, 32 }, + { "ShareBufferClass", 32768, 1024 }, + { "AABTreeClass", 300, 128 }, + { "MotionChannelClass", 16384, 32 }, + { "BitChannelClass", 84, 32 }, + { "TimeCodedMotionChannelClass", 116, 32 }, + { "AdaptiveDeltaMotionChannelClass", 32, 32 }, + { "TimeCodedBitChannelClass", 32, 32 }, + { "UVBufferClass", 8192, 32 }, + { "TexBufferClass", 384, 128 }, + { "MatBufferClass", 256, 128 }, + { "MatrixMapperClass", 32, 32 }, + { "ScaleTextureMapperClass", 32, 32 }, + { "LinearOffsetTextureMapperClass", 96, 32 }, + { "GridTextureMapperClass", 32, 32 }, + { "RotateTextureMapperClass", 32, 32 }, + { "SineLinearOffsetTextureMapperClass", 32, 32 }, + { "StepLinearOffsetTextureMapperClass", 32, 32 }, + { "ZigZagLinearOffsetTextureMapperClass", 32, 32 }, + { "ClassicEnvironmentMapperClass", 32, 32 }, + { "EnvironmentMapperClass", 256, 32 }, + { "EdgeMapperClass", 32, 32 }, + { "WSClassicEnvironmentMapperClass", 32, 32 }, + { "WSEnvironmentMapperClass", 32, 32 }, + { "GridClassicEnvironmentMapperClass", 32, 32 }, + { "GridEnvironmentMapperClass", 32, 32 }, + { "ScreenMapperClass", 32, 32 }, + { "RandomTextureMapperClass", 32, 32 }, + { "BumpEnvTextureMapperClass", 32, 32 }, + { "MeshLoadContextClass", 4, 4 }, + { "MaterialInfoClass", 8192, 32 }, + { "MeshMatDescClass", 8192, 32 }, + { "TextureLoadTaskClass", 256, 32 }, + { "SortingNodeStruct", 288, 32 }, + { "ProxyArrayClass", 32, 32 }, + { "Line3DClass", 8, 8 }, + { "Render2DClass", 64, 32 }, + { "SurfaceClass", 128, 32 }, + { "FontCharsClassCharDataStruct", 1024, 32 }, + { "FontCharsBuffer", 16, 4 }, + { "FVFInfoClass", 152, 64 }, + { "TerrainTracksRenderObjClass", 128, 32 }, + { "DynamicIBAccessClass", 32, 32 }, + { "DX8IndexBufferClass", 128, 32 }, + { "SortingIndexBufferClass", 32, 32 }, + { "DX8VertexBufferClass", 128, 32 }, + { "SortingVertexBufferClass", 32, 32 }, + { "DynD3DMATERIAL8", 8192, 32 }, + { "DynamicMatrix3D", 512, 32 }, + { "MeshGeometryClass", 32, 32 }, + { "DynamicMeshModel", 32, 32 }, + { "GapFillerClass", 32, 32 }, + { "FontCharsClass", 64, 32 }, + { "ThumbnailManagerClass", 32, 32}, + { "SmudgeSet", 32, 32}, + { "Smudge", 128, 32}, + { 0, 0, 0 } +}; + +//----------------------------------------------------------------------------- +void userMemoryAdjustPoolSize(const char *poolName, Int& initialAllocationCount, Int& overflowAllocationCount) +{ + if (initialAllocationCount > 0) + return; + + for (const PoolSizeRec* p = sizes; p->name != NULL; ++p) + { + if (strcmp(p->name, poolName) == 0) + { + initialAllocationCount = p->initial; + overflowAllocationCount = p->overflow; + return; + } + } + + DEBUG_CRASH(("Initial size for pool %s not found -- you should add it to MemoryInit.cpp\n",poolName)); +} + +//----------------------------------------------------------------------------- +static Int roundUpMemBound(Int i) +{ + const int MEM_BOUND_ALIGNMENT = 4; + + if (i < MEM_BOUND_ALIGNMENT) + return MEM_BOUND_ALIGNMENT; + else + return (i + (MEM_BOUND_ALIGNMENT-1)) & ~(MEM_BOUND_ALIGNMENT-1); +} + +//----------------------------------------------------------------------------- +void userMemoryManagerInitPools() +{ + // note that we MUST use stdio stuff here, and not the normal game file system + // (with bigfile support, etc), because that relies on memory pools, which + // aren't yet initialized properly! so rely ONLY on straight stdio stuff here. + // (not even AsciiString. thanks.) + + // since we're called prior to main, the cur dir might not be what + // we expect. so do it the hard way. + char buf[_MAX_PATH]; + ::GetModuleFileName(NULL, buf, sizeof(buf)); + char* pEnd = buf + strlen(buf); + while (pEnd != buf) + { + if (*pEnd == '\\') + { + *pEnd = 0; + break; + } + --pEnd; + } + strcat(buf, "\\Data\\INI\\MemoryPools.ini"); + + FILE* fp = fopen(buf, "r"); + if (fp) + { + char poolName[256]; + int initial, overflow; + while (fgets(buf, _MAX_PATH, fp)) + { + if (buf[0] == ';') + continue; + if (sscanf(buf, "%s %d %d", poolName, &initial, &overflow ) == 3) + { + for (PoolSizeRec* p = sizes; p->name != NULL; ++p) + { + if (stricmp(p->name, poolName) == 0) + { + // currently, these must be multiples of 4. so round up. + p->initial = roundUpMemBound(initial); + p->overflow = roundUpMemBound(overflow); + break; // from for-p + } + } + } + } + fclose(fp); + } +} + diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index a612c6a0ef7..ec2b6000d19 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -52,6 +52,7 @@ #include "GameLogic/Module/CountermeasuresBehavior.h" #include "GameLogic/Module/DumbProjectileBehavior.h" #include "GameLogic/Module/FreeFallProjectileBehavior.h" +#include "GameLogic/Module/TeleportMovementBehavior.h" #include "GameLogic/Module/InstantDeathBehavior.h" #include "GameLogic/Module/SlowDeathBehavior.h" #include "GameLogic/Module/HelicopterSlowDeathUpdate.h" @@ -187,6 +188,7 @@ #include "GameLogic/Module/ToppleUpdate.h" #include "GameLogic/Module/TransportAIUpdate.h" #include "GameLogic/Module/WanderAIUpdate.h" +#include "GameLogic/Module/TeleporterAIUpdate.h" #include "GameLogic/Module/WaveGuideUpdate.h" #include "GameLogic/Module/WeaponBonusUpdate.h" #include "GameLogic/Module/ArmorDamageScalarUpdate.h" @@ -340,6 +342,7 @@ void ModuleFactory::init( void ) addModule( CountermeasuresBehavior ); addModule( DumbProjectileBehavior ); addModule( FreeFallProjectileBehavior ); + addModule( TeleportMovementBehavior ); addModule( PhysicsBehavior ); addModule( InstantDeathBehavior ); addModule( SlowDeathBehavior ); @@ -479,6 +482,7 @@ void ModuleFactory::init( void ) addModule( AnimationSteeringUpdate ); addModule( TransportAIUpdate ); addModule( WanderAIUpdate ); + addModule( TeleporterAIUpdate ); addModule( WaveGuideUpdate ); addModule( WorkerAIUpdate ); addModule( PowerPlantUpdate ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index a41e48f9e5c..13074cd6a41 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp @@ -511,19 +511,19 @@ void pickAndPlayUnitVoiceResponse( const DrawableList *list, GameMessage::Type m soundToPlayPtr = templ->getPerUnitSound( "VoiceTertiaryWeaponMode" ); break; case WEAPON_FOUR: - soundToPlayPtr = templ->getPerUnitSound( "VoicerWeaponModeFour" ); + soundToPlayPtr = templ->getPerUnitSound( "VoiceWeaponModeFour" ); break; case WEAPON_FIVE: - soundToPlayPtr = templ->getPerUnitSound( "VoicerWeaponModeFive" ); + soundToPlayPtr = templ->getPerUnitSound( "VoiceWeaponModeFive" ); break; case WEAPON_SIX: - soundToPlayPtr = templ->getPerUnitSound( "VoicerWeaponModeSix" ); + soundToPlayPtr = templ->getPerUnitSound( "VoiceWeaponModeSix" ); break; case WEAPON_SEVEN: - soundToPlayPtr = templ->getPerUnitSound( "VoicerWeaponModeSeven" ); + soundToPlayPtr = templ->getPerUnitSound( "VoiceWeaponModeSeven" ); break; case WEAPON_EIGHT: - soundToPlayPtr = templ->getPerUnitSound( "VoicerWeaponModeEight" ); + soundToPlayPtr = templ->getPerUnitSound( "VoiceWeaponModeEight" ); break; } objectWithSound = obj; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp index 018f14a7606..8853c0c99ea 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp @@ -422,6 +422,10 @@ void AIGroup::recompute( void ) if ((*i)->isKindOf(KINDOF_IMMOBILE)) continue; + // don't consider (chrono) teleporters (they are very fast, or currently disabled) + if ((*i)->isKindOf(KINDOF_TELEPORTER)) + continue; + if( (*i)->isDisabledByType( DISABLED_HELD) ) { continue; // don't bother counting riders in the max speed calculation. diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/TeleportMovementBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/TeleportMovementBehavior.cpp new file mode 100644 index 00000000000..85665d46d7d --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/TeleportMovementBehavior.cpp @@ -0,0 +1,198 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: TeleportMovementBehavior.cpp ///////////////////////////////////////////////////////////////////////// +// Author: Graham Smallwood, July 2002 +// Desc: Behavior that reacts to poison Damage by continuously damaging us further in an Update +/////////////////////////////////////////////////////////////////////////////////////////////////// + + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine +#include "Common/Xfer.h" +#include "Common/DisabledTypes.h" +#include "GameClient/Drawable.h" +#include "GameLogic/Module/TeleportMovementBehavior.h" +#include "GameLogic/AI.h" +#include "GameLogic/AIPathfind.h" +#include "GameLogic/Module/AIUpdate.h" +#include "GameLogic/Damage.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/Object.h" +#include "GameLogic/PartitionManager.h" +#include "GameLogic/TerrainLogic.h" + + +//------------------------------------------------------------------------------------------------- +TeleportMovementBehaviorModuleData::TeleportMovementBehaviorModuleData() +{ + +} + +//------------------------------------------------------------------------------------------------- +/*static*/ void TeleportMovementBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + + static const FieldParse dataFieldParse[] = + { + { "MinDistanceForTeleport", INI::parseReal, NULL, offsetof(TeleportMovementBehaviorModuleData, m_minDistance) }, + { "DisabledDurationPerDistance", INI::parseDurationReal, NULL, offsetof(TeleportMovementBehaviorModuleData, m_disabledDuration) }, + { 0, 0, 0, 0 } + }; + + UpdateModuleData::buildFieldParse(p); + p.add(dataFieldParse); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +TeleportMovementBehavior::TeleportMovementBehavior(Thing* thing, const ModuleData* moduleData) : UpdateModule(thing, moduleData) +{ + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +TeleportMovementBehavior::~TeleportMovementBehavior(void) +{ +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ + +void TeleportMovementBehavior::doTeleport(Coord3D targetPos, Real angle, Real dist) +{ + const TeleportMovementBehaviorModuleData* d = getTeleportMovementBehaviorModuleData(); + Object* obj = getObject(); + + obj->setPosition(&targetPos); + obj->setOrientation(angle); + + + UnsignedInt disabledFrame = TheGameLogic->getFrame() + REAL_TO_UNSIGNEDINT(dist * d->m_disabledDuration); + + obj->setDisabledUntil(DISABLED_PARALYZED, disabledFrame); + + //If we have a path, clear it? + +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +UpdateSleepTime TeleportMovementBehavior::update() +{ + const TeleportMovementBehaviorModuleData* d = getTeleportMovementBehaviorModuleData(); + + Object* obj = getObject(); + + AIUpdateInterface* ai = obj->getAI(); + if (!ai) + return UPDATE_SLEEP_FOREVER; + + if (ai->isMoving()) { + Object* goalObj = ai->getGoalObject(); + const Coord3D* goalPos = ai->getGoalPosition(); + + Real requiredRange = 0; + + Coord3D targetPos; + + // Get TargetPos + if (goalObj != NULL) { + targetPos = *goalObj->getPosition(); + } + else if(goalPos != NULL) { + targetPos = *goalPos; + } + + Coord3D dir; + Real distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + Real targetAngle = atan2(dir.y, dir.x); + dir.normalize(); + + Real dist = sqrt(distSq); + + if (ai->isAttacking()) { + requiredRange = obj->getLargestWeaponRange(); + } + + // We are in range already + if (dist <= requiredRange || dist <= d->m_minDistance) { + return UPDATE_SLEEP_NONE; + } + + //Adjust target to required distance + if (requiredRange > 0) { + dir.scale(requiredRange); + targetPos.sub(&dir); + targetPos.z = TheTerrainLogic->getGroundHeight(targetPos.x, targetPos.y); + } + + doTeleport(targetPos, targetAngle, dist); + + } + + return UPDATE_SLEEP_NONE; + +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void TeleportMovementBehavior::crc(Xfer* xfer) +{ + + // extend base class + UpdateModule::crc(xfer); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ + // ------------------------------------------------------------------------------------------------ +void TeleportMovementBehavior::xfer(Xfer* xfer) +{ + + // version + const XferVersion currentVersion = 2; + XferVersion version = currentVersion; + xfer->xferVersion(&version, currentVersion); + + // extend base class + UpdateModule::xfer(xfer); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void TeleportMovementBehavior::loadPostProcess(void) +{ + + // extend base class + UpdateModule::loadPostProcess(); + +} // end loadPostProcess diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index ab6137f8e36..050f74d558c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -2980,7 +2980,8 @@ Bool Object::isMobile() const if (isKindOf(KINDOF_IMMOBILE)) return false; - if( isDisabled() ) + // AW: This excemption is needed, because teleporters still need to listen to AI commands when disabled + if( isDisabled() && !isDisabledByType(DISABLED_TELEPORT) ) return false; return true; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp new file mode 100644 index 00000000000..c95602e98be --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp @@ -0,0 +1,322 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// TeleporterAIUpdate.cpp ////////// +// Will give self random move commands +// Author: Graham Smallwood, April 2002 + +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#include "Common/RandomValue.h" +#include "GameLogic/Module/TeleporterAIUpdate.h" +#include "GameLogic/Object.h" +#include "Common/Xfer.h" +#include "Common/DisabledTypes.h" +#include "GameClient/Drawable.h" +#include "GameLogic/AI.h" +#include "GameLogic/AIPathfind.h" +#include "GameLogic/Module/AIUpdate.h" +#include "GameLogic/Damage.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/Weapon.h" +#include "GameLogic/PartitionManager.h" +#include "GameLogic/TerrainLogic.h" + + +//------------------------------------------------------------------------------------------------- +TeleporterAIUpdateModuleData::TeleporterAIUpdateModuleData( void ) +{ + +} + +//------------------------------------------------------------------------------------------------- +/*static*/ void TeleporterAIUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + AIUpdateModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + { "MinDistanceForTeleport", INI::parseReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_minDistance) }, + { "DisabledDurationPerDistance", INI::parseDurationReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_disabledDuration) }, + { 0, 0, 0, 0 } + }; + p.add(dataFieldParse); +} + + +//------------------------------------------------------------------------------------------------- +AIStateMachine* TeleporterAIUpdate::makeStateMachine() +{ + return newInstance(AIStateMachine)( getObject(), "TeleporterAIUpdateMachine"); +} + +//------------------------------------------------------------------------------------------------- +TeleporterAIUpdate::TeleporterAIUpdate( Thing *thing, const ModuleData* moduleData ) : AIUpdateInterface( thing, moduleData ) +{ + +} + +//------------------------------------------------------------------------------------------------- +TeleporterAIUpdate::~TeleporterAIUpdate( void ) +{ + +} + +//------------------------------------------------------------------------------------------------- +UpdateSleepTime TeleporterAIUpdate::update( void ) +{ + //// If I'm standing still, move somewhere + //if (isIdle()) + //{ + // Coord3D dest = *(getObject()->getPosition()); + // dest.x += GameLogicRandomValue( 5, 50 ); + // dest.y += GameLogicRandomValue( 5, 50 ); + // aiMoveToPosition( &dest, CMD_FROM_AI ); + //} + + // extend + UpdateSleepTime ret = AIUpdateInterface::update(); + //return (mine < ret) ? mine : ret; + /// @todo srj -- someday, make sleepy. for now, must not sleep. + return ret; // UPDATE_SLEEP_NONE; +} // end update + + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ + +void TeleporterAIUpdate::doTeleport(Coord3D targetPos, Real angle, Real dist) +{ + const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); + Object* obj = getObject(); + + // TheAI->pathfinder()->adjustTargetDestination(source, target, pos, this, &approachTargetPos); + //TODO: Handle line of sight?! + + obj->setPosition(&targetPos); + obj->setOrientation(angle); + + destroyPath(); + setLocomotorGoalPositionExplicit(targetPos); + setLocomotorGoalOrientation(angle); + + UnsignedInt disabledFrame = TheGameLogic->getFrame() + REAL_TO_UNSIGNEDINT(dist * d->m_disabledDuration); + + obj->setDisabledUntil(DISABLED_TELEPORT, disabledFrame); + + //If we have a path, clear it? + +} + +//------------------------------------------------------------------------------------------------- + +Bool TeleporterAIUpdate::findAttackLocation(Object* victim, Coord3D* victimPos, Coord3D* targetPos) +{ + Object* obj = getObject(); + Weapon* weap = obj->getCurrentWeapon(); + if (!weap) + return False; + + bool viewBlocked; + bool inRange; + + Coord3D newPos = *targetPos; + + // TODO: use adjustTargetDestination, or replicate it (with included line of sight check) + + while (TRUE) { + + // Maybe we can use a simpler check here? + viewBlocked = TheAI->pathfinder()->isAttackViewBlockedByObstacle(obj, *targetPos, victim, *victimPos); + inRange = weap->isSourceObjectWithGoalPositionWithinAttackRange(obj, targetPos, victim, victimPos); + + if (!viewBlocked && inRange) { + targetPos* = newPos; + return true; + } + } +} + +//------------------------------------------------------------------------------------------------- +UpdateSleepTime TeleporterAIUpdate::doLocomotor(void) +{ + if (!isMoving()) { + return AIUpdateInterface::doLocomotor(); + } + + const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); + + Object* obj = getObject(); + + Object* goalObj = getGoalObject(); + const Coord3D* goalPos = getGoalPosition(); + + Real requiredRange = 0; + + Coord3D targetPos; + Coord3D dir; + Real distSq; + + //Path* path = getPath(); + + // Get TargetPos + //if (isAttackPath() && (path != NULL)) { + //if (path != NULL) { + // targetPos = *path->getFirstNode()->getPosition(); + // DEBUG_LOG((">>> TPAI - doLoc: PATH pos (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_BOUNDINGSPHERE_2D, &dir); + //}else + if (goalObj != NULL) { + targetPos = *goalObj->getPosition(); + DEBUG_LOG((">>> TPAI - doLoc: goalOBJPos (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_BOUNDINGSPHERE_2D, &dir); + } + else if (goalPos != NULL) { + targetPos = *goalPos; + DEBUG_LOG((">>> TPAI - doLoc: goalPOS (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + } + else { + return UPDATE_SLEEP_FOREVER; + } + + Real targetAngle = atan2(dir.y, dir.x); + dir.normalize(); + + Real dist = sqrt(distSq); + + Real RANGE_MARGIN = 5.0f; // We calculate distance this much shorter than weapon range + Real TELEPORT_DIST_MARGIN = 5.0f; // We teleport this much closer than needed + + + DEBUG_LOG((">>> TPAI - doLoc: LocomotorGoalType = %d\n", getLocomotorGoalType())); + + // We are within min range + if (dist <= d->m_minDistance) { + return AIUpdateInterface::doLocomotor(); + } + + if (isAttacking()) { + // requiredRange = obj->getLargestWeaponRange(); + Weapon* weap = obj->getCurrentWeapon(); + if (weap) + requiredRange = weap->getAttackRange(obj) -RANGE_MARGIN; + + //Adjust target to required distance + if (requiredRange > 0) { + dir.scale(requiredRange - TELEPORT_DIST_MARGIN); + targetPos.sub(&dir); + targetPos.z = TheTerrainLogic->getGroundHeight(targetPos.x, targetPos.y); + dist -= requiredRange; + } + + // TODO: Compute path from the adjusted position + + DEBUG_LOG((">>> TPAI - doLoc: isAttacking, dist = %f, reqRange = %f\n", dist, requiredRange)); + } + else { + TheAI->pathfinder()->adjustToPossibleDestination(obj, getLocomotorSet(), &targetPos); + } + + // We are in range already + //if (dist <= requiredRange) { + // return AIUpdateInterface::doLocomotor(); + //} + + + + DEBUG_LOG((">>> TPAI - doLoc: teleport with dist = %f\n", dist)); + doTeleport(targetPos, targetAngle, dist); + + + //DEBUG: Distance after Teleport + //Coord3D targetPos_org; + + //if (goalObj != NULL) { + // targetPos_org = *goalObj->getPosition(); + //} + //else if (goalPos != NULL) { + // targetPos_org = *goalPos; + //} + //distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos_org, FROM_CENTER_2D); + //dist = sqrt(distSq); + + //DEBUG_LOG((">>> TPAI - doLoc: goalPos(1) = %f, %f, %f\n", targetPos_org.x, targetPos_org.y, targetPos_org.z)); + //DEBUG_LOG((">>> TPAI - doLoc: distance after teleport = %f\n", dist)); + + + return AIUpdateInterface::doLocomotor(); + + +} + +//------------------------------------------------------------------------------------------------- +/** + * See if we can do a quick path without pathfinding. + */ +Bool TeleporterAIUpdate::canComputeQuickPath(void) +{ + return true; +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +Bool TeleporterAIUpdate::computeQuickPath(const Coord3D* destination) +{ + return AIUpdateInterface::computeQuickPath(destination); +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void TeleporterAIUpdate::crc( Xfer *xfer ) +{ + // extend base class + AIUpdateInterface::crc(xfer); +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void TeleporterAIUpdate::xfer( Xfer *xfer ) +{ + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // extend base class + AIUpdateInterface::xfer(xfer); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void TeleporterAIUpdate::loadPostProcess( void ) +{ + // extend base class + AIUpdateInterface::loadPostProcess(); +} // end loadPostProcess From e23f54dd2889a943de91425149b5a0774312a22e Mon Sep 17 00:00:00 2001 From: andreasw Date: Tue, 24 Jun 2025 17:18:57 +0200 Subject: [PATCH 26/42] Chrono movement progress --- .../GameLogic/Module/TeleporterAIUpdate.h | 2 +- .../Update/AIUpdate/TeleporterAIUpdate.cpp | 122 +++++++++++++++--- 2 files changed, 104 insertions(+), 20 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleporterAIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleporterAIUpdate.h index 924337a1076..9e8ff4a0a2d 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleporterAIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleporterAIUpdate.h @@ -73,7 +73,7 @@ class TeleporterAIUpdate : public AIUpdateInterface void doTeleport(Coord3D targetPos, Real angle, Real dist); - Bool findAttackLocation(Object* victim, Coord3D* victimPos, Coord3D* targetPos); + Bool findAttackLocation(Object* victim, const Coord3D* victimPos, Coord3D* targetPos, Real* targetAngle); virtual UpdateSleepTime doLocomotor(); 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 c95602e98be..400262d47d3 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp @@ -34,6 +34,7 @@ #include "Common/Xfer.h" #include "Common/DisabledTypes.h" #include "GameClient/Drawable.h" +#include "GameClient/FXList.h" #include "GameLogic/AI.h" #include "GameLogic/AIPathfind.h" #include "GameLogic/Module/AIUpdate.h" @@ -131,31 +132,106 @@ void TeleporterAIUpdate::doTeleport(Coord3D targetPos, Real angle, Real dist) //------------------------------------------------------------------------------------------------- -Bool TeleporterAIUpdate::findAttackLocation(Object* victim, Coord3D* victimPos, Coord3D* targetPos) +Bool TeleporterAIUpdate::findAttackLocation(Object* victim, const Coord3D* victimPos, Coord3D* targetPos, Real* targetAngle) { Object* obj = getObject(); Weapon* weap = obj->getCurrentWeapon(); if (!weap) - return False; + return false; - bool viewBlocked; - bool inRange; + bool viewBlocked = TheAI->pathfinder()->isAttackViewBlockedByObstacle(obj, *targetPos, victim, *victimPos); + bool inRange = weap->isSourceObjectWithGoalPositionWithinAttackRange(obj, targetPos, victim, victimPos); - Coord3D newPos = *targetPos; + Real RANGE_MARGIN = 10.0f; - // TODO: use adjustTargetDestination, or replicate it (with included line of sight check) + // If the unit's current distance is lower than the attack range, we try to keep this distance - while (TRUE) { - // Maybe we can use a simpler check here? - viewBlocked = TheAI->pathfinder()->isAttackViewBlockedByObstacle(obj, *targetPos, victim, *victimPos); - inRange = weap->isSourceObjectWithGoalPositionWithinAttackRange(obj, targetPos, victim, victimPos); + Real maxRange = weap->getAttackRange(obj) - RANGE_MARGIN; + Real range = maxRange - weap->getTemplate()->getMinimumAttackRange(); - if (!viewBlocked && inRange) { - targetPos* = newPos; - return true; + if (!viewBlocked && inRange) { + DEBUG_LOG((">>> TPAI - findAttackLocation: done with initial pos\n")); + return true; + } + + // Calculate direction vector from victim to candidate position + Coord3D dir; + Real distSq = ThePartitionManager->getGoalDistanceSquared(obj, targetPos, victimPos, FROM_CENTER_2D, &dir); + Real dist = sqrt(distSq); + if (dist < maxRange) { + maxRange = dist; + } + + Coord2D direction; + direction.x = -dir.x; + direction.y = -dir.y; + Real initAngle = atan2(direction.y, direction.x); // angle from victim to target + + direction.normalize(); + //distance = sqrt(distSq); + + //if (distance > 0) { + // direction.x = (targetPos->x - targetPos->x) / distance; + // direction.y = (targetPos->y - targetPos->y) / distance; + //} + //else { + // // if we are directly at the target, but are not actually in range, something went wrong + // return false; + //} + + // DEBUG: + const FXList* debug_fx1 = TheFXListStore->findFXList("FX_DEBUG_MARKER_GREEN"); + const FXList* debug_fx2 = TheFXListStore->findFXList("FX_DEBUG_MARKER_RED"); + + Coord3D newPos; + newPos.x = targetPos->x; + newPos.y = targetPos->y; + newPos.z = targetPos->z; + + + const Real maxAngle = deg2rad(180.0f); + const Real step_size_angle = deg2rad(10.0f); + const Real step_size_length = 15.0f; + // const int max_steps = 500; + + const int max_rings = REAL_TO_INT(range / step_size_length); + const int max_steps = REAL_TO_INT(maxAngle / step_size_angle); + DEBUG_LOG((">>> TPAI - findAttackLocation: range = %f, max_rings = %d\n", range, max_rings)); + for (int ring = 0; ring < max_rings; ++ring) { + + Real radius = maxRange - (ring * step_size_length); + + for (int step = 0; step < max_steps; ++step) { + int sign = (step % 2) ? 1 : -1; + Real angle = initAngle + (step * sign * step_size_angle); + + //polar offset + newPos.x = victimPos->x + radius * cos(angle); + newPos.y = victimPos->y + radius * sin(angle); + newPos.z = TheTerrainLogic->getGroundHeight(newPos.x, newPos.y); + + if (sign == 1) + FXList::doFXPos(debug_fx1, &newPos); + else + FXList::doFXPos(debug_fx2, &newPos); + + viewBlocked = TheAI->pathfinder()->isAttackViewBlockedByObstacle(obj, newPos, victim, *victimPos); + inRange = weap->isSourceObjectWithGoalPositionWithinAttackRange(obj, &newPos, victim, victimPos); + + if (!viewBlocked && inRange) { + *targetPos = newPos; + *targetAngle = angle + PI; + DEBUG_LOG((">>> TPAI - findAttackLocation: done after ring=%d, step=%d\n", ring, step)); + + return true; + } } } + + DEBUG_LOG((">>> TPAI - findAttackLocation: failed to find attack position\n")); + + return false; } //------------------------------------------------------------------------------------------------- @@ -221,23 +297,31 @@ UpdateSleepTime TeleporterAIUpdate::doLocomotor(void) // requiredRange = obj->getLargestWeaponRange(); Weapon* weap = obj->getCurrentWeapon(); if (weap) - requiredRange = weap->getAttackRange(obj) -RANGE_MARGIN; + requiredRange = weap->getAttackRange(obj) - RANGE_MARGIN; //Adjust target to required distance if (requiredRange > 0) { - dir.scale(requiredRange - TELEPORT_DIST_MARGIN); + dir.scale(min(dist, requiredRange - TELEPORT_DIST_MARGIN)); targetPos.sub(&dir); targetPos.z = TheTerrainLogic->getGroundHeight(targetPos.x, targetPos.y); - dist -= requiredRange; + + //if (dist > requiredRange) + // dist -= requiredRange; } - // TODO: Compute path from the adjusted position + DEBUG_LOG((">>> TPAI - doLoc: findAttackLocation targetPos (BEFORE) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + findAttackLocation(goalObj, goalPos, &targetPos, &targetAngle); + DEBUG_LOG((">>> TPAI - doLoc: findAttackLocation targetPos (AFTER) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + //recompute distance and angle + distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + //targetAngle = atan2(dir.y, dir.x); + dist = sqrt(distSq); DEBUG_LOG((">>> TPAI - doLoc: isAttacking, dist = %f, reqRange = %f\n", dist, requiredRange)); } - else { + /*else { TheAI->pathfinder()->adjustToPossibleDestination(obj, getLocomotorSet(), &targetPos); - } + }*/ // We are in range already //if (dist <= requiredRange) { From 5d950197019e73c3f7c035bed075547f5d5451c4 Mon Sep 17 00:00:00 2001 From: Andi Date: Sat, 28 Jun 2025 15:27:12 +0200 Subject: [PATCH 27/42] basic movement working --- .../GameLogic/Module/TeleporterAIUpdate.h | 12 +- .../Source/GameLogic/AI/AIGroup.cpp | 13 +- .../Update/AIUpdate/TeleporterAIUpdate.cpp | 173 ++++++++++++------ 3 files changed, 136 insertions(+), 62 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleporterAIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleporterAIUpdate.h index 9e8ff4a0a2d..12e79218f38 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleporterAIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleporterAIUpdate.h @@ -33,6 +33,8 @@ #include "GameLogic/Module/AIUpdate.h" +class FXList; + //------------------------------------------------------------------------------------------------- class TeleporterAIUpdateModuleData : public AIUpdateModuleData @@ -41,6 +43,9 @@ class TeleporterAIUpdateModuleData : public AIUpdateModuleData Real m_minDistance; Real m_disabledDuration; + const FXList* m_sourceFX; + const FXList* m_targetFX; + TeleporterAIUpdateModuleData(); static void buildFieldParse(MultiIniFieldParse& p); @@ -71,10 +76,12 @@ class TeleporterAIUpdate : public AIUpdateInterface protected: - void doTeleport(Coord3D targetPos, Real angle, Real dist); + UpdateSleepTime doTeleport(Coord3D targetPos, Real angle, Real dist); Bool findAttackLocation(Object* victim, const Coord3D* victimPos, Coord3D* targetPos, Real* targetAngle); + Bool isLocationValid(Object* obj, const Coord3D* targetPos, Object* victim, const Coord3D* victimPos, Weapon* weap); + virtual UpdateSleepTime doLocomotor(); //virtual Bool getTreatAsAircraftForLocoDistToGoal() const; @@ -93,6 +100,9 @@ class TeleporterAIUpdate : public AIUpdateInterface virtual AIStateMachine* makeStateMachine(); +//private: +// Bool m_inAttackPos; + }; #endif diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp index 8853c0c99ea..5b531f55b58 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp @@ -564,7 +564,12 @@ Bool AIGroup::friend_computeGroundPath( const Coord3D *pos, CommandSourceType cm if( obj->getAI()==NULL ) { continue; - } + } + if (obj->isKindOf(KINDOF_TELEPORTER)) + { + continue; + } + if( obj->isKindOf( KINDOF_INFANTRY ) ) { numInfantry++; @@ -687,6 +692,8 @@ static void clampToMap(Coord3D *dest, PlayerType pt) Bool AIGroup::friend_moveInfantryToPos( const Coord3D *pos, CommandSourceType cmdSource ) { + DEBUG_LOG(("!! AIGroup::friend_moveInfantryToPos.\n")); + if (m_groundPath==NULL) return false; Int numColumns = 3; @@ -756,6 +763,10 @@ Bool AIGroup::friend_moveInfantryToPos( const Coord3D *pos, CommandSourceType cm PlayerType controllingPlayerType = PLAYER_COMPUTER; for( i = m_memberList.begin(); i != m_memberList.end(); ++i ) { + if ((*i)->isKindOf(KINDOF_TELEPORTER)) + { + continue; + } if ((*i)->isDisabledByType( DISABLED_HELD ) ) { continue; // don't bother telling the occupants to move. 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 400262d47d3..0557ae928b8 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp @@ -48,7 +48,8 @@ //------------------------------------------------------------------------------------------------- TeleporterAIUpdateModuleData::TeleporterAIUpdateModuleData( void ) { - + m_sourceFX = NULL; + m_targetFX = NULL; } //------------------------------------------------------------------------------------------------- @@ -60,6 +61,8 @@ TeleporterAIUpdateModuleData::TeleporterAIUpdateModuleData( void ) { { "MinDistanceForTeleport", INI::parseReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_minDistance) }, { "DisabledDurationPerDistance", INI::parseDurationReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_disabledDuration) }, + { "TeleportStartFX", INI::parseFXList, NULL, offsetof(TeleporterAIUpdateModuleData, m_sourceFX) }, + { "TeleportEndFX", INI::parseFXList, NULL, offsetof(TeleporterAIUpdateModuleData, m_targetFX) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); @@ -75,7 +78,7 @@ AIStateMachine* TeleporterAIUpdate::makeStateMachine() //------------------------------------------------------------------------------------------------- TeleporterAIUpdate::TeleporterAIUpdate( Thing *thing, const ModuleData* moduleData ) : AIUpdateInterface( thing, moduleData ) { - + //m_inAttackPos = FALSE; } //------------------------------------------------------------------------------------------------- @@ -107,30 +110,45 @@ UpdateSleepTime TeleporterAIUpdate::update( void ) // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ -void TeleporterAIUpdate::doTeleport(Coord3D targetPos, Real angle, Real dist) +UpdateSleepTime TeleporterAIUpdate::doTeleport(Coord3D targetPos, Real angle, Real dist) { const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); Object* obj = getObject(); - // TheAI->pathfinder()->adjustTargetDestination(source, target, pos, this, &approachTargetPos); + FXList::doFXObj(d->m_sourceFX, getObject()); + //TODO: Handle line of sight?! obj->setPosition(&targetPos); obj->setOrientation(angle); + FXList::doFXObj(d->m_targetFX, getObject()); + destroyPath(); - setLocomotorGoalPositionExplicit(targetPos); + //friend_endingMove(); + TheAI->pathfinder()->updateGoal(obj, &targetPos, TheTerrainLogic->getLayerForDestination(&targetPos)); + // setLocomotorGoalPositionExplicit(targetPos); setLocomotorGoalOrientation(angle); - UnsignedInt disabledFrame = TheGameLogic->getFrame() + REAL_TO_UNSIGNEDINT(dist * d->m_disabledDuration); + UnsignedInt disabledFrames = REAL_TO_UNSIGNEDINT(dist * d->m_disabledDuration); - obj->setDisabledUntil(DISABLED_TELEPORT, disabledFrame); + obj->setDisabledUntil(DISABLED_TELEPORT, TheGameLogic->getFrame() + disabledFrames); - //If we have a path, clear it? + return UPDATE_SLEEP(disabledFrames); } //------------------------------------------------------------------------------------------------- +Bool TeleporterAIUpdate::isLocationValid(Object* obj, const Coord3D* targetPos, Object* victim, const Coord3D* victimPos, Weapon* weap) +{ + 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); + + return !viewBlocked && inRange && posValid; +} + Bool TeleporterAIUpdate::findAttackLocation(Object* victim, const Coord3D* victimPos, Coord3D* targetPos, Real* targetAngle) { @@ -139,8 +157,45 @@ Bool TeleporterAIUpdate::findAttackLocation(Object* victim, const Coord3D* victi if (!weap) return false; - bool viewBlocked = TheAI->pathfinder()->isAttackViewBlockedByObstacle(obj, *targetPos, victim, *victimPos); - bool inRange = weap->isSourceObjectWithGoalPositionWithinAttackRange(obj, targetPos, victim, victimPos); + Coord3D newPos; + newPos.x = targetPos->x; + newPos.y = targetPos->y; + newPos.z = targetPos->z; + + // Check if the current location is valid. + // This needs to be rechecked after the disabled timer. + if (isLocationValid(obj, targetPos, victim, victimPos, weap)) { + + // After verifying the initial location + //if (!m_inAttackPos) { // Only adjust before a teleport + if (!TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &newPos)) { + DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination failed!\n")); + } + else { + DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); + } + + if (isLocationValid(obj, &newPos, victim, victimPos, weap)) { + DEBUG_LOG((">>> TPAI - findAttackLocation: done with initial pos\n")); + *targetPos = newPos; + return true; + } + //} + //else { + // DEBUG_LOG((">>> TPAI - findAttackLocation: done with initial pos\n")); + // *targetPos = newPos; + // return true; + //} + } + + newPos.x = targetPos->x; + newPos.y = targetPos->y; + newPos.z = targetPos->z; + + //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); Real RANGE_MARGIN = 10.0f; @@ -150,10 +205,6 @@ Bool TeleporterAIUpdate::findAttackLocation(Object* victim, const Coord3D* victi Real maxRange = weap->getAttackRange(obj) - RANGE_MARGIN; Real range = maxRange - weap->getTemplate()->getMinimumAttackRange(); - if (!viewBlocked && inRange) { - DEBUG_LOG((">>> TPAI - findAttackLocation: done with initial pos\n")); - return true; - } // Calculate direction vector from victim to candidate position Coord3D dir; @@ -181,13 +232,8 @@ Bool TeleporterAIUpdate::findAttackLocation(Object* victim, const Coord3D* victi //} // DEBUG: - const FXList* debug_fx1 = TheFXListStore->findFXList("FX_DEBUG_MARKER_GREEN"); - const FXList* debug_fx2 = TheFXListStore->findFXList("FX_DEBUG_MARKER_RED"); - - Coord3D newPos; - newPos.x = targetPos->x; - newPos.y = targetPos->y; - newPos.z = targetPos->z; + /*const FXList* debug_fx1 = TheFXListStore->findFXList("FX_DEBUG_MARKER_GREEN"); + const FXList* debug_fx2 = TheFXListStore->findFXList("FX_DEBUG_MARKER_RED");*/ const Real maxAngle = deg2rad(180.0f); @@ -211,15 +257,27 @@ Bool TeleporterAIUpdate::findAttackLocation(Object* victim, const Coord3D* victi newPos.y = victimPos->y + radius * sin(angle); newPos.z = TheTerrainLogic->getGroundHeight(newPos.x, newPos.y); - if (sign == 1) + //viewBlocked = TheAI->pathfinder()->isAttackViewBlockedByObstacle(obj, newPos, victim, *victimPos); + //inRange = weap->isSourceObjectWithGoalPositionWithinAttackRange(obj, &newPos, victim, victimPos); + //destinationLayer = TheTerrainLogic->getLayerForDestination(&newPos); + //posValid = TheAI->pathfinder()->validMovementPosition(getObject()->getCrusherLevel() > 0, destinationLayer, getLocomotorSet(), &newPos); + + DEBUG_LOG((">>> TPAI - findAttackLocation: candidate Pos: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); + + // TheAI->pathfinder()->adjustTargetDestination(obj, victim, victimPos, weap, &newPos); + if (!TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &newPos)) { + DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination failed!\n")); + } + else { + DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); + } + + /*if (sign == 1) FXList::doFXPos(debug_fx1, &newPos); else - FXList::doFXPos(debug_fx2, &newPos); - - viewBlocked = TheAI->pathfinder()->isAttackViewBlockedByObstacle(obj, newPos, victim, *victimPos); - inRange = weap->isSourceObjectWithGoalPositionWithinAttackRange(obj, &newPos, victim, victimPos); + FXList::doFXPos(debug_fx2, &newPos);*/ - if (!viewBlocked && inRange) { + if (isLocationValid(obj, &newPos, victim, victimPos, weap)) { *targetPos = newPos; *targetAngle = angle + PI; DEBUG_LOG((">>> TPAI - findAttackLocation: done after ring=%d, step=%d\n", ring, step)); @@ -293,66 +351,59 @@ UpdateSleepTime TeleporterAIUpdate::doLocomotor(void) return AIUpdateInterface::doLocomotor(); } + // TODO: IF object is already in attacking position and can fire, do not adjust any positions?! + // But still check if position is valid! + if (isAttacking()) { // requiredRange = obj->getLargestWeaponRange(); Weapon* weap = obj->getCurrentWeapon(); - if (weap) - requiredRange = weap->getAttackRange(obj) - RANGE_MARGIN; + if (!weap) + return AIUpdateInterface::doLocomotor(); + + // Check if current position is valid for attack + if (isLocationValid(obj, obj->getPosition(), goalObj, goalPos, weap)) { + return AIUpdateInterface::doLocomotor(); + } + + requiredRange = weap->getAttackRange(obj) - RANGE_MARGIN; //Adjust target to required distance if (requiredRange > 0) { dir.scale(min(dist, requiredRange - TELEPORT_DIST_MARGIN)); targetPos.sub(&dir); targetPos.z = TheTerrainLogic->getGroundHeight(targetPos.x, targetPos.y); - - //if (dist > requiredRange) - // dist -= requiredRange; } - DEBUG_LOG((">>> TPAI - doLoc: findAttackLocation targetPos (BEFORE) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); - findAttackLocation(goalObj, goalPos, &targetPos, &targetAngle); - DEBUG_LOG((">>> TPAI - doLoc: findAttackLocation targetPos (AFTER) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + // Find proper attack position for adjusted target + if (!findAttackLocation(goalObj, goalPos, &targetPos, &targetAngle)) { + DEBUG_LOG((">>> TPAI - doLoc: isAttacking. FAILED TO FIND VALID LOCATION!\n")); + + // This might happen if we try to attack e.g. a boat in water + // TODO: Should we move as close as we can? + + return AIUpdateInterface::doLocomotor(); + } + //DEBUG_LOG((">>> TPAI - doLoc: findAttackLocation targetPos (AFTER) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + //recompute distance and angle distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); //targetAngle = atan2(dir.y, dir.x); dist = sqrt(distSq); DEBUG_LOG((">>> TPAI - doLoc: isAttacking, dist = %f, reqRange = %f\n", dist, requiredRange)); + //m_inAttackPos = TRUE; } /*else { - TheAI->pathfinder()->adjustToPossibleDestination(obj, getLocomotorSet(), &targetPos); + m_inAttackPos = FALSE; }*/ - - // We are in range already - //if (dist <= requiredRange) { - // return AIUpdateInterface::doLocomotor(); - //} - + DEBUG_LOG((">>> TPAI - doLoc: teleport with dist = %f\n", dist)); doTeleport(targetPos, targetAngle, dist); - - //DEBUG: Distance after Teleport - //Coord3D targetPos_org; - - //if (goalObj != NULL) { - // targetPos_org = *goalObj->getPosition(); - //} - //else if (goalPos != NULL) { - // targetPos_org = *goalPos; - //} - //distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos_org, FROM_CENTER_2D); - //dist = sqrt(distSq); - - //DEBUG_LOG((">>> TPAI - doLoc: goalPos(1) = %f, %f, %f\n", targetPos_org.x, targetPos_org.y, targetPos_org.z)); - //DEBUG_LOG((">>> TPAI - doLoc: distance after teleport = %f\n", dist)); - - return AIUpdateInterface::doLocomotor(); - } //------------------------------------------------------------------------------------------------- @@ -394,6 +445,8 @@ void TeleporterAIUpdate::xfer( Xfer *xfer ) // extend base class AIUpdateInterface::xfer(xfer); + //xfer->xferBool(&m_inAttackPos); + } // end xfer // ------------------------------------------------------------------------------------------------ From 92ed8ebb1f7fef0d492f161263b043c0c7218d63 Mon Sep 17 00:00:00 2001 From: andreasw Date: Tue, 1 Jul 2025 15:23:54 +0200 Subject: [PATCH 28/42] guard works now --- .../Include/GameLogic/AIStateMachine.h | 6 ++ .../Source/GameLogic/AI/AIStates.cpp | 14 ++++ .../Update/AIUpdate/TeleporterAIUpdate.cpp | 64 ++++++++++++++++--- 3 files changed, 75 insertions(+), 9 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/AIStateMachine.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/AIStateMachine.h index 1a5cf974f82..d99b8560b2b 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/AIStateMachine.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/AIStateMachine.h @@ -165,6 +165,8 @@ class AIStateMachine : public StateMachine StateReturnType setTemporaryState( StateID newStateID, Int frameLimitCoount ); ///< change the temporary state of the machine, and number of frames limit. StateID getTemporaryState(void) const {return m_temporaryState?m_temporaryState->getID():INVALID_STATE_ID;} + AIGuardMachine* getGuardMachine( void ); + public: // overrides. virtual StateReturnType updateStateMachine(); ///< run one step of the machine #ifdef STATE_MACHINE_DEBUG @@ -1177,6 +1179,10 @@ class AIGuardState : public State #ifdef STATE_MACHINE_DEBUG virtual AsciiString getName() const ; #endif + + // For Teleporter Guard logic + inline AIGuardMachine* const getGuardMachine() { return m_guardMachine; } + protected: // snapshot interface virtual void crc( Xfer *xfer ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp index ace990a8d5b..1e748b2ce54 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp @@ -1095,6 +1095,20 @@ Squad *AIStateMachine::getGoalSquad( void ) return m_goalSquad; } + +//---------------------------------------------------------------------------------------------------------- +AIGuardMachine* AIStateMachine::getGuardMachine(void) +{ + if (getCurrentStateID() == AI_GUARD) { + AIGuardState* guardState = (AIGuardState*)(StateMachine::internalGetState(getCurrentStateID())); + if (guardState != NULL) { + return guardState->getGuardMachine(); + } + } + + return NULL; +} + // State transition conditions ---------------------------------------------------------------------------- /** * Return true if the machine's owner's current weapon's range 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 0557ae928b8..4266a55b4cc 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp @@ -36,6 +36,7 @@ #include "GameClient/Drawable.h" #include "GameClient/FXList.h" #include "GameLogic/AI.h" +#include "GameLogic/AIGuard.h" #include "GameLogic/AIPathfind.h" #include "GameLogic/Module/AIUpdate.h" #include "GameLogic/Damage.h" @@ -312,26 +313,71 @@ UpdateSleepTime TeleporterAIUpdate::doLocomotor(void) Coord3D dir; Real distSq; + // TODO: Check states + // - (generic) Moving + // - Attacking + // - Guard + // -- GuardAttack + // -- Move to Object + // - Enter + //Path* path = getPath(); // Get TargetPos - //if (isAttackPath() && (path != NULL)) { - //if (path != NULL) { - // targetPos = *path->getFirstNode()->getPosition(); - // DEBUG_LOG((">>> TPAI - doLoc: PATH pos (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); - // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_BOUNDINGSPHERE_2D, &dir); - //}else + if (goalObj != NULL) { targetPos = *goalObj->getPosition(); + //goalPos = targetPos; //This should be the same anyways DEBUG_LOG((">>> TPAI - doLoc: goalOBJPos (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_BOUNDINGSPHERE_2D, &dir); } - else if (goalPos != NULL) { + else if (goalPos != NULL && !(goalPos->x == 0 && goalPos->y == 0 && goalPos->z == 0)) { targetPos = *goalPos; DEBUG_LOG((">>> TPAI - doLoc: goalPOS (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); } + //else if (getGuardLocation() != NULL && !(getGuardLocation()->x == 0 && getGuardLocation()->y == 0 && getGuardLocation()->z == 0)) { // getStateMachine()->isInGuardIdleState() + // targetPos = *getGuardLocation(); + // TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &targetPos); + // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + // DEBUG_LOG((">>> TPAI - doLoc: goalPos GUARD (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + // if (getStateMachine()->isInGuardIdleState()) { + // requiredRange = 25.0f; // Allow extra range to give some room for large groups guarding + // } + else if (getStateMachine()->getCurrentStateID() == AI_GUARD) { + if (isAttacking()) { + AIGuardMachine* guardMachine = getStateMachine()->getGuardMachine(); + if (guardMachine != NULL) { + ObjectID nemID = guardMachine->getNemesisID(); + if (nemID != INVALID_ID) { + Object* nemesis = TheGameLogic->findObjectByID(nemID); + if (nemesis != NULL) { + goalObj = nemesis; + goalPos = goalObj->getPosition(); + targetPos = *goalPos; + + DEBUG_LOG((">>> TPAI - doLoc: goalPos GUARD NEMESIS (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_BOUNDINGSPHERE_2D, &dir); + } + } + } + } + else if (getGuardLocation() != NULL && !(getGuardLocation()->x == 0 && getGuardLocation()->y == 0 && getGuardLocation()->z == 0)) { // getStateMachine()->isInGuardIdleState() + targetPos = *getGuardLocation(); + TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &targetPos); + distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + DEBUG_LOG((">>> TPAI - doLoc: goalPos GUARD (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + if (getStateMachine()->isInGuardIdleState()) { + requiredRange = 25.0f; // Allow extra range to give some room for large groups guarding + } + } + } + //else if (isAttackPath() && (path != NULL)) { + // targetPos = *path->getFirstNode()->getPosition(); + // DEBUG_LOG((">>> TPAI - doLoc: PATH pos (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_BOUNDINGSPHERE_2D, &dir); else { + DEBUG_LOG((">>> TPAI - doLoc: GOAL POS AND OBJ ARE NULL??\n")); return UPDATE_SLEEP_FOREVER; } @@ -344,10 +390,10 @@ UpdateSleepTime TeleporterAIUpdate::doLocomotor(void) Real TELEPORT_DIST_MARGIN = 5.0f; // We teleport this much closer than needed - DEBUG_LOG((">>> TPAI - doLoc: LocomotorGoalType = %d\n", getLocomotorGoalType())); + DEBUG_LOG((">>> TPAI - doLoc: LocomotorGoalType = %d, AI STATE = %s (%d)\n", getLocomotorGoalType(), getStateMachine()->getCurrentStateName(), getStateMachine()->getCurrentStateID())); // We are within min range - if (dist <= d->m_minDistance) { + if (dist <= d->m_minDistance || dist <= requiredRange) { return AIUpdateInterface::doLocomotor(); } From 6911d99aa018b5950136d67b7e030310ed1c0647 Mon Sep 17 00:00:00 2001 From: andreasw Date: Tue, 1 Jul 2025 17:52:01 +0200 Subject: [PATCH 29/42] entering buildings works now --- .../GameLogic/Object/Contain/OpenContain.cpp | 6 ++ .../Update/AIUpdate/TeleporterAIUpdate.cpp | 86 ++++++++++++------- 2 files changed, 59 insertions(+), 33 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index c6fd0e1c300..e8e976a421f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp @@ -292,6 +292,9 @@ void OpenContain::addOrRemoveObjFromWorld(Object* obj, Bool add) //------------------------------------------------------------------------------------------------- void OpenContain::addToContain( Object *rider ) { + if (rider->isDisabledByType(DISABLED_TELEPORT)) + return; + if( getObject()->checkAndDetonateBoobyTrap(rider) ) { // Whoops, I was mined. Cancel if I (or they) am now dead. @@ -891,6 +894,9 @@ void OpenContain::onDie( const DamageInfo * damageInfo ) // ------------------------------------------------------------------------------------------------ Bool OpenContain::isValidContainerFor(const Object* obj, Bool checkCapacity) const { + //if (obj->isDisabledByType(DISABLED_TELEPORT)) + // return false; + const Object *us = getObject(); const OpenContainModuleData *modData = getOpenContainModuleData(); 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 4266a55b4cc..bf33e5f1b14 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp @@ -328,22 +328,17 @@ UpdateSleepTime TeleporterAIUpdate::doLocomotor(void) if (goalObj != NULL) { targetPos = *goalObj->getPosition(); //goalPos = targetPos; //This should be the same anyways - DEBUG_LOG((">>> TPAI - doLoc: goalOBJPos (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); - distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_BOUNDINGSPHERE_2D, &dir); + //DEBUG_LOG((">>> TPAI - doLoc: goalOBJPos (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + //if (isAttacking()) + distSq = ThePartitionManager->getDistanceSquared(obj, goalObj, FROM_BOUNDINGSPHERE_2D, &dir); + //else + // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); } else if (goalPos != NULL && !(goalPos->x == 0 && goalPos->y == 0 && goalPos->z == 0)) { targetPos = *goalPos; - DEBUG_LOG((">>> TPAI - doLoc: goalPOS (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + //DEBUG_LOG((">>> TPAI - doLoc: goalPOS (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); } - //else if (getGuardLocation() != NULL && !(getGuardLocation()->x == 0 && getGuardLocation()->y == 0 && getGuardLocation()->z == 0)) { // getStateMachine()->isInGuardIdleState() - // targetPos = *getGuardLocation(); - // TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &targetPos); - // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - // DEBUG_LOG((">>> TPAI - doLoc: goalPos GUARD (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); - // if (getStateMachine()->isInGuardIdleState()) { - // requiredRange = 25.0f; // Allow extra range to give some room for large groups guarding - // } else if (getStateMachine()->getCurrentStateID() == AI_GUARD) { if (isAttacking()) { AIGuardMachine* guardMachine = getStateMachine()->getGuardMachine(); @@ -356,50 +351,49 @@ UpdateSleepTime TeleporterAIUpdate::doLocomotor(void) goalPos = goalObj->getPosition(); targetPos = *goalPos; - DEBUG_LOG((">>> TPAI - doLoc: goalPos GUARD NEMESIS (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); - distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_BOUNDINGSPHERE_2D, &dir); + //DEBUG_LOG((">>> TPAI - doLoc: goalPos GUARD NEMESIS (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + //distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_BOUNDINGSPHERE_2D, &dir); + distSq = ThePartitionManager->getDistanceSquared(obj, goalObj, FROM_BOUNDINGSPHERE_2D, &dir); } } } } else if (getGuardLocation() != NULL && !(getGuardLocation()->x == 0 && getGuardLocation()->y == 0 && getGuardLocation()->z == 0)) { // getStateMachine()->isInGuardIdleState() targetPos = *getGuardLocation(); - TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &targetPos); + //TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &targetPos); distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - DEBUG_LOG((">>> TPAI - doLoc: goalPos GUARD (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + //DEBUG_LOG((">>> TPAI - doLoc: goalPos GUARD (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); if (getStateMachine()->isInGuardIdleState()) { requiredRange = 25.0f; // Allow extra range to give some room for large groups guarding } } } - //else if (isAttackPath() && (path != NULL)) { - // targetPos = *path->getFirstNode()->getPosition(); - // DEBUG_LOG((">>> TPAI - doLoc: PATH pos (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); - // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_BOUNDINGSPHERE_2D, &dir); else { DEBUG_LOG((">>> TPAI - doLoc: GOAL POS AND OBJ ARE NULL??\n")); return UPDATE_SLEEP_FOREVER; } - Real targetAngle = atan2(dir.y, dir.x); - dir.normalize(); + if (getStateMachine()->getCurrentStateID() == AI_ENTER) { + requiredRange = 15.0f; + } - Real dist = sqrt(distSq); + DEBUG_LOG((">>> TPAI - doLoc: LocomotorGoalType = %d, AI STATE = %s (%d)\n", getLocomotorGoalType(), getStateMachine()->getCurrentStateName(), getStateMachine()->getCurrentStateID())); Real RANGE_MARGIN = 5.0f; // We calculate distance this much shorter than weapon range Real TELEPORT_DIST_MARGIN = 5.0f; // We teleport this much closer than needed - - DEBUG_LOG((">>> TPAI - doLoc: LocomotorGoalType = %d, AI STATE = %s (%d)\n", getLocomotorGoalType(), getStateMachine()->getCurrentStateName(), getStateMachine()->getCurrentStateID())); + // Get initial dist and dir + // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + Real dist = sqrt(distSq); + Real targetAngle = atan2(dir.y, dir.x); + dir.normalize(); // We are within min range if (dist <= d->m_minDistance || dist <= requiredRange) { return AIUpdateInterface::doLocomotor(); } - // TODO: IF object is already in attacking position and can fire, do not adjust any positions?! - // But still check if position is valid! - + //When we attack, we attempt to teleport into range if (isAttacking()) { // requiredRange = obj->getLargestWeaponRange(); Weapon* weap = obj->getCurrentWeapon(); @@ -433,17 +427,43 @@ UpdateSleepTime TeleporterAIUpdate::doLocomotor(void) //recompute distance and angle distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - //targetAngle = atan2(dir.y, dir.x); + ////targetAngle = atan2(dir.y, dir.x); dist = sqrt(distSq); - DEBUG_LOG((">>> TPAI - doLoc: isAttacking, dist = %f, reqRange = %f\n", dist, requiredRange)); + //DEBUG_LOG((">>> TPAI - doLoc: isAttacking, dist = %f, reqRange = %f\n", dist, requiredRange)); //m_inAttackPos = TRUE; } - /*else { - m_inAttackPos = FALSE; - }*/ - + //else if( /*use special power?*/) { + // //same as with attacks, try to get into range + //} + else if (getStateMachine()->getCurrentStateID() == AI_ENTER) { + // We need to correct the position to the outer bounding box of the structure + //Adjust target to required distance + requiredRange = goalObj->getGeometryInfo().getBoundingCircleRadius(); + if (requiredRange > 0) { + dir.scale(min(dist, requiredRange)); + targetPos.sub(&dir); + targetPos.z = TheTerrainLogic->getGroundHeight(targetPos.x, targetPos.y); + } + TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &targetPos); + + //recompute distance and angle + distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + targetAngle = atan2(dir.y, dir.x); + dist = sqrt(distSq); + } + else { + TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &targetPos); + //recompute distance and angle + distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + targetAngle = atan2(dir.y, dir.x); + dist = sqrt(distSq); + + //TODO: if we target an object, have the angle changed to look at the object + } + + DEBUG_LOG((">>> TPAI - doLoc: teleport with dist = %f\n", dist)); doTeleport(targetPos, targetAngle, dist); From 0d111fc4cd856b22f574b63008a473046ffd3087 Mon Sep 17 00:00:00 2001 From: Andi Date: Sat, 5 Jul 2025 17:00:50 +0200 Subject: [PATCH 30/42] Chrono Movement V1 for infantry only --- .../GameEngine/Include/Common/ModelState.h | 628 +++++++++--------- .../Include/GameClient/TintStatus.h | 1 + .../GameLogic/Module/TeleporterAIUpdate.h | 20 +- .../GameEngine/Source/Common/BitFlags.cpp | 468 ++++++------- .../GameEngine/Source/GameClient/Drawable.cpp | 1 + .../Source/GameLogic/Object/Object.cpp | 3 +- .../Update/AIUpdate/TeleporterAIUpdate.cpp | 246 ++++--- 7 files changed, 743 insertions(+), 624 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h b/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h index 9800fa6e664..621df2f0e77 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h @@ -1,313 +1,315 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// ModelState.h -// Basic data types needed for the game engine. This is an extension of BaseType.h. -// Author: Michael S. Booth, April 2001 - -#pragma once - -#ifndef _ModelState_H_ -#define _ModelState_H_ - -#include "Lib/BaseType.h" -#include "Common/INI.h" -#include "Common/BitFlags.h" -#include "Common/BitFlagsIO.h" - -//------------------------------------------------------------------------------------------------- - -/** - THE PROBLEM - ----------- - - -- there are lots of different states. (consider that structures can be: day/night, snow/nosnow, - powered/not, garrisoned/empty, damaged/not... you do the math.) - - -- some states are mutually exclusive (idle vs. moving), others are not (snow/nosnow, day/night). - generally, humanoid units have mostly the former, while structure units have mostly the latter. - The current ModelState system really only supports the mutually-exclusive states well. - - -- we'd rather not have to specify every state in the INI files, BUT we do want to be able - to intelligently choose the best model for a given state, whether or not a "real" state exists for it. - - -- it would be desirable to have a unified way of representing "ModelState" so that we don't - have multiple similar-yet-different systems. - - YUCK, WHAT NOW - -------------- - - Let's represent the Model State with two dictinct pieces: - - -- an "ActionState" piece, representing the mutually-exclusive states, which are almost - always an action of some sort - - -- and a "ConditionState" piece, which is a set of bitflags to indicate the static "condition" - of the model. - - Note that these are usually set independently in code, but they are lumped together in order to - determine the actual model to be used. - - (Let's require all objects would be required to have an "Idle" ActionState, which is the - normal, just-sitting there condition.) - - From a code point of view, this becomes an issue of requesting a certain state, and - finding the best-fit match for it. So, what are the rules for finding a good match? - - -- Action states must match exactly. If the desired action state is not found, then the - IDLE state is substituted (but this should generally be considered an error condition). - - -- Condition states choose the match with the closest match among the "Condition" bits in the - INI file, based on satisfying the most of the "required" conditions and the fewest of the - "forbidden" conditions. - -*/ - -#define NUM_MODELCONDITION_DOOR_STATES 4 - -//------------------------------------------------------------------------------------------------- -// IMPORTANT NOTE: you should endeavor to set up states such that the most "normal" -// state is defined by the bit being off. That is, the typical "normal" condition -// has all condition flags set to zero. -enum ModelConditionFlagType CPP_11(: Int) -{ - MODELCONDITION_INVALID = -1, - - MODELCONDITION_FIRST = 0, - -// -// Note: these values are saved in save files, so you MUST NOT REMOVE OR CHANGE -// existing values! -// - MODELCONDITION_TOPPLED = MODELCONDITION_FIRST, - MODELCONDITION_FRONTCRUSHED, - MODELCONDITION_BACKCRUSHED, - MODELCONDITION_DAMAGED, - MODELCONDITION_REALLY_DAMAGED, - MODELCONDITION_RUBBLE, - MODELCONDITION_SPECIAL_DAMAGED, - MODELCONDITION_NIGHT, - MODELCONDITION_SNOW, - MODELCONDITION_PARACHUTING, - MODELCONDITION_GARRISONED, - MODELCONDITION_ENEMYNEAR, - MODELCONDITION_WEAPONSET_VETERAN, - MODELCONDITION_WEAPONSET_ELITE, - MODELCONDITION_WEAPONSET_HERO, - MODELCONDITION_WEAPONSET_CRATEUPGRADE_ONE, - MODELCONDITION_WEAPONSET_CRATEUPGRADE_TWO, - MODELCONDITION_WEAPONSET_PLAYER_UPGRADE, - MODELCONDITION_DOOR_1_OPENING, - MODELCONDITION_DOOR_1_CLOSING, - MODELCONDITION_DOOR_1_WAITING_OPEN, - MODELCONDITION_DOOR_1_WAITING_TO_CLOSE, - MODELCONDITION_DOOR_2_OPENING, - MODELCONDITION_DOOR_2_CLOSING, - MODELCONDITION_DOOR_2_WAITING_OPEN, - MODELCONDITION_DOOR_2_WAITING_TO_CLOSE, - MODELCONDITION_DOOR_3_OPENING, - MODELCONDITION_DOOR_3_CLOSING, - MODELCONDITION_DOOR_3_WAITING_OPEN, - MODELCONDITION_DOOR_3_WAITING_TO_CLOSE, - MODELCONDITION_DOOR_4_OPENING, - MODELCONDITION_DOOR_4_CLOSING, - MODELCONDITION_DOOR_4_WAITING_OPEN, - MODELCONDITION_DOOR_4_WAITING_TO_CLOSE, - MODELCONDITION_ATTACKING, //Simply set when a unit is fighting -- terrorist moving with a target will flail arms like a psycho. - MODELCONDITION_PREATTACK_A, //Use for pre-attack animations (like aiming, pulling out a knife, or detonating explosives). - MODELCONDITION_FIRING_A, - MODELCONDITION_BETWEEN_FIRING_SHOTS_A, - MODELCONDITION_RELOADING_A, - MODELCONDITION_PREATTACK_B, //Use for pre-attack animations (like aiming, pulling out a knife, or detonating explosives). - MODELCONDITION_FIRING_B, - MODELCONDITION_BETWEEN_FIRING_SHOTS_B, - MODELCONDITION_RELOADING_B, - MODELCONDITION_PREATTACK_C, //Use for pre-attack animations (like aiming, pulling out a knife, or detonating explosives). - MODELCONDITION_FIRING_C, - MODELCONDITION_BETWEEN_FIRING_SHOTS_C, - MODELCONDITION_RELOADING_C, - MODELCONDITION_TURRET_ROTATE, - MODELCONDITION_POST_COLLAPSE, - MODELCONDITION_MOVING, - MODELCONDITION_DYING, - MODELCONDITION_AWAITING_CONSTRUCTION, - MODELCONDITION_PARTIALLY_CONSTRUCTED, - MODELCONDITION_ACTIVELY_BEING_CONSTRUCTED, - MODELCONDITION_PRONE, - MODELCONDITION_FREEFALL, - MODELCONDITION_ACTIVELY_CONSTRUCTING, - MODELCONDITION_CONSTRUCTION_COMPLETE, - MODELCONDITION_RADAR_EXTENDING, - MODELCONDITION_RADAR_UPGRADED, - MODELCONDITION_PANICKING, // yes, it's spelled with a "k". look it up. - MODELCONDITION_AFLAME, - MODELCONDITION_SMOLDERING, - MODELCONDITION_BURNED, - MODELCONDITION_DOCKING, ///< This encloses the whole time you are Entering, Actioning, and Exiting a dock - MODELCONDITION_DOCKING_BEGINNING, ///< From Enter to Action - MODELCONDITION_DOCKING_ACTIVE, ///< From Action to Exit - MODELCONDITION_DOCKING_ENDING, ///< Exit all the way to next enter (use only animations that end with this) - MODELCONDITION_CARRYING, - MODELCONDITION_FLOODED, - MODELCONDITION_LOADED, // loaded woot! ... like a transport is loaded - MODELCONDITION_JETAFTERBURNER,// shows "flames" for extra motive force (eg, when taking off) - MODELCONDITION_JETEXHAUST, // shows "exhaust" for motive force - MODELCONDITION_PACKING, // packs an object - MODELCONDITION_UNPACKING, // unpacks an object - MODELCONDITION_DEPLOYED, // a deployed object state - MODELCONDITION_OVER_WATER, // Units that can go over water want cool effects for doing so - MODELCONDITION_POWER_PLANT_UPGRADED, // to show special control rods on the cold fusion plant - MODELCONDITION_CLIMBING, //For units climbing up or down cliffs. - MODELCONDITION_SOLD, // object is being sold -#ifdef ALLOW_SURRENDER - MODELCONDITION_SURRENDER, //When units surrender... -#endif - MODELCONDITION_RAPPELLING, - MODELCONDITION_ARMED, // armed like a mine or bomb is armed (not like a human is armed) - MODELCONDITION_POWER_PLANT_UPGRADING, // while special control rods on the cold fusion plant are extending - - //Special model conditions work as following: - //Something turns it on... but a timer in the object will turn them off after a given - //amount of time. If you add any more special animations, then you'll need to add the - //code to turn off the state. - MODELCONDITION_SPECIAL_CHEERING, //When units do a victory cheer (or player initiated cheer). - - MODELCONDITION_CONTINUOUS_FIRE_SLOW, - MODELCONDITION_CONTINUOUS_FIRE_MEAN, - MODELCONDITION_CONTINUOUS_FIRE_FAST, - - MODELCONDITION_RAISING_FLAG, - MODELCONDITION_CAPTURED, - - MODELCONDITION_EXPLODED_FLAILING, - MODELCONDITION_EXPLODED_BOUNCING, - MODELCONDITION_SPLATTED, - - // this is an easier-to-use variant on the whole FIRING_A deal... - // these bits are set if firing, reloading, between shots, or preattack. - MODELCONDITION_USING_WEAPON_A, - MODELCONDITION_USING_WEAPON_B, - MODELCONDITION_USING_WEAPON_C, - - MODELCONDITION_PREORDER, - - MODELCONDITION_CENTER_TO_LEFT, - MODELCONDITION_LEFT_TO_CENTER, - MODELCONDITION_CENTER_TO_RIGHT, - MODELCONDITION_RIGHT_TO_CENTER, - - MODELCONDITION_RIDER1, //Added these for different riders - MODELCONDITION_RIDER2, - MODELCONDITION_RIDER3, - MODELCONDITION_RIDER4, - MODELCONDITION_RIDER5, - MODELCONDITION_RIDER6, - MODELCONDITION_RIDER7, - MODELCONDITION_RIDER8, - - MODELCONDITION_STUNNED_FLAILING, // Daniel Teh's idea, added by Lorenzen, 5/28/03 - MODELCONDITION_STUNNED, - MODELCONDITION_SECOND_LIFE, - MODELCONDITION_JAMMED, ///< Jammed as in missile jammed by ECM - MODELCONDITION_ARMORSET_CRATEUPGRADE_ONE, - MODELCONDITION_ARMORSET_CRATEUPGRADE_TWO, - - MODELCONDITION_USER_1, ///< Wildcard flag to use with upgrade modules or other random little things - MODELCONDITION_USER_2, - - MODELCONDITION_DISGUISED, - - // --- - // New Weaponsets - MODELCONDITION_WEAPONSET_PLAYER_UPGRADE2, - MODELCONDITION_WEAPONSET_PLAYER_UPGRADE3, - MODELCONDITION_WEAPONSET_PLAYER_UPGRADE4, - - // MODELCONDITION_WEAPONSET_CONTAINED, // for new Garrisoned and Contained weaponsets - // MODELCONDITION_WEAPONSET_GARRISONED, // somewhat obsolote since we are usually not visible when contained. - - - // New Weaponslots (4 to 8 -- D to H) - MODELCONDITION_PREATTACK_D, - MODELCONDITION_FIRING_D, - MODELCONDITION_BETWEEN_FIRING_SHOTS_D, - MODELCONDITION_RELOADING_D, - MODELCONDITION_USING_WEAPON_D, - - MODELCONDITION_PREATTACK_E, - MODELCONDITION_FIRING_E, - MODELCONDITION_BETWEEN_FIRING_SHOTS_E, - MODELCONDITION_RELOADING_E, - MODELCONDITION_USING_WEAPON_E, - - MODELCONDITION_PREATTACK_F, - MODELCONDITION_FIRING_F, - MODELCONDITION_BETWEEN_FIRING_SHOTS_F, - MODELCONDITION_RELOADING_F, - MODELCONDITION_USING_WEAPON_F, - - MODELCONDITION_PREATTACK_G, - MODELCONDITION_FIRING_G, - MODELCONDITION_BETWEEN_FIRING_SHOTS_G, - MODELCONDITION_RELOADING_G, - MODELCONDITION_USING_WEAPON_G, - - MODELCONDITION_PREATTACK_H, - MODELCONDITION_FIRING_H, - MODELCONDITION_BETWEEN_FIRING_SHOTS_H, - MODELCONDITION_RELOADING_H, - MODELCONDITION_USING_WEAPON_H, - - // VTOL - MODELCONDITION_TAKEOFF, - MODELCONDITION_LANDING, - - -// -// Note: these values are saved in save files, so you MUST NOT REMOVE OR CHANGE -// existing values! -// - - MODELCONDITION_COUNT // keep last! -}; - -//------------------------------------------------------------------------------------------------- - -typedef BitFlags ModelConditionFlags; - -#define MAKE_MODELCONDITION_MASK(k) ModelConditionFlags(ModelConditionFlags::kInit, (k)) -#define MAKE_MODELCONDITION_MASK2(k,a) ModelConditionFlags(ModelConditionFlags::kInit, (k), (a)) -#define MAKE_MODELCONDITION_MASK3(k,a,b) ModelConditionFlags(ModelConditionFlags::kInit, (k), (a), (b)) -#define MAKE_MODELCONDITION_MASK4(k,a,b,c) ModelConditionFlags(ModelConditionFlags::kInit, (k), (a), (b), (c)) -#define MAKE_MODELCONDITION_MASK5(k,a,b,c,d) ModelConditionFlags(ModelConditionFlags::kInit, (k), (a), (b), (c), (d)) -#define MAKE_MODELCONDITION_MASK12(a,b,c,d,e,f,g,h,i,j,k,l) ModelConditionFlags(ModelConditionFlags::kInit, (a), (b), (c), (d), (e), (f), (g), (h), (i), (j), (k), (l)) - -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- - -#endif // _ModelState_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// ModelState.h +// Basic data types needed for the game engine. This is an extension of BaseType.h. +// Author: Michael S. Booth, April 2001 + +#pragma once + +#ifndef _ModelState_H_ +#define _ModelState_H_ + +#include "Lib/BaseType.h" +#include "Common/INI.h" +#include "Common/BitFlags.h" +#include "Common/BitFlagsIO.h" + +//------------------------------------------------------------------------------------------------- + +/** + THE PROBLEM + ----------- + + -- there are lots of different states. (consider that structures can be: day/night, snow/nosnow, + powered/not, garrisoned/empty, damaged/not... you do the math.) + + -- some states are mutually exclusive (idle vs. moving), others are not (snow/nosnow, day/night). + generally, humanoid units have mostly the former, while structure units have mostly the latter. + The current ModelState system really only supports the mutually-exclusive states well. + + -- we'd rather not have to specify every state in the INI files, BUT we do want to be able + to intelligently choose the best model for a given state, whether or not a "real" state exists for it. + + -- it would be desirable to have a unified way of representing "ModelState" so that we don't + have multiple similar-yet-different systems. + + YUCK, WHAT NOW + -------------- + + Let's represent the Model State with two dictinct pieces: + + -- an "ActionState" piece, representing the mutually-exclusive states, which are almost + always an action of some sort + + -- and a "ConditionState" piece, which is a set of bitflags to indicate the static "condition" + of the model. + + Note that these are usually set independently in code, but they are lumped together in order to + determine the actual model to be used. + + (Let's require all objects would be required to have an "Idle" ActionState, which is the + normal, just-sitting there condition.) + + From a code point of view, this becomes an issue of requesting a certain state, and + finding the best-fit match for it. So, what are the rules for finding a good match? + + -- Action states must match exactly. If the desired action state is not found, then the + IDLE state is substituted (but this should generally be considered an error condition). + + -- Condition states choose the match with the closest match among the "Condition" bits in the + INI file, based on satisfying the most of the "required" conditions and the fewest of the + "forbidden" conditions. + +*/ + +#define NUM_MODELCONDITION_DOOR_STATES 4 + +//------------------------------------------------------------------------------------------------- +// IMPORTANT NOTE: you should endeavor to set up states such that the most "normal" +// state is defined by the bit being off. That is, the typical "normal" condition +// has all condition flags set to zero. +enum ModelConditionFlagType CPP_11(: Int) +{ + MODELCONDITION_INVALID = -1, + + MODELCONDITION_FIRST = 0, + +// +// Note: these values are saved in save files, so you MUST NOT REMOVE OR CHANGE +// existing values! +// + MODELCONDITION_TOPPLED = MODELCONDITION_FIRST, + MODELCONDITION_FRONTCRUSHED, + MODELCONDITION_BACKCRUSHED, + MODELCONDITION_DAMAGED, + MODELCONDITION_REALLY_DAMAGED, + MODELCONDITION_RUBBLE, + MODELCONDITION_SPECIAL_DAMAGED, + MODELCONDITION_NIGHT, + MODELCONDITION_SNOW, + MODELCONDITION_PARACHUTING, + MODELCONDITION_GARRISONED, + MODELCONDITION_ENEMYNEAR, + MODELCONDITION_WEAPONSET_VETERAN, + MODELCONDITION_WEAPONSET_ELITE, + MODELCONDITION_WEAPONSET_HERO, + MODELCONDITION_WEAPONSET_CRATEUPGRADE_ONE, + MODELCONDITION_WEAPONSET_CRATEUPGRADE_TWO, + MODELCONDITION_WEAPONSET_PLAYER_UPGRADE, + MODELCONDITION_DOOR_1_OPENING, + MODELCONDITION_DOOR_1_CLOSING, + MODELCONDITION_DOOR_1_WAITING_OPEN, + MODELCONDITION_DOOR_1_WAITING_TO_CLOSE, + MODELCONDITION_DOOR_2_OPENING, + MODELCONDITION_DOOR_2_CLOSING, + MODELCONDITION_DOOR_2_WAITING_OPEN, + MODELCONDITION_DOOR_2_WAITING_TO_CLOSE, + MODELCONDITION_DOOR_3_OPENING, + MODELCONDITION_DOOR_3_CLOSING, + MODELCONDITION_DOOR_3_WAITING_OPEN, + MODELCONDITION_DOOR_3_WAITING_TO_CLOSE, + MODELCONDITION_DOOR_4_OPENING, + MODELCONDITION_DOOR_4_CLOSING, + MODELCONDITION_DOOR_4_WAITING_OPEN, + MODELCONDITION_DOOR_4_WAITING_TO_CLOSE, + MODELCONDITION_ATTACKING, //Simply set when a unit is fighting -- terrorist moving with a target will flail arms like a psycho. + MODELCONDITION_PREATTACK_A, //Use for pre-attack animations (like aiming, pulling out a knife, or detonating explosives). + MODELCONDITION_FIRING_A, + MODELCONDITION_BETWEEN_FIRING_SHOTS_A, + MODELCONDITION_RELOADING_A, + MODELCONDITION_PREATTACK_B, //Use for pre-attack animations (like aiming, pulling out a knife, or detonating explosives). + MODELCONDITION_FIRING_B, + MODELCONDITION_BETWEEN_FIRING_SHOTS_B, + MODELCONDITION_RELOADING_B, + MODELCONDITION_PREATTACK_C, //Use for pre-attack animations (like aiming, pulling out a knife, or detonating explosives). + MODELCONDITION_FIRING_C, + MODELCONDITION_BETWEEN_FIRING_SHOTS_C, + MODELCONDITION_RELOADING_C, + MODELCONDITION_TURRET_ROTATE, + MODELCONDITION_POST_COLLAPSE, + MODELCONDITION_MOVING, + MODELCONDITION_DYING, + MODELCONDITION_AWAITING_CONSTRUCTION, + MODELCONDITION_PARTIALLY_CONSTRUCTED, + MODELCONDITION_ACTIVELY_BEING_CONSTRUCTED, + MODELCONDITION_PRONE, + MODELCONDITION_FREEFALL, + MODELCONDITION_ACTIVELY_CONSTRUCTING, + MODELCONDITION_CONSTRUCTION_COMPLETE, + MODELCONDITION_RADAR_EXTENDING, + MODELCONDITION_RADAR_UPGRADED, + MODELCONDITION_PANICKING, // yes, it's spelled with a "k". look it up. + MODELCONDITION_AFLAME, + MODELCONDITION_SMOLDERING, + MODELCONDITION_BURNED, + MODELCONDITION_DOCKING, ///< This encloses the whole time you are Entering, Actioning, and Exiting a dock + MODELCONDITION_DOCKING_BEGINNING, ///< From Enter to Action + MODELCONDITION_DOCKING_ACTIVE, ///< From Action to Exit + MODELCONDITION_DOCKING_ENDING, ///< Exit all the way to next enter (use only animations that end with this) + MODELCONDITION_CARRYING, + MODELCONDITION_FLOODED, + MODELCONDITION_LOADED, // loaded woot! ... like a transport is loaded + MODELCONDITION_JETAFTERBURNER,// shows "flames" for extra motive force (eg, when taking off) + MODELCONDITION_JETEXHAUST, // shows "exhaust" for motive force + MODELCONDITION_PACKING, // packs an object + MODELCONDITION_UNPACKING, // unpacks an object + MODELCONDITION_DEPLOYED, // a deployed object state + MODELCONDITION_OVER_WATER, // Units that can go over water want cool effects for doing so + MODELCONDITION_POWER_PLANT_UPGRADED, // to show special control rods on the cold fusion plant + MODELCONDITION_CLIMBING, //For units climbing up or down cliffs. + MODELCONDITION_SOLD, // object is being sold +#ifdef ALLOW_SURRENDER + MODELCONDITION_SURRENDER, //When units surrender... +#endif + MODELCONDITION_RAPPELLING, + MODELCONDITION_ARMED, // armed like a mine or bomb is armed (not like a human is armed) + MODELCONDITION_POWER_PLANT_UPGRADING, // while special control rods on the cold fusion plant are extending + + //Special model conditions work as following: + //Something turns it on... but a timer in the object will turn them off after a given + //amount of time. If you add any more special animations, then you'll need to add the + //code to turn off the state. + MODELCONDITION_SPECIAL_CHEERING, //When units do a victory cheer (or player initiated cheer). + + MODELCONDITION_CONTINUOUS_FIRE_SLOW, + MODELCONDITION_CONTINUOUS_FIRE_MEAN, + MODELCONDITION_CONTINUOUS_FIRE_FAST, + + MODELCONDITION_RAISING_FLAG, + MODELCONDITION_CAPTURED, + + MODELCONDITION_EXPLODED_FLAILING, + MODELCONDITION_EXPLODED_BOUNCING, + MODELCONDITION_SPLATTED, + + // this is an easier-to-use variant on the whole FIRING_A deal... + // these bits are set if firing, reloading, between shots, or preattack. + MODELCONDITION_USING_WEAPON_A, + MODELCONDITION_USING_WEAPON_B, + MODELCONDITION_USING_WEAPON_C, + + MODELCONDITION_PREORDER, + + MODELCONDITION_CENTER_TO_LEFT, + MODELCONDITION_LEFT_TO_CENTER, + MODELCONDITION_CENTER_TO_RIGHT, + MODELCONDITION_RIGHT_TO_CENTER, + + MODELCONDITION_RIDER1, //Added these for different riders + MODELCONDITION_RIDER2, + MODELCONDITION_RIDER3, + MODELCONDITION_RIDER4, + MODELCONDITION_RIDER5, + MODELCONDITION_RIDER6, + MODELCONDITION_RIDER7, + MODELCONDITION_RIDER8, + + MODELCONDITION_STUNNED_FLAILING, // Daniel Teh's idea, added by Lorenzen, 5/28/03 + MODELCONDITION_STUNNED, + MODELCONDITION_SECOND_LIFE, + MODELCONDITION_JAMMED, ///< Jammed as in missile jammed by ECM + MODELCONDITION_ARMORSET_CRATEUPGRADE_ONE, + MODELCONDITION_ARMORSET_CRATEUPGRADE_TWO, + + MODELCONDITION_USER_1, ///< Wildcard flag to use with upgrade modules or other random little things + MODELCONDITION_USER_2, + + MODELCONDITION_DISGUISED, + + // --- + // New Weaponsets + MODELCONDITION_WEAPONSET_PLAYER_UPGRADE2, + MODELCONDITION_WEAPONSET_PLAYER_UPGRADE3, + MODELCONDITION_WEAPONSET_PLAYER_UPGRADE4, + + // MODELCONDITION_WEAPONSET_CONTAINED, // for new Garrisoned and Contained weaponsets + // MODELCONDITION_WEAPONSET_GARRISONED, // somewhat obsolote since we are usually not visible when contained. + + + // New Weaponslots (4 to 8 -- D to H) + MODELCONDITION_PREATTACK_D, + MODELCONDITION_FIRING_D, + MODELCONDITION_BETWEEN_FIRING_SHOTS_D, + MODELCONDITION_RELOADING_D, + MODELCONDITION_USING_WEAPON_D, + + MODELCONDITION_PREATTACK_E, + MODELCONDITION_FIRING_E, + MODELCONDITION_BETWEEN_FIRING_SHOTS_E, + MODELCONDITION_RELOADING_E, + MODELCONDITION_USING_WEAPON_E, + + MODELCONDITION_PREATTACK_F, + MODELCONDITION_FIRING_F, + MODELCONDITION_BETWEEN_FIRING_SHOTS_F, + MODELCONDITION_RELOADING_F, + MODELCONDITION_USING_WEAPON_F, + + MODELCONDITION_PREATTACK_G, + MODELCONDITION_FIRING_G, + MODELCONDITION_BETWEEN_FIRING_SHOTS_G, + MODELCONDITION_RELOADING_G, + MODELCONDITION_USING_WEAPON_G, + + MODELCONDITION_PREATTACK_H, + MODELCONDITION_FIRING_H, + MODELCONDITION_BETWEEN_FIRING_SHOTS_H, + MODELCONDITION_RELOADING_H, + MODELCONDITION_USING_WEAPON_H, + + // VTOL + MODELCONDITION_TAKEOFF, + MODELCONDITION_LANDING, + + // Teleporter / Chrono Legionnaire + MODELCONDITION_TELEPORT_RECOVER, + +// +// Note: these values are saved in save files, so you MUST NOT REMOVE OR CHANGE +// existing values! +// + + MODELCONDITION_COUNT // keep last! +}; + +//------------------------------------------------------------------------------------------------- + +typedef BitFlags ModelConditionFlags; + +#define MAKE_MODELCONDITION_MASK(k) ModelConditionFlags(ModelConditionFlags::kInit, (k)) +#define MAKE_MODELCONDITION_MASK2(k,a) ModelConditionFlags(ModelConditionFlags::kInit, (k), (a)) +#define MAKE_MODELCONDITION_MASK3(k,a,b) ModelConditionFlags(ModelConditionFlags::kInit, (k), (a), (b)) +#define MAKE_MODELCONDITION_MASK4(k,a,b,c) ModelConditionFlags(ModelConditionFlags::kInit, (k), (a), (b), (c)) +#define MAKE_MODELCONDITION_MASK5(k,a,b,c,d) ModelConditionFlags(ModelConditionFlags::kInit, (k), (a), (b), (c), (d)) +#define MAKE_MODELCONDITION_MASK12(a,b,c,d,e,f,g,h,i,j,k,l) ModelConditionFlags(ModelConditionFlags::kInit, (a), (b), (c), (d), (e), (f), (g), (h), (i), (j), (k), (l)) + +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- + +#endif // _ModelState_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/TintStatus.h b/GeneralsMD/Code/GameEngine/Include/GameClient/TintStatus.h index 7d84c4b578e..e4bb7bf1f8e 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/TintStatus.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/TintStatus.h @@ -23,6 +23,7 @@ enum TintStatus CPP_11(: Int) TINT_STATUS_SHIELDED, ///< When shielded, we tint SHIELDED_COLOR TINT_STATUS_DEMORALIZED, TINT_STATUS_BOOST, + TINT_STATUS_TELEPORT_RECOVER, ///< (Chrono Legionnaire -> recover from teleport) TINT_STATUS_EXTRA1, TINT_STATUS_EXTRA2, TINT_STATUS_EXTRA3, diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleporterAIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleporterAIUpdate.h index 12e79218f38..15b8de527fb 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleporterAIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleporterAIUpdate.h @@ -45,6 +45,14 @@ class TeleporterAIUpdateModuleData : public AIUpdateModuleData const FXList* m_sourceFX; const FXList* m_targetFX; + const FXList* m_recoverEndFX; + + AudioEventRTS m_recoverSoundLoop; + + TintStatus m_tintStatus; ///< tint color to apply when recovering from teleport + + Real m_opacityStart; + Real m_opacityEnd; TeleporterAIUpdateModuleData(); @@ -74,6 +82,9 @@ class TeleporterAIUpdate : public AIUpdateInterface virtual UpdateSleepTime update(); + /// this is never disabled, since we want disabled things to continue recovering from teleport + virtual DisabledMaskType getDisabledTypesToProcess() const { return DISABLEDMASK_ALL; } + protected: UpdateSleepTime doTeleport(Coord3D targetPos, Real angle, Real dist); @@ -100,9 +111,14 @@ class TeleporterAIUpdate : public AIUpdateInterface virtual AIStateMachine* makeStateMachine(); -//private: -// Bool m_inAttackPos; +private: + void applyRecoverEffects(Real dist); + void removeRecoverEffects(void); + AudioEventRTS m_recoverSoundLoop; ///< Audio to play during recovering + UnsignedInt m_disabledUntil; ///< frame we are done recovering + UnsignedInt m_disabledStart; ///< frame we have started recovering + bool m_isDisabled; ///< current recovering status }; #endif diff --git a/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp b/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp index 46f55c48a34..f515d93e9bf 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp @@ -1,233 +1,235 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: BitFlags.cpp /////////////////////////////////////////////////////////// -// -// Used to set detail levels of various game systems. -// Steven Johnson, Sept 2002 -// -// -/////////////////////////////////////////////////////////////////////////////// - -#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine - -#include "Common/BitFlags.h" -#include "Common/BitFlagsIO.h" -#include "Common/ModelState.h" -#include "GameLogic/ArmorSet.h" - -const char* ModelConditionFlags::s_bitNameList[] = -{ - "TOPPLED", - "FRONTCRUSHED", - "BACKCRUSHED", - "DAMAGED", - "REALLYDAMAGED", - "RUBBLE", - "SPECIAL_DAMAGED", - "NIGHT", - "SNOW", - "PARACHUTING", - "GARRISONED", - "ENEMYNEAR", - "WEAPONSET_VETERAN", - "WEAPONSET_ELITE", - "WEAPONSET_HERO", - "WEAPONSET_CRATEUPGRADE_ONE", - "WEAPONSET_CRATEUPGRADE_TWO", - "WEAPONSET_PLAYER_UPGRADE", - "DOOR_1_OPENING", - "DOOR_1_CLOSING", - "DOOR_1_WAITING_OPEN", - "DOOR_1_WAITING_TO_CLOSE", - "DOOR_2_OPENING", - "DOOR_2_CLOSING", - "DOOR_2_WAITING_OPEN", - "DOOR_2_WAITING_TO_CLOSE", - "DOOR_3_OPENING", - "DOOR_3_CLOSING", - "DOOR_3_WAITING_OPEN", - "DOOR_3_WAITING_TO_CLOSE", - "DOOR_4_OPENING", - "DOOR_4_CLOSING", - "DOOR_4_WAITING_OPEN", - "DOOR_4_WAITING_TO_CLOSE", - "ATTACKING", - "PREATTACK_A", - "FIRING_A", - "BETWEEN_FIRING_SHOTS_A", - "RELOADING_A", - "PREATTACK_B", - "FIRING_B", - "BETWEEN_FIRING_SHOTS_B", - "RELOADING_B", - "PREATTACK_C", - "FIRING_C", - "BETWEEN_FIRING_SHOTS_C", - "RELOADING_C", - "TURRET_ROTATE", - "POST_COLLAPSE", - "MOVING", - "DYING", - "AWAITING_CONSTRUCTION", - "PARTIALLY_CONSTRUCTED", - "ACTIVELY_BEING_CONSTRUCTED", - "PRONE", - "FREEFALL", - "ACTIVELY_CONSTRUCTING", - "CONSTRUCTION_COMPLETE", - "RADAR_EXTENDING", - "RADAR_UPGRADED", - "PANICKING", // yes, it's spelled with a "k". look it up. - "AFLAME", - "SMOLDERING", - "BURNED", - "DOCKING", - "DOCKING_BEGINNING", - "DOCKING_ACTIVE", - "DOCKING_ENDING", - "CARRYING", - "FLOODED", - "LOADED", - "JETAFTERBURNER", - "JETEXHAUST", - "PACKING", - "UNPACKING", - "DEPLOYED", - "OVER_WATER", - "POWER_PLANT_UPGRADED", - "CLIMBING", - "SOLD", -#ifdef ALLOW_SURRENDER - "SURRENDER", -#endif - "RAPPELLING", - "ARMED", - "POWER_PLANT_UPGRADING", - - "SPECIAL_CHEERING", - - "CONTINUOUS_FIRE_SLOW", - "CONTINUOUS_FIRE_MEAN", - "CONTINUOUS_FIRE_FAST", - - "RAISING_FLAG", - "CAPTURED", - - "EXPLODED_FLAILING", - "EXPLODED_BOUNCING", - "SPLATTED", - - "USING_WEAPON_A", - "USING_WEAPON_B", - "USING_WEAPON_C", - - "PREORDER", - - "CENTER_TO_LEFT", - "LEFT_TO_CENTER", - "CENTER_TO_RIGHT", - "RIGHT_TO_CENTER", - - "RIDER1", //Kris: Added these for different combat-bike riders, but feel free to use these for anything. - "RIDER2", - "RIDER3", - "RIDER4", - "RIDER5", - "RIDER6", - "RIDER7", - "RIDER8", - - "STUNNED_FLAILING", // Daniel Teh's idea, added by Lorenzen, 5/28/03 - "STUNNED", - "SECOND_LIFE", - "JAMMED", - "ARMORSET_CRATEUPGRADE_ONE", - "ARMORSET_CRATEUPGRADE_TWO", - - "USER_1", - "USER_2", - - "DISGUISED", - - // New Weaponsets - "WEAPONSET_PLAYER_UPGRADE2", - "WEAPONSET_PLAYER_UPGRADE3", - "WEAPONSET_PLAYER_UPGRADE4", - - // New Weaponslots (D-H) - - "PREATTACK_D", - "FIRING_D", - "BETWEEN_FIRING_SHOTS_D", - "RELOADING_D", - "USING_WEAPON_D", - - "PREATTACK_E", - "FIRING_E", - "BETWEEN_FIRING_SHOTS_E", - "RELOADING_E", - "USING_WEAPON_E", - - "PREATTACK_F", - "FIRING_F", - "BETWEEN_FIRING_SHOTS_F", - "RELOADING_F", - "USING_WEAPON_F", - - "PREATTACK_G", - "FIRING_G", - "BETWEEN_FIRING_SHOTS_G", - "RELOADING_G", - "USING_WEAPON_G", - - "PREATTACK_H", - "FIRING_H", - "BETWEEN_FIRING_SHOTS_H", - "RELOADING_H", - "USING_WEAPON_H", - - "TAKEOFF", - "LANDING", - - NULL -}; - -const char* ArmorSetFlags::s_bitNameList[] = -{ - "VETERAN", - "ELITE", - "HERO", - "PLAYER_UPGRADE", - "WEAK_VERSUS_BASEDEFENSES", - "SECOND_LIFE", - "CRATE_UPGRADE_ONE", - "CRATE_UPGRADE_TWO", - "PLAYER_UPGRADE2", - "PLAYER_UPGRADE3", - "PLAYER_UPGRADE4", - - NULL -}; - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: BitFlags.cpp /////////////////////////////////////////////////////////// +// +// Used to set detail levels of various game systems. +// Steven Johnson, Sept 2002 +// +// +/////////////////////////////////////////////////////////////////////////////// + +#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine + +#include "Common/BitFlags.h" +#include "Common/BitFlagsIO.h" +#include "Common/ModelState.h" +#include "GameLogic/ArmorSet.h" + +const char* ModelConditionFlags::s_bitNameList[] = +{ + "TOPPLED", + "FRONTCRUSHED", + "BACKCRUSHED", + "DAMAGED", + "REALLYDAMAGED", + "RUBBLE", + "SPECIAL_DAMAGED", + "NIGHT", + "SNOW", + "PARACHUTING", + "GARRISONED", + "ENEMYNEAR", + "WEAPONSET_VETERAN", + "WEAPONSET_ELITE", + "WEAPONSET_HERO", + "WEAPONSET_CRATEUPGRADE_ONE", + "WEAPONSET_CRATEUPGRADE_TWO", + "WEAPONSET_PLAYER_UPGRADE", + "DOOR_1_OPENING", + "DOOR_1_CLOSING", + "DOOR_1_WAITING_OPEN", + "DOOR_1_WAITING_TO_CLOSE", + "DOOR_2_OPENING", + "DOOR_2_CLOSING", + "DOOR_2_WAITING_OPEN", + "DOOR_2_WAITING_TO_CLOSE", + "DOOR_3_OPENING", + "DOOR_3_CLOSING", + "DOOR_3_WAITING_OPEN", + "DOOR_3_WAITING_TO_CLOSE", + "DOOR_4_OPENING", + "DOOR_4_CLOSING", + "DOOR_4_WAITING_OPEN", + "DOOR_4_WAITING_TO_CLOSE", + "ATTACKING", + "PREATTACK_A", + "FIRING_A", + "BETWEEN_FIRING_SHOTS_A", + "RELOADING_A", + "PREATTACK_B", + "FIRING_B", + "BETWEEN_FIRING_SHOTS_B", + "RELOADING_B", + "PREATTACK_C", + "FIRING_C", + "BETWEEN_FIRING_SHOTS_C", + "RELOADING_C", + "TURRET_ROTATE", + "POST_COLLAPSE", + "MOVING", + "DYING", + "AWAITING_CONSTRUCTION", + "PARTIALLY_CONSTRUCTED", + "ACTIVELY_BEING_CONSTRUCTED", + "PRONE", + "FREEFALL", + "ACTIVELY_CONSTRUCTING", + "CONSTRUCTION_COMPLETE", + "RADAR_EXTENDING", + "RADAR_UPGRADED", + "PANICKING", // yes, it's spelled with a "k". look it up. + "AFLAME", + "SMOLDERING", + "BURNED", + "DOCKING", + "DOCKING_BEGINNING", + "DOCKING_ACTIVE", + "DOCKING_ENDING", + "CARRYING", + "FLOODED", + "LOADED", + "JETAFTERBURNER", + "JETEXHAUST", + "PACKING", + "UNPACKING", + "DEPLOYED", + "OVER_WATER", + "POWER_PLANT_UPGRADED", + "CLIMBING", + "SOLD", +#ifdef ALLOW_SURRENDER + "SURRENDER", +#endif + "RAPPELLING", + "ARMED", + "POWER_PLANT_UPGRADING", + + "SPECIAL_CHEERING", + + "CONTINUOUS_FIRE_SLOW", + "CONTINUOUS_FIRE_MEAN", + "CONTINUOUS_FIRE_FAST", + + "RAISING_FLAG", + "CAPTURED", + + "EXPLODED_FLAILING", + "EXPLODED_BOUNCING", + "SPLATTED", + + "USING_WEAPON_A", + "USING_WEAPON_B", + "USING_WEAPON_C", + + "PREORDER", + + "CENTER_TO_LEFT", + "LEFT_TO_CENTER", + "CENTER_TO_RIGHT", + "RIGHT_TO_CENTER", + + "RIDER1", //Kris: Added these for different combat-bike riders, but feel free to use these for anything. + "RIDER2", + "RIDER3", + "RIDER4", + "RIDER5", + "RIDER6", + "RIDER7", + "RIDER8", + + "STUNNED_FLAILING", // Daniel Teh's idea, added by Lorenzen, 5/28/03 + "STUNNED", + "SECOND_LIFE", + "JAMMED", + "ARMORSET_CRATEUPGRADE_ONE", + "ARMORSET_CRATEUPGRADE_TWO", + + "USER_1", + "USER_2", + + "DISGUISED", + + // New Weaponsets + "WEAPONSET_PLAYER_UPGRADE2", + "WEAPONSET_PLAYER_UPGRADE3", + "WEAPONSET_PLAYER_UPGRADE4", + + // New Weaponslots (D-H) + + "PREATTACK_D", + "FIRING_D", + "BETWEEN_FIRING_SHOTS_D", + "RELOADING_D", + "USING_WEAPON_D", + + "PREATTACK_E", + "FIRING_E", + "BETWEEN_FIRING_SHOTS_E", + "RELOADING_E", + "USING_WEAPON_E", + + "PREATTACK_F", + "FIRING_F", + "BETWEEN_FIRING_SHOTS_F", + "RELOADING_F", + "USING_WEAPON_F", + + "PREATTACK_G", + "FIRING_G", + "BETWEEN_FIRING_SHOTS_G", + "RELOADING_G", + "USING_WEAPON_G", + + "PREATTACK_H", + "FIRING_H", + "BETWEEN_FIRING_SHOTS_H", + "RELOADING_H", + "USING_WEAPON_H", + + "TAKEOFF", + "LANDING", + + "TELEPORT_RECOVER", + + NULL +}; + +const char* ArmorSetFlags::s_bitNameList[] = +{ + "VETERAN", + "ELITE", + "HERO", + "PLAYER_UPGRADE", + "WEAK_VERSUS_BASEDEFENSES", + "SECOND_LIFE", + "CRATE_UPGRADE_ONE", + "CRATE_UPGRADE_TWO", + "PLAYER_UPGRADE2", + "PLAYER_UPGRADE3", + "PLAYER_UPGRADE4", + + NULL +}; + diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index fb01a2859ac..14b1426a580 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -128,6 +128,7 @@ const char* TintStatusFlags::s_bitNameList[] = "SHIELDED", "DEMORALIZED", "BOOST", + "TELEPORT_RECOVER", "EXTRA1", "EXTRA2", "EXTRA3", diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 050f74d558c..ce7f2843c6b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -2209,7 +2209,7 @@ void Object::setDisabledUntil( DisabledType type, UnsignedInt frame ) // Doh. Also shouldn't be tinting when disabled by scripting. // Doh^2. Also shouldn't be CLEARING tinting if we're disabling by held or script disabledness // Doh^3. Unmanned is no tint too - if( type != DISABLED_HELD && type != DISABLED_SCRIPT_DISABLED && type != DISABLED_UNMANNED ) + if( type != DISABLED_HELD && type != DISABLED_SCRIPT_DISABLED && type != DISABLED_UNMANNED && type != DISABLED_TELEPORT) { m_drawable->setTintStatus( TINT_STATUS_DISABLED ); } @@ -2385,6 +2385,7 @@ Bool Object::clearDisabled( DisabledType type ) exceptions.set(DISABLED_HELD); exceptions.set(DISABLED_SCRIPT_DISABLED); exceptions.set(DISABLED_UNMANNED); + exceptions.set(DISABLED_TELEPORT); DisabledMaskType myFlagsMinusExceptions = getDisabledFlags(); myFlagsMinusExceptions.clearAndSet(exceptions, DISABLEDMASK_NONE); 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 bf33e5f1b14..76737f7c0e0 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp @@ -28,11 +28,13 @@ #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine +#include "Common/GameAudio.h" #include "Common/RandomValue.h" #include "GameLogic/Module/TeleporterAIUpdate.h" #include "GameLogic/Object.h" #include "Common/Xfer.h" #include "Common/DisabledTypes.h" +#include "Common/ModelState.h" #include "GameClient/Drawable.h" #include "GameClient/FXList.h" #include "GameLogic/AI.h" @@ -44,6 +46,7 @@ #include "GameLogic/Weapon.h" #include "GameLogic/PartitionManager.h" #include "GameLogic/TerrainLogic.h" +#include "GameClient/TintStatus.h" //------------------------------------------------------------------------------------------------- @@ -51,6 +54,10 @@ TeleporterAIUpdateModuleData::TeleporterAIUpdateModuleData( void ) { m_sourceFX = NULL; m_targetFX = NULL; + m_recoverEndFX = NULL; + m_tintStatus = TINT_STATUS_INVALID; + m_opacityStart = 1.0; + m_opacityEnd = 1.0; } //------------------------------------------------------------------------------------------------- @@ -63,7 +70,12 @@ TeleporterAIUpdateModuleData::TeleporterAIUpdateModuleData( void ) { "MinDistanceForTeleport", INI::parseReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_minDistance) }, { "DisabledDurationPerDistance", INI::parseDurationReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_disabledDuration) }, { "TeleportStartFX", INI::parseFXList, NULL, offsetof(TeleporterAIUpdateModuleData, m_sourceFX) }, - { "TeleportEndFX", INI::parseFXList, NULL, offsetof(TeleporterAIUpdateModuleData, m_targetFX) }, + { "TeleportTargetFX", INI::parseFXList, NULL, offsetof(TeleporterAIUpdateModuleData, m_targetFX) }, + { "TeleportRecoverEndFX", INI::parseFXList, NULL, offsetof(TeleporterAIUpdateModuleData, m_recoverEndFX) }, + { "TeleportRecoverSoundAmbient", INI::parseAudioEventRTS, NULL, offsetof(TeleporterAIUpdateModuleData, m_recoverSoundLoop) }, + { "TeleportRecoverTint", TintStatusFlags::parseSingleBitFromINI, NULL, offsetof(TeleporterAIUpdateModuleData, m_tintStatus) }, + { "TeleportRecoverOpacityStart", INI::parseReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_opacityStart) }, + { "TeleportRecoverOpacityEnd", INI::parseReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_opacityEnd) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); @@ -79,7 +91,9 @@ AIStateMachine* TeleporterAIUpdate::makeStateMachine() //------------------------------------------------------------------------------------------------- TeleporterAIUpdate::TeleporterAIUpdate( Thing *thing, const ModuleData* moduleData ) : AIUpdateInterface( thing, moduleData ) { - //m_inAttackPos = FALSE; + m_disabledUntil = 0; + m_disabledStart = 0; + m_isDisabled = false; } //------------------------------------------------------------------------------------------------- @@ -89,25 +103,104 @@ TeleporterAIUpdate::~TeleporterAIUpdate( void ) } //------------------------------------------------------------------------------------------------- -UpdateSleepTime TeleporterAIUpdate::update( void ) +UpdateSleepTime TeleporterAIUpdate::update(void) { - //// If I'm standing still, move somewhere - //if (isIdle()) - //{ - // Coord3D dest = *(getObject()->getPosition()); - // dest.x += GameLogicRandomValue( 5, 50 ); - // dest.y += GameLogicRandomValue( 5, 50 ); - // aiMoveToPosition( &dest, CMD_FROM_AI ); - //} + const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); + Object* obj = getObject(); + + //UpdateSleepTime ret = UPDATE_SLEEP_FOREVER; + + UnsignedInt now = TheGameLogic->getFrame(); + + if (m_isDisabled) { + if (m_disabledUntil > now) { + // We are currently disabled + Real progress = __max(__min(INT_TO_REAL(now - m_disabledStart) / INT_TO_REAL(m_disabledUntil - m_disabledStart), 1.0), 0.0); + + Drawable* drw = obj->getDrawable(); + if (drw) + { + // - set opacity + if (d->m_opacityStart < 1.0f || d->m_opacityEnd < 1.0f) { + Real curOpacity = (1.0 - progress) * d->m_opacityStart + progress * d->m_opacityEnd; + // DEBUG_LOG((">>> TPAI Update: opacity = %f\n", curOpacity)); + drw->setDrawableOpacity(curOpacity); + } + } + // We actually need to stop here, because the default update would allow us to attack while disabled + return UPDATE_SLEEP_NONE; + //ret = UPDATE_SLEEP_NONE; + } + else { + // We are done + removeRecoverEffects(); + m_isDisabled = false; + } + } // extend - UpdateSleepTime ret = AIUpdateInterface::update(); - //return (mine < ret) ? mine : ret; - /// @todo srj -- someday, make sleepy. for now, must not sleep. - return ret; // UPDATE_SLEEP_NONE; + // UpdateSleepTime ret2 = AIUpdateInterface::update(); + // return (ret < ret2) ? ret : ret2; + + return AIUpdateInterface::update(); + } // end update +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void TeleporterAIUpdate::applyRecoverEffects(Real dist) +{ + const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); + Object* obj = getObject(); + + // - set conditionstate + obj->setModelConditionState(MODELCONDITION_TELEPORT_RECOVER); + + // - add ambient sound + m_recoverSoundLoop = d->m_recoverSoundLoop; + m_recoverSoundLoop.setObjectID(obj->getID()); + m_recoverSoundLoop.setPlayingHandle(TheAudio->addAudioEvent(&m_recoverSoundLoop)); + + Drawable* drw = obj->getDrawable(); + if (drw) + { + // - set color tint + if (d->m_tintStatus > TINT_STATUS_INVALID && d->m_tintStatus < TINT_STATUS_COUNT) + { + drw->setTintStatus(d->m_tintStatus); + } + + // - set opacity + if (d->m_opacityStart < 1.0 || d->m_opacityEnd < 1.0) { + drw->setEffectiveOpacity(1.0); + } + } + +} + +// ------------------------------------------------------------------------------------------------ +void TeleporterAIUpdate::removeRecoverEffects() +{ + const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); + Object* obj = getObject(); + + obj->clearModelConditionState(MODELCONDITION_TELEPORT_RECOVER); + + TheAudio->removeAudioEvent(m_recoverSoundLoop.getPlayingHandle()); + + Drawable* drw = obj->getDrawable(); + if (drw) + { + // - clear color tint + if (d->m_tintStatus > TINT_STATUS_INVALID && d->m_tintStatus < TINT_STATUS_COUNT) + { + drw->clearTintStatus(d->m_tintStatus); + } + } + + FXList::doFXObj(d->m_recoverEndFX, getObject()); +} // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ @@ -118,24 +211,28 @@ UpdateSleepTime TeleporterAIUpdate::doTeleport(Coord3D targetPos, Real angle, Re FXList::doFXObj(d->m_sourceFX, getObject()); - //TODO: Handle line of sight?! - obj->setPosition(&targetPos); obj->setOrientation(angle); FXList::doFXObj(d->m_targetFX, getObject()); destroyPath(); - //friend_endingMove(); + TheAI->pathfinder()->updateGoal(obj, &targetPos, TheTerrainLogic->getLayerForDestination(&targetPos)); - // setLocomotorGoalPositionExplicit(targetPos); setLocomotorGoalOrientation(angle); UnsignedInt disabledFrames = REAL_TO_UNSIGNEDINT(dist * d->m_disabledDuration); - obj->setDisabledUntil(DISABLED_TELEPORT, TheGameLogic->getFrame() + disabledFrames); + m_disabledStart = TheGameLogic->getFrame(); + m_disabledUntil = m_disabledStart + disabledFrames; + + m_isDisabled = true; + obj->setDisabledUntil(DISABLED_TELEPORT, m_disabledUntil); + + applyRecoverEffects(dist); - return UPDATE_SLEEP(disabledFrames); + // return UPDATE_SLEEP(disabledFrames); + return UPDATE_SLEEP_NONE; // We can't actually sleep since we need to adjust some things dynamically } @@ -150,7 +247,7 @@ Bool TeleporterAIUpdate::isLocationValid(Object* obj, const Coord3D* targetPos, return !viewBlocked && inRange && posValid; } - +//------------------------------------------------------------------------------------------------- Bool TeleporterAIUpdate::findAttackLocation(Object* victim, const Coord3D* victimPos, Coord3D* targetPos, Real* targetAngle) { Object* obj = getObject(); @@ -166,37 +263,24 @@ Bool TeleporterAIUpdate::findAttackLocation(Object* victim, const Coord3D* victi // Check if the current location is valid. // This needs to be rechecked after the disabled timer. if (isLocationValid(obj, targetPos, victim, victimPos, weap)) { - - // After verifying the initial location - //if (!m_inAttackPos) { // Only adjust before a teleport - if (!TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &newPos)) { - DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination failed!\n")); - } - else { - DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); - } - - if (isLocationValid(obj, &newPos, victim, victimPos, weap)) { - DEBUG_LOG((">>> TPAI - findAttackLocation: done with initial pos\n")); - *targetPos = newPos; - return true; - } - //} + if (!TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &newPos)) { + DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination failed!\n")); + } //else { - // DEBUG_LOG((">>> TPAI - findAttackLocation: done with initial pos\n")); - // *targetPos = newPos; - // return true; + // DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); //} + + if (isLocationValid(obj, &newPos, victim, victimPos, weap)) { + // DEBUG_LOG((">>> TPAI - findAttackLocation: done with initial pos\n")); + *targetPos = newPos; + return true; + } } newPos.x = targetPos->x; newPos.y = targetPos->y; newPos.z = targetPos->z; - //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); Real RANGE_MARGIN = 10.0f; @@ -221,21 +305,6 @@ Bool TeleporterAIUpdate::findAttackLocation(Object* victim, const Coord3D* victi Real initAngle = atan2(direction.y, direction.x); // angle from victim to target direction.normalize(); - //distance = sqrt(distSq); - - //if (distance > 0) { - // direction.x = (targetPos->x - targetPos->x) / distance; - // direction.y = (targetPos->y - targetPos->y) / distance; - //} - //else { - // // if we are directly at the target, but are not actually in range, something went wrong - // return false; - //} - - // DEBUG: - /*const FXList* debug_fx1 = TheFXListStore->findFXList("FX_DEBUG_MARKER_GREEN"); - const FXList* debug_fx2 = TheFXListStore->findFXList("FX_DEBUG_MARKER_RED");*/ - const Real maxAngle = deg2rad(180.0f); const Real step_size_angle = deg2rad(10.0f); @@ -244,7 +313,7 @@ Bool TeleporterAIUpdate::findAttackLocation(Object* victim, const Coord3D* victi const int max_rings = REAL_TO_INT(range / step_size_length); const int max_steps = REAL_TO_INT(maxAngle / step_size_angle); - DEBUG_LOG((">>> TPAI - findAttackLocation: range = %f, max_rings = %d\n", range, max_rings)); + // DEBUG_LOG((">>> TPAI - findAttackLocation: range = %f, max_rings = %d\n", range, max_rings)); for (int ring = 0; ring < max_rings; ++ring) { Real radius = maxRange - (ring * step_size_length); @@ -263,15 +332,15 @@ Bool TeleporterAIUpdate::findAttackLocation(Object* victim, const Coord3D* victi //destinationLayer = TheTerrainLogic->getLayerForDestination(&newPos); //posValid = TheAI->pathfinder()->validMovementPosition(getObject()->getCrusherLevel() > 0, destinationLayer, getLocomotorSet(), &newPos); - DEBUG_LOG((">>> TPAI - findAttackLocation: candidate Pos: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); + // DEBUG_LOG((">>> TPAI - findAttackLocation: candidate Pos: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); // TheAI->pathfinder()->adjustTargetDestination(obj, victim, victimPos, weap, &newPos); if (!TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &newPos)) { DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination failed!\n")); } - else { - DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); - } + //else { + // DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); + //} /*if (sign == 1) FXList::doFXPos(debug_fx1, &newPos); @@ -281,7 +350,7 @@ Bool TeleporterAIUpdate::findAttackLocation(Object* victim, const Coord3D* victi if (isLocationValid(obj, &newPos, victim, victimPos, weap)) { *targetPos = newPos; *targetAngle = angle + PI; - DEBUG_LOG((">>> TPAI - findAttackLocation: done after ring=%d, step=%d\n", ring, step)); + //DEBUG_LOG((">>> TPAI - findAttackLocation: done after ring=%d, step=%d\n", ring, step)); return true; } @@ -374,7 +443,31 @@ UpdateSleepTime TeleporterAIUpdate::doLocomotor(void) } if (getStateMachine()->getCurrentStateID() == AI_ENTER) { + // If we want to enter and got this close, we just move normally requiredRange = 15.0f; + //} else if (getStateMachine()->getCurrentStateID() == AI_DOCK) { + // // Get the dock's approach position. + // // If we are at least X distance away, teleport, otherwise, do normal movement + // DockUpdateInterface* dock = goalObj->getDockUpdateInterface(); + // if (dock != NULL) { + // int dockIndex; // we don't really need this + // Bool reserved = dock->reserveApproachPosition(obj, &targetPos, &dockIndex); + // if (reserved) { + // // Get dist to goal obj center + // Real distSqObj = ThePartitionManager->getDistanceSquared(obj, goalObj, FROM_CENTER_2D, &dir); + // // Get dist to approach pos + // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + + // DEBUG_LOG((">>> TPAI: DOCK distSq = %f, distSqObj = %f\n", distSq, distSqObj)); + + // // If we are close to both the approach pos and the center pos, move normally + // Real minDistSq = 25.0f * 25.0f; + // if (distSqObj < minDistSq && distSqObj < minDistSq) { + // return AIUpdateInterface::doLocomotor(); + // } + // // otherwise teleport + // } + // } } DEBUG_LOG((">>> TPAI - doLoc: LocomotorGoalType = %d, AI STATE = %s (%d)\n", getLocomotorGoalType(), getStateMachine()->getCurrentStateName(), getStateMachine()->getCurrentStateID())); @@ -436,9 +529,10 @@ UpdateSleepTime TeleporterAIUpdate::doLocomotor(void) //else if( /*use special power?*/) { // //same as with attacks, try to get into range //} - else if (getStateMachine()->getCurrentStateID() == AI_ENTER) { + // else if (getStateMachine()->getCurrentStateID() == AI_ENTER || getStateMachine()->getCurrentStateID() == AI_ENTER) { + else if (goalObj != NULL) { // We need to correct the position to the outer bounding box of the structure - //Adjust target to required distance + // TODO: Respect actual geometry, not just radius requiredRange = goalObj->getGeometryInfo().getBoundingCircleRadius(); if (requiredRange > 0) { dir.scale(min(dist, requiredRange)); @@ -449,23 +543,22 @@ UpdateSleepTime TeleporterAIUpdate::doLocomotor(void) //recompute distance and angle distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - targetAngle = atan2(dir.y, dir.x); + + // targetAngle = atan2(dir.y, dir.x); + targetAngle = atan2(goalPos->y - targetPos.y, goalPos->x - targetPos.x); dist = sqrt(distSq); } else { + // TODO: if this doesn't find a location, TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &targetPos); //recompute distance and angle distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); targetAngle = atan2(dir.y, dir.x); dist = sqrt(distSq); - - //TODO: if we target an object, have the angle changed to look at the object } - - - DEBUG_LOG((">>> TPAI - doLoc: teleport with dist = %f\n", dist)); + // DEBUG_LOG((">>> TPAI - doLoc: teleport with dist = %f\n", dist)); doTeleport(targetPos, targetAngle, dist); return AIUpdateInterface::doLocomotor(); @@ -511,7 +604,10 @@ void TeleporterAIUpdate::xfer( Xfer *xfer ) // extend base class AIUpdateInterface::xfer(xfer); - //xfer->xferBool(&m_inAttackPos); + xfer->xferBool(&m_isDisabled); + + xfer->xferUnsignedInt(&m_disabledUntil); + xfer->xferUnsignedInt(&m_disabledStart); } // end xfer From f5129560a40932af8b9d996c128830777998af3f Mon Sep 17 00:00:00 2001 From: Andi Date: Sat, 5 Jul 2025 17:02:39 +0200 Subject: [PATCH 31/42] removed teleportMovementBehavior files --- GeneralsMD/Code/GameEngine/CMakeLists.txt | 2 - .../Module/TeleportMovementBehavior.h | 81 ------- .../Source/Common/System/MemoryInit.cpp | 1 - .../Source/Common/Thing/ModuleFactory.cpp | 2 - .../Behavior/TeleportMovementBehavior.cpp | 198 ------------------ 5 files changed, 284 deletions(-) delete mode 100644 GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleportMovementBehavior.h delete mode 100644 GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/TeleportMovementBehavior.cpp diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index 8a153e389dd..c846d683c87 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -311,7 +311,6 @@ set(GAMEENGINE_SRC Include/GameLogic/Module/DozerAIUpdate.h Include/GameLogic/Module/DumbProjectileBehavior.h Include/GameLogic/Module/FreeFallProjectileBehavior.h - Include/GameLogic/Module/TeleportMovementBehavior.h Include/GameLogic/Module/DynamicGeometryInfoUpdate.h Include/GameLogic/Module/DynamicShroudClearingRangeUpdate.h Include/GameLogic/Module/EjectPilotDie.h @@ -858,7 +857,6 @@ set(GAMEENGINE_SRC Source/GameLogic/Object/Behavior/CountermeasuresBehavior.cpp Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp Source/GameLogic/Object/Behavior/FreeFallProjectileBehavior.cpp - Source/GameLogic/Object/Behavior/TeleportMovementBehavior.cpp Source/GameLogic/Object/Behavior/FireWeaponWhenDamagedBehavior.cpp Source/GameLogic/Object/Behavior/FireWeaponWhenDeadBehavior.cpp Source/GameLogic/Object/Behavior/FlightDeckBehavior.cpp diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleportMovementBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleportMovementBehavior.h deleted file mode 100644 index 36e209b314d..00000000000 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleportMovementBehavior.h +++ /dev/null @@ -1,81 +0,0 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: TeleportMovementBehavior.h ///////////////////////////////////////////////////////////////////////// -// Author: Graham Smallwood, July 2002 -// Desc: Behavior that reacts to poison Damage by continuously damaging us further in an Update -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __TeleportMovement_Behavior_H_ -#define __TeleportMovement_Behavior_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "GameLogic/Module/BehaviorModule.h" -#include "GameLogic/Module/UpdateModule.h" - - -//------------------------------------------------------------------------------------------------- -class TeleportMovementBehaviorModuleData : public UpdateModuleData -{ -public: - - TeleportMovementBehaviorModuleData(); - - static void buildFieldParse(MultiIniFieldParse& p); - - Real m_minDistance; - Real m_disabledDuration; - -private: - -}; - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -class TeleportMovementBehavior : public UpdateModule -{ - - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(TeleportMovementBehavior, "TeleportMovementBehavior") - MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA(TeleportMovementBehavior, TeleportMovementBehaviorModuleData) - -public: - TeleportMovementBehavior(Thing* thing, const ModuleData* moduleData); - - // UpdateInterface - virtual UpdateSleepTime update(); - - void doTeleport(Coord3D targetPos, Real angle, Real dist); - -protected: - - -private: - - -}; - -#endif // __TeleportMovement_Behavior_H_ - diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp index decb84fa74c..7a7f1c45c12 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp @@ -196,7 +196,6 @@ static PoolSizeRec sizes[] = { "MissileAIUpdate", 512, 32 }, { "DumbProjectileBehavior", 64, 32 }, { "FreeFallProjectileBehavior", 32, 32 }, - { "TeleportMovementBehavior", 32, 32 }, { "DestroyDie", 1024, 32 }, { "UpgradeDie", 128, 32 }, { "KeepObjectDie", 128, 32 }, diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index ec2b6000d19..9629f767c20 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -52,7 +52,6 @@ #include "GameLogic/Module/CountermeasuresBehavior.h" #include "GameLogic/Module/DumbProjectileBehavior.h" #include "GameLogic/Module/FreeFallProjectileBehavior.h" -#include "GameLogic/Module/TeleportMovementBehavior.h" #include "GameLogic/Module/InstantDeathBehavior.h" #include "GameLogic/Module/SlowDeathBehavior.h" #include "GameLogic/Module/HelicopterSlowDeathUpdate.h" @@ -342,7 +341,6 @@ void ModuleFactory::init( void ) addModule( CountermeasuresBehavior ); addModule( DumbProjectileBehavior ); addModule( FreeFallProjectileBehavior ); - addModule( TeleportMovementBehavior ); addModule( PhysicsBehavior ); addModule( InstantDeathBehavior ); addModule( SlowDeathBehavior ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/TeleportMovementBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/TeleportMovementBehavior.cpp deleted file mode 100644 index 85665d46d7d..00000000000 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/TeleportMovementBehavior.cpp +++ /dev/null @@ -1,198 +0,0 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: TeleportMovementBehavior.cpp ///////////////////////////////////////////////////////////////////////// -// Author: Graham Smallwood, July 2002 -// Desc: Behavior that reacts to poison Damage by continuously damaging us further in an Update -/////////////////////////////////////////////////////////////////////////////////////////////////// - - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine -#include "Common/Xfer.h" -#include "Common/DisabledTypes.h" -#include "GameClient/Drawable.h" -#include "GameLogic/Module/TeleportMovementBehavior.h" -#include "GameLogic/AI.h" -#include "GameLogic/AIPathfind.h" -#include "GameLogic/Module/AIUpdate.h" -#include "GameLogic/Damage.h" -#include "GameLogic/GameLogic.h" -#include "GameLogic/Object.h" -#include "GameLogic/PartitionManager.h" -#include "GameLogic/TerrainLogic.h" - - -//------------------------------------------------------------------------------------------------- -TeleportMovementBehaviorModuleData::TeleportMovementBehaviorModuleData() -{ - -} - -//------------------------------------------------------------------------------------------------- -/*static*/ void TeleportMovementBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p) -{ - - static const FieldParse dataFieldParse[] = - { - { "MinDistanceForTeleport", INI::parseReal, NULL, offsetof(TeleportMovementBehaviorModuleData, m_minDistance) }, - { "DisabledDurationPerDistance", INI::parseDurationReal, NULL, offsetof(TeleportMovementBehaviorModuleData, m_disabledDuration) }, - { 0, 0, 0, 0 } - }; - - UpdateModuleData::buildFieldParse(p); - p.add(dataFieldParse); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -TeleportMovementBehavior::TeleportMovementBehavior(Thing* thing, const ModuleData* moduleData) : UpdateModule(thing, moduleData) -{ - -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -TeleportMovementBehavior::~TeleportMovementBehavior(void) -{ -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ - -void TeleportMovementBehavior::doTeleport(Coord3D targetPos, Real angle, Real dist) -{ - const TeleportMovementBehaviorModuleData* d = getTeleportMovementBehaviorModuleData(); - Object* obj = getObject(); - - obj->setPosition(&targetPos); - obj->setOrientation(angle); - - - UnsignedInt disabledFrame = TheGameLogic->getFrame() + REAL_TO_UNSIGNEDINT(dist * d->m_disabledDuration); - - obj->setDisabledUntil(DISABLED_PARALYZED, disabledFrame); - - //If we have a path, clear it? - -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -UpdateSleepTime TeleportMovementBehavior::update() -{ - const TeleportMovementBehaviorModuleData* d = getTeleportMovementBehaviorModuleData(); - - Object* obj = getObject(); - - AIUpdateInterface* ai = obj->getAI(); - if (!ai) - return UPDATE_SLEEP_FOREVER; - - if (ai->isMoving()) { - Object* goalObj = ai->getGoalObject(); - const Coord3D* goalPos = ai->getGoalPosition(); - - Real requiredRange = 0; - - Coord3D targetPos; - - // Get TargetPos - if (goalObj != NULL) { - targetPos = *goalObj->getPosition(); - } - else if(goalPos != NULL) { - targetPos = *goalPos; - } - - Coord3D dir; - Real distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - Real targetAngle = atan2(dir.y, dir.x); - dir.normalize(); - - Real dist = sqrt(distSq); - - if (ai->isAttacking()) { - requiredRange = obj->getLargestWeaponRange(); - } - - // We are in range already - if (dist <= requiredRange || dist <= d->m_minDistance) { - return UPDATE_SLEEP_NONE; - } - - //Adjust target to required distance - if (requiredRange > 0) { - dir.scale(requiredRange); - targetPos.sub(&dir); - targetPos.z = TheTerrainLogic->getGroundHeight(targetPos.x, targetPos.y); - } - - doTeleport(targetPos, targetAngle, dist); - - } - - return UPDATE_SLEEP_NONE; - -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void TeleportMovementBehavior::crc(Xfer* xfer) -{ - - // extend base class - UpdateModule::crc(xfer); - -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ - // ------------------------------------------------------------------------------------------------ -void TeleportMovementBehavior::xfer(Xfer* xfer) -{ - - // version - const XferVersion currentVersion = 2; - XferVersion version = currentVersion; - xfer->xferVersion(&version, currentVersion); - - // extend base class - UpdateModule::xfer(xfer); - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void TeleportMovementBehavior::loadPostProcess(void) -{ - - // extend base class - UpdateModule::loadPostProcess(); - -} // end loadPostProcess From 354c5fc7a22ff7edd64df4cab7b9f095c978aa29 Mon Sep 17 00:00:00 2001 From: andreasw Date: Tue, 8 Jul 2025 15:51:05 +0200 Subject: [PATCH 32/42] fix line endings --- .../GameEngine/Include/Common/DisabledTypes.h | 242 +-- .../Code/GameEngine/Include/Common/KindOf.h | 516 +++--- .../GameEngine/Include/Common/ModelState.h | 630 +++---- .../GameEngine/Source/Common/BitFlags.cpp | 470 ++--- .../Source/Common/System/KindOf.cpp | 428 ++--- .../Source/Common/System/MemoryInit.cpp | 1628 ++++++++--------- 6 files changed, 1957 insertions(+), 1957 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/DisabledTypes.h b/GeneralsMD/Code/GameEngine/Include/Common/DisabledTypes.h index 8dca20e543a..c25d1ee012c 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/DisabledTypes.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/DisabledTypes.h @@ -1,121 +1,121 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: DisabledTypes.h ////////////////////////////////////////////////////////////////////////// -// Author: Kris Morness, September 2002 -// Desc: Defines all the types of disabled statii any given object can have. -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __DISABLED_TYPES_H_ -#define __DISABLED_TYPES_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "Lib/BaseType.h" -#include "Common/BitFlags.h" -#include "Common/BitFlagsIO.h" - -//------------------------------------------------------------------------------------------------- -/** Kind of flags for determining groups of things that belong together - * NOTE: You *MUST* keep this in the same order as the DisabledNames[] below */ -//------------------------------------------------------------------------------------------------- -enum DisabledType CPP_11(: Int) -{ - DISABLED_DEFAULT, //Typical disable -- like systems, things that don't need to run. - DISABLED_HACKED, //This unit has been hacked - DISABLED_EMP, //This unit has been disabled via electro-magnetic-pulse. - DISABLED_HELD, //Special case -- held means it can fire and isHeld checks to make sure ONLY held is set! - DISABLED_PARALYZED, //Battle plans have changed, and unit is confused/paralyzed - DISABLED_UNMANNED, //Vehicle is unmanned - DISABLED_UNDERPOWERED,//Seperate from ScriptUnderpowered, the owning player has insufficient power. Energy status controls this - DISABLED_FREEFALL, //This unit has been disabled via being in free fall - - DISABLED_AWESTRUCK, - DISABLED_BRAINWASHED, - DISABLED_SUBDUED, ///< Temporarily shut down by Subdual damage - //These ones are specificially for scripts to enable/reenable! - DISABLED_SCRIPT_DISABLED, - DISABLED_SCRIPT_UNDERPOWERED, - - DISABLED_TELEPORT, // Chrono Legionnaire after teleporting - - DISABLED_COUNT, - - DISABLED_ANY = 65535 ///< Do not use this value for setting disabled types (read-only) -}; - -typedef BitFlags DisabledMaskType; - -#define MAKE_DISABLED_MASK(k) DisabledMaskType(DisabledMaskType::kInit, (k)) -#define MAKE_DISABLED_MASK2(k,a) DisabledMaskType(DisabledMaskType::kInit, (k), (a)) -#define MAKE_DISABLED_MASK3(k,a,b) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b)) -#define MAKE_DISABLED_MASK4(k,a,b,c) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b), (c)) -#define MAKE_DISABLED_MASK5(k,a,b,c,d) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b), (c), (d)) - -inline Bool TEST_DISABLEDMASK(const DisabledMaskType& m, DisabledType t) -{ - return m.test(t); -} - -inline Bool TEST_DISABLEDMASK_ANY(const DisabledMaskType& m, const DisabledMaskType& mask) -{ - return m.anyIntersectionWith(mask); -} - -inline Bool TEST_DISABLEDMASK_MULTI(const DisabledMaskType& m, const DisabledMaskType& mustBeSet, const DisabledMaskType& mustBeClear) -{ - return m.testSetAndClear(mustBeSet, mustBeClear); -} - -inline Bool DISABLEDMASK_ANY_SET(const DisabledMaskType& m) -{ - return m.any(); -} - -inline void CLEAR_DISABLEDMASK(DisabledMaskType& m) -{ - m.clear(); -} - -inline void SET_ALL_DISABLEDMASK_BITS(DisabledMaskType& m) -{ - m.clear(); - m.flip(); -} - -inline void FLIP_DISABLEDMASK(DisabledMaskType& m) -{ - m.flip(); -} - - - -// defined in Common/System/DisabledTypes.cpp -extern DisabledMaskType DISABLEDMASK_NONE; // inits to all zeroes -extern DisabledMaskType DISABLEDMASK_ALL; // inits to all bits set. -void initDisabledMasks(); - -#endif // __DISABLED_TYPES_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: DisabledTypes.h ////////////////////////////////////////////////////////////////////////// +// Author: Kris Morness, September 2002 +// Desc: Defines all the types of disabled statii any given object can have. +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __DISABLED_TYPES_H_ +#define __DISABLED_TYPES_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Lib/BaseType.h" +#include "Common/BitFlags.h" +#include "Common/BitFlagsIO.h" + +//------------------------------------------------------------------------------------------------- +/** Kind of flags for determining groups of things that belong together + * NOTE: You *MUST* keep this in the same order as the DisabledNames[] below */ +//------------------------------------------------------------------------------------------------- +enum DisabledType CPP_11(: Int) +{ + DISABLED_DEFAULT, //Typical disable -- like systems, things that don't need to run. + DISABLED_HACKED, //This unit has been hacked + DISABLED_EMP, //This unit has been disabled via electro-magnetic-pulse. + DISABLED_HELD, //Special case -- held means it can fire and isHeld checks to make sure ONLY held is set! + DISABLED_PARALYZED, //Battle plans have changed, and unit is confused/paralyzed + DISABLED_UNMANNED, //Vehicle is unmanned + DISABLED_UNDERPOWERED,//Seperate from ScriptUnderpowered, the owning player has insufficient power. Energy status controls this + DISABLED_FREEFALL, //This unit has been disabled via being in free fall + + DISABLED_AWESTRUCK, + DISABLED_BRAINWASHED, + DISABLED_SUBDUED, ///< Temporarily shut down by Subdual damage + //These ones are specificially for scripts to enable/reenable! + DISABLED_SCRIPT_DISABLED, + DISABLED_SCRIPT_UNDERPOWERED, + + DISABLED_TELEPORT, // Chrono Legionnaire after teleporting + + DISABLED_COUNT, + + DISABLED_ANY = 65535 ///< Do not use this value for setting disabled types (read-only) +}; + +typedef BitFlags DisabledMaskType; + +#define MAKE_DISABLED_MASK(k) DisabledMaskType(DisabledMaskType::kInit, (k)) +#define MAKE_DISABLED_MASK2(k,a) DisabledMaskType(DisabledMaskType::kInit, (k), (a)) +#define MAKE_DISABLED_MASK3(k,a,b) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b)) +#define MAKE_DISABLED_MASK4(k,a,b,c) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b), (c)) +#define MAKE_DISABLED_MASK5(k,a,b,c,d) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b), (c), (d)) + +inline Bool TEST_DISABLEDMASK(const DisabledMaskType& m, DisabledType t) +{ + return m.test(t); +} + +inline Bool TEST_DISABLEDMASK_ANY(const DisabledMaskType& m, const DisabledMaskType& mask) +{ + return m.anyIntersectionWith(mask); +} + +inline Bool TEST_DISABLEDMASK_MULTI(const DisabledMaskType& m, const DisabledMaskType& mustBeSet, const DisabledMaskType& mustBeClear) +{ + return m.testSetAndClear(mustBeSet, mustBeClear); +} + +inline Bool DISABLEDMASK_ANY_SET(const DisabledMaskType& m) +{ + return m.any(); +} + +inline void CLEAR_DISABLEDMASK(DisabledMaskType& m) +{ + m.clear(); +} + +inline void SET_ALL_DISABLEDMASK_BITS(DisabledMaskType& m) +{ + m.clear(); + m.flip(); +} + +inline void FLIP_DISABLEDMASK(DisabledMaskType& m) +{ + m.flip(); +} + + + +// defined in Common/System/DisabledTypes.cpp +extern DisabledMaskType DISABLEDMASK_NONE; // inits to all zeroes +extern DisabledMaskType DISABLEDMASK_ALL; // inits to all bits set. +void initDisabledMasks(); + +#endif // __DISABLED_TYPES_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h b/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h index b6ca0d67417..757bdf97f2c 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h @@ -1,258 +1,258 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: KindOf.h ////////////////////////////////////////////////////////////////////////// -// Author: Steven Johnson, Dec 2001 -// Desc: -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __KINDOF_H_ -#define __KINDOF_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "Lib/BaseType.h" -#include "Common/BitFlags.h" -#include "Common/BitFlagsIO.h" - -//------------------------------------------------------------------------------------------------- -/** Kind of flags for determining groups of things that belong together - * NOTE: You *MUST* keep this in the same order as the KindOfNames[] below */ -//------------------------------------------------------------------------------------------------- -enum KindOfType CPP_11(: Int) -{ - KINDOF_INVALID = -1, - KINDOF_FIRST = 0, - KINDOF_OBSTACLE = KINDOF_FIRST, ///< an obstacle to land-based pathfinders - KINDOF_SELECTABLE, ///< Actually means MOUSE-INTERACTABLE (doesn't mean you can select it!) - KINDOF_IMMOBILE, ///< fixed in location - KINDOF_CAN_ATTACK, ///< can attack - KINDOF_STICK_TO_TERRAIN_SLOPE, ///< should be stuck at ground level, aligned to terrain slope. requires that IMMOBILE bit is also set. - KINDOF_CAN_CAST_REFLECTIONS, ///< can cast reflections in water - KINDOF_SHRUBBERY, ///< tree, bush, etc. - KINDOF_STRUCTURE, ///< structure of some sort (buildable or not) - KINDOF_INFANTRY, ///< unit like soldier etc - KINDOF_VEHICLE, ///< unit like tank, jeep, plane, helicopter, etc. - KINDOF_AIRCRAFT, ///< unit like plane, helicopter, etc., that is predominantly a flyer. (hovercraft are NOT aircraft) - KINDOF_HUGE_VEHICLE, ///< unit that is, technically, a vehicle, but WAY larger than normal (eg, Overlord) - KINDOF_DOZER, ///< a dozer - KINDOF_HARVESTER, ///< a harvester - KINDOF_COMMANDCENTER, ///< a command center -#ifdef ALLOW_SURRENDER - KINDOF_PRISON, ///< a prison detention center kind of thing - KINDOF_COLLECTS_PRISON_BOUNTY, ///< when prisoners are delivered to these, the player gets money - KINDOF_POW_TRUCK, ///< a pow truck can pick up and return prisoners -#endif - KINDOF_LINEBUILD, ///< wall-type thing that is built in a line - KINDOF_SALVAGER, ///< something that can create and use Salvage Crates - KINDOF_WEAPON_SALVAGER, ///< subset of salvager that can get weapon upgrades from salvage - KINDOF_TRANSPORT, ///< a true transport (has TransportContain) - KINDOF_BRIDGE, ///< a Bridge. (special structure) - KINDOF_LANDMARK_BRIDGE, ///< a landmark bridge (special bridge that isn't resizable) - KINDOF_BRIDGE_TOWER, ///< a bridge tower that we can target for bridge destruction - KINDOF_PROJECTILE, ///< Instead of being a ground or air unit, this object is special - KINDOF_PRELOAD, ///< all model data will be preloaded even if not on map - KINDOF_NO_GARRISON, ///< unit may not garrison bldgs, even if infantry bit is set - KINDOF_WAVEGUIDE, ///< water wave object - KINDOF_WAVE_EFFECT, ///< wave effect point - KINDOF_NO_COLLIDE, ///< Never collide with or be collided with - KINDOF_REPAIR_PAD, ///< is a repair pad object that can repair other machines - KINDOF_HEAL_PAD, ///< is a heal pad object that can heal flesh and bone units - KINDOF_STEALTH_GARRISON, /** enemy teams can't tell that unit is in building.. and if they - garrison that building, they stealth unit will eject. */ - KINDOF_CASH_GENERATOR, ///< used to check if the unit generates cash... checked by cash hackers and whatever else comes up - KINDOF_DRAWABLE_ONLY, ///< template is used only to create drawables (not Objects) - KINDOF_MP_COUNT_FOR_VICTORY, ///< If a player loses all his buildings that have this kindof in a multiplayer game, he loses. - KINDOF_REBUILD_HOLE, ///< a GLA rebuild hole - KINDOF_SCORE, ///< Object counts for Multiplayer scores, and short-game calculations (for buildings) - KINDOF_SCORE_CREATE, ///< Object only counts for multiplayer score for creation. - KINDOF_SCORE_DESTROY, ///< Object only counts for multiplayer score for destruction. - KINDOF_NO_HEAL_ICON, ///< do not ever display healing icons on these objects - KINDOF_CAN_RAPPEL, ///< can rappel. duh. - KINDOF_PARACHUTABLE, ///< parachutable object -#ifdef ALLOW_SURRENDER - KINDOF_CAN_SURRENDER, ///< object that can surrender -#endif - KINDOF_CAN_BE_REPULSED, ///< object that runs away from a repulsor object. - KINDOF_MOB_NEXUS, ///< object that cooyrdinates the members of a mob (i.e. GLAInfantryAngryMob) - KINDOF_IGNORED_IN_GUI, ///< object that is the members of a mob (i.e. GLAInfantryAngryMob) - KINDOF_CRATE, ///< a bonus crate - KINDOF_CAPTURABLE, ///< is "capturable" even if not an enemy (should generally be used only for structures, eg, Tech bldgs) - KINDOF_CLEARED_BY_BUILD, ///< is auto-cleared from the map when built over via construction - KINDOF_SMALL_MISSILE, ///< Missile object: ONLY USED FOR ANTI-MISSILE TARGETTING PURPOSES! Keep using PROJECTILE! - KINDOF_ALWAYS_VISIBLE, ///< is never obscured by fog of war or shroud. mostly for UI feedback objects. - KINDOF_UNATTACKABLE, ///< You cannot target this thing, it probably doesn't really exist - KINDOF_MINE, ///< a landmine. (possibly also extend to Col. Burton timed charges?) - KINDOF_CLEANUP_HAZARD, ///< radiation and bio-poison are samples of area conditions that can be cleaned up (or avoided) - KINDOF_PORTABLE_STRUCTURE, ///< Flag to identify building like subobjects an Overlord is allowed to Contain. - KINDOF_ALWAYS_SELECTABLE, ///< is never unselectable (even if effectively dead). mostly for UI feedback objects. - KINDOF_ATTACK_NEEDS_LINE_OF_SIGHT, ///< Unit has to have clear line of sight (los) to attack. - KINDOF_WALK_ON_TOP_OF_WALL, ///< Units can walk on top of a wall made of these kind of objects. - KINDOF_DEFENSIVE_WALL, ///< wall can't be driven thru, even if crusher, so pathfinder must path around it - KINDOF_FS_POWER, ///< Faction structure power building - KINDOF_FS_FACTORY, ///< Faction structure power building - KINDOF_FS_BASE_DEFENSE, ///< Faction structure base defense - KINDOF_FS_TECHNOLOGY, ///< Faction structure technology building - KINDOF_AIRCRAFT_PATH_AROUND, ///< Tall enough that aircraft need to path around this. - KINDOF_LOW_OVERLAPPABLE, ///< When overlapped, things always overlap at a 'low' height rather than our object geom - KINDOF_FORCEATTACKABLE, ///< unit is always attackable via force-attack, even if not selectable - KINDOF_AUTO_RALLYPOINT, ///< When immobile-structure-object is selected, left clicking on ground will set new rally point without requiring command button. - KINDOF_TECH_BUILDING, ///< Neutral tech building - Oil derrick, Hospital, Radio Station, Refinery. - KINDOF_POWERED, ///< This object gets the Underpowered disabled condition when its owning player has power consumption exceed supply - KINDOF_PRODUCED_AT_HELIPAD, ///< ugh... hacky fix for comanche. (srj) - KINDOF_DRONE, ///< Object drone type -- used for filtering them out of battle plan bonuses, making un-snipable, and whatever else may come up. - KINDOF_CAN_SEE_THROUGH_STRUCTURE,///< Structure does not block line of sight. - KINDOF_BALLISTIC_MISSILE, ///< Large ballistic missiles that are specifically large enough to be targetted by base defenses. - KINDOF_CLICK_THROUGH, ///< Objects with this will never be picked by mouse interactions! - KINDOF_SUPPLY_SOURCE_ON_PREVIEW,///< Any thing that we can get "supplies" from that we want to show up on the map preview - KINDOF_PARACHUTE, ///< it's a parachute - KINDOF_GARRISONABLE_UNTIL_DESTROYED, ///< Object is capable of garrisoning troops until completely destroyed. - KINDOF_BOAT, ///< It's a boat! - KINDOF_IMMUNE_TO_CAPTURE, ///< Under no circumstances can this building ever be captured. - KINDOF_HULK, ///< Hulk types so we can do special things to them via scripts or other things that may come up. - KINDOF_SHOW_PORTRAIT_WHEN_CONTROLLED, ///< Only shows portraits when controlled. - KINDOF_SPAWNS_ARE_THE_WEAPONS, ///< Evaluate the spawn slaves as this object's weapons. - KINDOF_CANNOT_BUILD_NEAR_SUPPLIES, ///< you can't be built "too close" to anything that provides supplies - KINDOF_SUPPLY_SOURCE, ///< this object provides supplies - KINDOF_REVEAL_TO_ALL, ///< this object reveals shroud for all players - KINDOF_DISGUISER, ///< This object has the ability to disguise. - KINDOF_INERT, ///< this object shouldn't be considered for any sort of interaction with any player. - KINDOF_HERO, ///< Any of the single-instance infantry, JarmenKell, BlackLotus, ColonelBurton - KINDOF_IGNORES_SELECT_ALL, ///< Too late to figure out intelligently if something should respond to a Select All command - KINDOF_DONT_AUTO_CRUSH_INFANTRY, ///< These units don't try to crush the infantry if ai. - KINDOF_CLIFF_JUMPER, ///< Can't climb cliffs, but can jump off of them. - KINDOF_FS_SUPPLY_DROPZONE, ///< A supply dropzone. - KINDOF_FS_SUPERWEAPON, ///< A superweapon structure like a nuke silo, particle uplink cannon, scudstorm. - KINDOF_FS_BLACK_MARKET, ///< Is this object a black market? - KINDOF_FS_SUPPLY_CENTER, ///< Is this object a supply center? - KINDOF_FS_STRATEGY_CENTER, ///< Is this object a strategy center? - KINDOF_MONEY_HACKER, ///< Unit that generates money from air. Needed for things that directly power them up. - KINDOF_ARMOR_SALVAGER, ///< subset of salvager that can get armor upgrades from salvage - KINDOF_REVEALS_ENEMY_PATHS, ///< like the listening outpost... when selected, any enemy drawable will draw show paths when moused over - KINDOF_BOOBY_TRAP, ///< A sticky bomb that gets set off by 5 random and unrelated events. - KINDOF_FS_FAKE, ///< Fake structure! - KINDOF_FS_INTERNET_CENTER, ///< Internet Center. - KINDOF_BLAST_CRATER, ///< deeply gouges out the terrain under object footprint - KINDOF_PROP, ///< A prop, visual only, doesn't interact with other objects (rock, street sign, inert fire hydrant) - KINDOF_OPTIMIZED_TREE, ///< An optimized, client side only tree. (The only good kind of tree. jba) - KINDOF_FS_ADVANCED_TECH, ///< Represents each faction's advanced techtree building -- strategy center, propaganda center, and palace. - KINDOF_FS_BARRACKS, ///< A barracks - KINDOF_FS_WARFACTORY, ///< A war factory or arms dealer. - KINDOF_FS_AIRFIELD, ///< An airfield. - KINDOF_AIRCRAFT_CARRIER, ///< An aircraft carrier. - KINDOF_NO_SELECT, ///< Can't select it but you can mouse over it to see it's health (drones!) - KINDOF_REJECT_UNMANNED, ///< Unit cannot enter an unmanned vehicle. - KINDOF_CANNOT_RETALIATE, ///< Unit will not retaliate if asked. - KINDOF_TECH_BASE_DEFENSE, ///< Tech Building that acts as base defence when captured - KINDOF_EMP_HARDENED, ///< Like a delivery plane (B52, B3, CargoPlane,etc.) or a SpectreGunship, which sort-of IS the weapon... - KINDOF_DEMOTRAP, ///< Added strictly only for disarming purposes. They don't act like mines which have rendering and selection implications! - KINDOF_CONSERVATIVE_BUILDING, ///< Conservative structures aren't considered part of your base for sneak attack boundary calculations... - KINDOF_IGNORE_DOCKING_BONES, ///< Structure will not look up docking bones. Patch 1.03 hack. - - // NEW KINDOFs - - KINDOF_VTOL, - KINDOF_LARGE_AIRCRAFT, - KINDOF_MEDIUM_AIRCRAFT, - KINDOF_SMALL_AIRCRAFT, - KINDOF_ARTILLERY, - KINDOF_HEAVY_ARTILLERY, - KINDOF_ANTI_AIR, - KINDOF_SCOUT, - KINDOF_COMMANDO, - KINDOF_HEAVY_INFANTRY, - KINDOF_SUPERHEAVY_VEHICLE, - - KINDOF_TELEPORTER, - - KINDOF_EXTRA1, - KINDOF_EXTRA2, - KINDOF_EXTRA3, - KINDOF_EXTRA4, - KINDOF_EXTRA5, - KINDOF_EXTRA6, - KINDOF_EXTRA7, - KINDOF_EXTRA8, - KINDOF_EXTRA9, - KINDOF_EXTRA10, - KINDOF_EXTRA11, - KINDOF_EXTRA12, - KINDOF_EXTRA13, - KINDOF_EXTRA14, - KINDOF_EXTRA15, - KINDOF_EXTRA16, - - - KINDOF_COUNT // total number of kindofs - -}; - -typedef BitFlags KindOfMaskType; - -#define MAKE_KINDOF_MASK(k) KindOfMaskType(KindOfMaskType::kInit, (k)) - -inline Bool TEST_KINDOFMASK(const KindOfMaskType& m, KindOfType t) -{ - return m.test(t); -} - -inline Bool TEST_KINDOFMASK_ANY(const KindOfMaskType& m, const KindOfMaskType& mask) -{ - return m.anyIntersectionWith(mask); -} - -inline Bool TEST_KINDOFMASK_MULTI(const KindOfMaskType& m, const KindOfMaskType& mustBeSet, const KindOfMaskType& mustBeClear) -{ - return m.testSetAndClear(mustBeSet, mustBeClear); -} - -inline Bool KINDOFMASK_ANY_SET(const KindOfMaskType& m) -{ - return m.any(); -} - -inline void CLEAR_KINDOFMASK(KindOfMaskType& m) -{ - m.clear(); -} - -inline void SET_ALL_KINDOFMASK_BITS(KindOfMaskType& m) -{ - m.clear(); - m.flip(); -} - -inline void FLIP_KINDOFMASK(KindOfMaskType& m) -{ - m.flip(); -} - -// defined in Common/System/Kindof.cpp -extern KindOfMaskType KINDOFMASK_NONE; // inits to all zeroes -extern KindOfMaskType KINDOFMASK_FS; // Initializes all FS types for faction structures. -void initKindOfMasks(); - -#endif // __KINDOF_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: KindOf.h ////////////////////////////////////////////////////////////////////////// +// Author: Steven Johnson, Dec 2001 +// Desc: +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __KINDOF_H_ +#define __KINDOF_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Lib/BaseType.h" +#include "Common/BitFlags.h" +#include "Common/BitFlagsIO.h" + +//------------------------------------------------------------------------------------------------- +/** Kind of flags for determining groups of things that belong together + * NOTE: You *MUST* keep this in the same order as the KindOfNames[] below */ +//------------------------------------------------------------------------------------------------- +enum KindOfType CPP_11(: Int) +{ + KINDOF_INVALID = -1, + KINDOF_FIRST = 0, + KINDOF_OBSTACLE = KINDOF_FIRST, ///< an obstacle to land-based pathfinders + KINDOF_SELECTABLE, ///< Actually means MOUSE-INTERACTABLE (doesn't mean you can select it!) + KINDOF_IMMOBILE, ///< fixed in location + KINDOF_CAN_ATTACK, ///< can attack + KINDOF_STICK_TO_TERRAIN_SLOPE, ///< should be stuck at ground level, aligned to terrain slope. requires that IMMOBILE bit is also set. + KINDOF_CAN_CAST_REFLECTIONS, ///< can cast reflections in water + KINDOF_SHRUBBERY, ///< tree, bush, etc. + KINDOF_STRUCTURE, ///< structure of some sort (buildable or not) + KINDOF_INFANTRY, ///< unit like soldier etc + KINDOF_VEHICLE, ///< unit like tank, jeep, plane, helicopter, etc. + KINDOF_AIRCRAFT, ///< unit like plane, helicopter, etc., that is predominantly a flyer. (hovercraft are NOT aircraft) + KINDOF_HUGE_VEHICLE, ///< unit that is, technically, a vehicle, but WAY larger than normal (eg, Overlord) + KINDOF_DOZER, ///< a dozer + KINDOF_HARVESTER, ///< a harvester + KINDOF_COMMANDCENTER, ///< a command center +#ifdef ALLOW_SURRENDER + KINDOF_PRISON, ///< a prison detention center kind of thing + KINDOF_COLLECTS_PRISON_BOUNTY, ///< when prisoners are delivered to these, the player gets money + KINDOF_POW_TRUCK, ///< a pow truck can pick up and return prisoners +#endif + KINDOF_LINEBUILD, ///< wall-type thing that is built in a line + KINDOF_SALVAGER, ///< something that can create and use Salvage Crates + KINDOF_WEAPON_SALVAGER, ///< subset of salvager that can get weapon upgrades from salvage + KINDOF_TRANSPORT, ///< a true transport (has TransportContain) + KINDOF_BRIDGE, ///< a Bridge. (special structure) + KINDOF_LANDMARK_BRIDGE, ///< a landmark bridge (special bridge that isn't resizable) + KINDOF_BRIDGE_TOWER, ///< a bridge tower that we can target for bridge destruction + KINDOF_PROJECTILE, ///< Instead of being a ground or air unit, this object is special + KINDOF_PRELOAD, ///< all model data will be preloaded even if not on map + KINDOF_NO_GARRISON, ///< unit may not garrison bldgs, even if infantry bit is set + KINDOF_WAVEGUIDE, ///< water wave object + KINDOF_WAVE_EFFECT, ///< wave effect point + KINDOF_NO_COLLIDE, ///< Never collide with or be collided with + KINDOF_REPAIR_PAD, ///< is a repair pad object that can repair other machines + KINDOF_HEAL_PAD, ///< is a heal pad object that can heal flesh and bone units + KINDOF_STEALTH_GARRISON, /** enemy teams can't tell that unit is in building.. and if they + garrison that building, they stealth unit will eject. */ + KINDOF_CASH_GENERATOR, ///< used to check if the unit generates cash... checked by cash hackers and whatever else comes up + KINDOF_DRAWABLE_ONLY, ///< template is used only to create drawables (not Objects) + KINDOF_MP_COUNT_FOR_VICTORY, ///< If a player loses all his buildings that have this kindof in a multiplayer game, he loses. + KINDOF_REBUILD_HOLE, ///< a GLA rebuild hole + KINDOF_SCORE, ///< Object counts for Multiplayer scores, and short-game calculations (for buildings) + KINDOF_SCORE_CREATE, ///< Object only counts for multiplayer score for creation. + KINDOF_SCORE_DESTROY, ///< Object only counts for multiplayer score for destruction. + KINDOF_NO_HEAL_ICON, ///< do not ever display healing icons on these objects + KINDOF_CAN_RAPPEL, ///< can rappel. duh. + KINDOF_PARACHUTABLE, ///< parachutable object +#ifdef ALLOW_SURRENDER + KINDOF_CAN_SURRENDER, ///< object that can surrender +#endif + KINDOF_CAN_BE_REPULSED, ///< object that runs away from a repulsor object. + KINDOF_MOB_NEXUS, ///< object that cooyrdinates the members of a mob (i.e. GLAInfantryAngryMob) + KINDOF_IGNORED_IN_GUI, ///< object that is the members of a mob (i.e. GLAInfantryAngryMob) + KINDOF_CRATE, ///< a bonus crate + KINDOF_CAPTURABLE, ///< is "capturable" even if not an enemy (should generally be used only for structures, eg, Tech bldgs) + KINDOF_CLEARED_BY_BUILD, ///< is auto-cleared from the map when built over via construction + KINDOF_SMALL_MISSILE, ///< Missile object: ONLY USED FOR ANTI-MISSILE TARGETTING PURPOSES! Keep using PROJECTILE! + KINDOF_ALWAYS_VISIBLE, ///< is never obscured by fog of war or shroud. mostly for UI feedback objects. + KINDOF_UNATTACKABLE, ///< You cannot target this thing, it probably doesn't really exist + KINDOF_MINE, ///< a landmine. (possibly also extend to Col. Burton timed charges?) + KINDOF_CLEANUP_HAZARD, ///< radiation and bio-poison are samples of area conditions that can be cleaned up (or avoided) + KINDOF_PORTABLE_STRUCTURE, ///< Flag to identify building like subobjects an Overlord is allowed to Contain. + KINDOF_ALWAYS_SELECTABLE, ///< is never unselectable (even if effectively dead). mostly for UI feedback objects. + KINDOF_ATTACK_NEEDS_LINE_OF_SIGHT, ///< Unit has to have clear line of sight (los) to attack. + KINDOF_WALK_ON_TOP_OF_WALL, ///< Units can walk on top of a wall made of these kind of objects. + KINDOF_DEFENSIVE_WALL, ///< wall can't be driven thru, even if crusher, so pathfinder must path around it + KINDOF_FS_POWER, ///< Faction structure power building + KINDOF_FS_FACTORY, ///< Faction structure power building + KINDOF_FS_BASE_DEFENSE, ///< Faction structure base defense + KINDOF_FS_TECHNOLOGY, ///< Faction structure technology building + KINDOF_AIRCRAFT_PATH_AROUND, ///< Tall enough that aircraft need to path around this. + KINDOF_LOW_OVERLAPPABLE, ///< When overlapped, things always overlap at a 'low' height rather than our object geom + KINDOF_FORCEATTACKABLE, ///< unit is always attackable via force-attack, even if not selectable + KINDOF_AUTO_RALLYPOINT, ///< When immobile-structure-object is selected, left clicking on ground will set new rally point without requiring command button. + KINDOF_TECH_BUILDING, ///< Neutral tech building - Oil derrick, Hospital, Radio Station, Refinery. + KINDOF_POWERED, ///< This object gets the Underpowered disabled condition when its owning player has power consumption exceed supply + KINDOF_PRODUCED_AT_HELIPAD, ///< ugh... hacky fix for comanche. (srj) + KINDOF_DRONE, ///< Object drone type -- used for filtering them out of battle plan bonuses, making un-snipable, and whatever else may come up. + KINDOF_CAN_SEE_THROUGH_STRUCTURE,///< Structure does not block line of sight. + KINDOF_BALLISTIC_MISSILE, ///< Large ballistic missiles that are specifically large enough to be targetted by base defenses. + KINDOF_CLICK_THROUGH, ///< Objects with this will never be picked by mouse interactions! + KINDOF_SUPPLY_SOURCE_ON_PREVIEW,///< Any thing that we can get "supplies" from that we want to show up on the map preview + KINDOF_PARACHUTE, ///< it's a parachute + KINDOF_GARRISONABLE_UNTIL_DESTROYED, ///< Object is capable of garrisoning troops until completely destroyed. + KINDOF_BOAT, ///< It's a boat! + KINDOF_IMMUNE_TO_CAPTURE, ///< Under no circumstances can this building ever be captured. + KINDOF_HULK, ///< Hulk types so we can do special things to them via scripts or other things that may come up. + KINDOF_SHOW_PORTRAIT_WHEN_CONTROLLED, ///< Only shows portraits when controlled. + KINDOF_SPAWNS_ARE_THE_WEAPONS, ///< Evaluate the spawn slaves as this object's weapons. + KINDOF_CANNOT_BUILD_NEAR_SUPPLIES, ///< you can't be built "too close" to anything that provides supplies + KINDOF_SUPPLY_SOURCE, ///< this object provides supplies + KINDOF_REVEAL_TO_ALL, ///< this object reveals shroud for all players + KINDOF_DISGUISER, ///< This object has the ability to disguise. + KINDOF_INERT, ///< this object shouldn't be considered for any sort of interaction with any player. + KINDOF_HERO, ///< Any of the single-instance infantry, JarmenKell, BlackLotus, ColonelBurton + KINDOF_IGNORES_SELECT_ALL, ///< Too late to figure out intelligently if something should respond to a Select All command + KINDOF_DONT_AUTO_CRUSH_INFANTRY, ///< These units don't try to crush the infantry if ai. + KINDOF_CLIFF_JUMPER, ///< Can't climb cliffs, but can jump off of them. + KINDOF_FS_SUPPLY_DROPZONE, ///< A supply dropzone. + KINDOF_FS_SUPERWEAPON, ///< A superweapon structure like a nuke silo, particle uplink cannon, scudstorm. + KINDOF_FS_BLACK_MARKET, ///< Is this object a black market? + KINDOF_FS_SUPPLY_CENTER, ///< Is this object a supply center? + KINDOF_FS_STRATEGY_CENTER, ///< Is this object a strategy center? + KINDOF_MONEY_HACKER, ///< Unit that generates money from air. Needed for things that directly power them up. + KINDOF_ARMOR_SALVAGER, ///< subset of salvager that can get armor upgrades from salvage + KINDOF_REVEALS_ENEMY_PATHS, ///< like the listening outpost... when selected, any enemy drawable will draw show paths when moused over + KINDOF_BOOBY_TRAP, ///< A sticky bomb that gets set off by 5 random and unrelated events. + KINDOF_FS_FAKE, ///< Fake structure! + KINDOF_FS_INTERNET_CENTER, ///< Internet Center. + KINDOF_BLAST_CRATER, ///< deeply gouges out the terrain under object footprint + KINDOF_PROP, ///< A prop, visual only, doesn't interact with other objects (rock, street sign, inert fire hydrant) + KINDOF_OPTIMIZED_TREE, ///< An optimized, client side only tree. (The only good kind of tree. jba) + KINDOF_FS_ADVANCED_TECH, ///< Represents each faction's advanced techtree building -- strategy center, propaganda center, and palace. + KINDOF_FS_BARRACKS, ///< A barracks + KINDOF_FS_WARFACTORY, ///< A war factory or arms dealer. + KINDOF_FS_AIRFIELD, ///< An airfield. + KINDOF_AIRCRAFT_CARRIER, ///< An aircraft carrier. + KINDOF_NO_SELECT, ///< Can't select it but you can mouse over it to see it's health (drones!) + KINDOF_REJECT_UNMANNED, ///< Unit cannot enter an unmanned vehicle. + KINDOF_CANNOT_RETALIATE, ///< Unit will not retaliate if asked. + KINDOF_TECH_BASE_DEFENSE, ///< Tech Building that acts as base defence when captured + KINDOF_EMP_HARDENED, ///< Like a delivery plane (B52, B3, CargoPlane,etc.) or a SpectreGunship, which sort-of IS the weapon... + KINDOF_DEMOTRAP, ///< Added strictly only for disarming purposes. They don't act like mines which have rendering and selection implications! + KINDOF_CONSERVATIVE_BUILDING, ///< Conservative structures aren't considered part of your base for sneak attack boundary calculations... + KINDOF_IGNORE_DOCKING_BONES, ///< Structure will not look up docking bones. Patch 1.03 hack. + + // NEW KINDOFs + + KINDOF_VTOL, + KINDOF_LARGE_AIRCRAFT, + KINDOF_MEDIUM_AIRCRAFT, + KINDOF_SMALL_AIRCRAFT, + KINDOF_ARTILLERY, + KINDOF_HEAVY_ARTILLERY, + KINDOF_ANTI_AIR, + KINDOF_SCOUT, + KINDOF_COMMANDO, + KINDOF_HEAVY_INFANTRY, + KINDOF_SUPERHEAVY_VEHICLE, + + KINDOF_TELEPORTER, + + KINDOF_EXTRA1, + KINDOF_EXTRA2, + KINDOF_EXTRA3, + KINDOF_EXTRA4, + KINDOF_EXTRA5, + KINDOF_EXTRA6, + KINDOF_EXTRA7, + KINDOF_EXTRA8, + KINDOF_EXTRA9, + KINDOF_EXTRA10, + KINDOF_EXTRA11, + KINDOF_EXTRA12, + KINDOF_EXTRA13, + KINDOF_EXTRA14, + KINDOF_EXTRA15, + KINDOF_EXTRA16, + + + KINDOF_COUNT // total number of kindofs + +}; + +typedef BitFlags KindOfMaskType; + +#define MAKE_KINDOF_MASK(k) KindOfMaskType(KindOfMaskType::kInit, (k)) + +inline Bool TEST_KINDOFMASK(const KindOfMaskType& m, KindOfType t) +{ + return m.test(t); +} + +inline Bool TEST_KINDOFMASK_ANY(const KindOfMaskType& m, const KindOfMaskType& mask) +{ + return m.anyIntersectionWith(mask); +} + +inline Bool TEST_KINDOFMASK_MULTI(const KindOfMaskType& m, const KindOfMaskType& mustBeSet, const KindOfMaskType& mustBeClear) +{ + return m.testSetAndClear(mustBeSet, mustBeClear); +} + +inline Bool KINDOFMASK_ANY_SET(const KindOfMaskType& m) +{ + return m.any(); +} + +inline void CLEAR_KINDOFMASK(KindOfMaskType& m) +{ + m.clear(); +} + +inline void SET_ALL_KINDOFMASK_BITS(KindOfMaskType& m) +{ + m.clear(); + m.flip(); +} + +inline void FLIP_KINDOFMASK(KindOfMaskType& m) +{ + m.flip(); +} + +// defined in Common/System/Kindof.cpp +extern KindOfMaskType KINDOFMASK_NONE; // inits to all zeroes +extern KindOfMaskType KINDOFMASK_FS; // Initializes all FS types for faction structures. +void initKindOfMasks(); + +#endif // __KINDOF_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h b/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h index 621df2f0e77..9d55321ae94 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h @@ -1,315 +1,315 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// ModelState.h -// Basic data types needed for the game engine. This is an extension of BaseType.h. -// Author: Michael S. Booth, April 2001 - -#pragma once - -#ifndef _ModelState_H_ -#define _ModelState_H_ - -#include "Lib/BaseType.h" -#include "Common/INI.h" -#include "Common/BitFlags.h" -#include "Common/BitFlagsIO.h" - -//------------------------------------------------------------------------------------------------- - -/** - THE PROBLEM - ----------- - - -- there are lots of different states. (consider that structures can be: day/night, snow/nosnow, - powered/not, garrisoned/empty, damaged/not... you do the math.) - - -- some states are mutually exclusive (idle vs. moving), others are not (snow/nosnow, day/night). - generally, humanoid units have mostly the former, while structure units have mostly the latter. - The current ModelState system really only supports the mutually-exclusive states well. - - -- we'd rather not have to specify every state in the INI files, BUT we do want to be able - to intelligently choose the best model for a given state, whether or not a "real" state exists for it. - - -- it would be desirable to have a unified way of representing "ModelState" so that we don't - have multiple similar-yet-different systems. - - YUCK, WHAT NOW - -------------- - - Let's represent the Model State with two dictinct pieces: - - -- an "ActionState" piece, representing the mutually-exclusive states, which are almost - always an action of some sort - - -- and a "ConditionState" piece, which is a set of bitflags to indicate the static "condition" - of the model. - - Note that these are usually set independently in code, but they are lumped together in order to - determine the actual model to be used. - - (Let's require all objects would be required to have an "Idle" ActionState, which is the - normal, just-sitting there condition.) - - From a code point of view, this becomes an issue of requesting a certain state, and - finding the best-fit match for it. So, what are the rules for finding a good match? - - -- Action states must match exactly. If the desired action state is not found, then the - IDLE state is substituted (but this should generally be considered an error condition). - - -- Condition states choose the match with the closest match among the "Condition" bits in the - INI file, based on satisfying the most of the "required" conditions and the fewest of the - "forbidden" conditions. - -*/ - -#define NUM_MODELCONDITION_DOOR_STATES 4 - -//------------------------------------------------------------------------------------------------- -// IMPORTANT NOTE: you should endeavor to set up states such that the most "normal" -// state is defined by the bit being off. That is, the typical "normal" condition -// has all condition flags set to zero. -enum ModelConditionFlagType CPP_11(: Int) -{ - MODELCONDITION_INVALID = -1, - - MODELCONDITION_FIRST = 0, - -// -// Note: these values are saved in save files, so you MUST NOT REMOVE OR CHANGE -// existing values! -// - MODELCONDITION_TOPPLED = MODELCONDITION_FIRST, - MODELCONDITION_FRONTCRUSHED, - MODELCONDITION_BACKCRUSHED, - MODELCONDITION_DAMAGED, - MODELCONDITION_REALLY_DAMAGED, - MODELCONDITION_RUBBLE, - MODELCONDITION_SPECIAL_DAMAGED, - MODELCONDITION_NIGHT, - MODELCONDITION_SNOW, - MODELCONDITION_PARACHUTING, - MODELCONDITION_GARRISONED, - MODELCONDITION_ENEMYNEAR, - MODELCONDITION_WEAPONSET_VETERAN, - MODELCONDITION_WEAPONSET_ELITE, - MODELCONDITION_WEAPONSET_HERO, - MODELCONDITION_WEAPONSET_CRATEUPGRADE_ONE, - MODELCONDITION_WEAPONSET_CRATEUPGRADE_TWO, - MODELCONDITION_WEAPONSET_PLAYER_UPGRADE, - MODELCONDITION_DOOR_1_OPENING, - MODELCONDITION_DOOR_1_CLOSING, - MODELCONDITION_DOOR_1_WAITING_OPEN, - MODELCONDITION_DOOR_1_WAITING_TO_CLOSE, - MODELCONDITION_DOOR_2_OPENING, - MODELCONDITION_DOOR_2_CLOSING, - MODELCONDITION_DOOR_2_WAITING_OPEN, - MODELCONDITION_DOOR_2_WAITING_TO_CLOSE, - MODELCONDITION_DOOR_3_OPENING, - MODELCONDITION_DOOR_3_CLOSING, - MODELCONDITION_DOOR_3_WAITING_OPEN, - MODELCONDITION_DOOR_3_WAITING_TO_CLOSE, - MODELCONDITION_DOOR_4_OPENING, - MODELCONDITION_DOOR_4_CLOSING, - MODELCONDITION_DOOR_4_WAITING_OPEN, - MODELCONDITION_DOOR_4_WAITING_TO_CLOSE, - MODELCONDITION_ATTACKING, //Simply set when a unit is fighting -- terrorist moving with a target will flail arms like a psycho. - MODELCONDITION_PREATTACK_A, //Use for pre-attack animations (like aiming, pulling out a knife, or detonating explosives). - MODELCONDITION_FIRING_A, - MODELCONDITION_BETWEEN_FIRING_SHOTS_A, - MODELCONDITION_RELOADING_A, - MODELCONDITION_PREATTACK_B, //Use for pre-attack animations (like aiming, pulling out a knife, or detonating explosives). - MODELCONDITION_FIRING_B, - MODELCONDITION_BETWEEN_FIRING_SHOTS_B, - MODELCONDITION_RELOADING_B, - MODELCONDITION_PREATTACK_C, //Use for pre-attack animations (like aiming, pulling out a knife, or detonating explosives). - MODELCONDITION_FIRING_C, - MODELCONDITION_BETWEEN_FIRING_SHOTS_C, - MODELCONDITION_RELOADING_C, - MODELCONDITION_TURRET_ROTATE, - MODELCONDITION_POST_COLLAPSE, - MODELCONDITION_MOVING, - MODELCONDITION_DYING, - MODELCONDITION_AWAITING_CONSTRUCTION, - MODELCONDITION_PARTIALLY_CONSTRUCTED, - MODELCONDITION_ACTIVELY_BEING_CONSTRUCTED, - MODELCONDITION_PRONE, - MODELCONDITION_FREEFALL, - MODELCONDITION_ACTIVELY_CONSTRUCTING, - MODELCONDITION_CONSTRUCTION_COMPLETE, - MODELCONDITION_RADAR_EXTENDING, - MODELCONDITION_RADAR_UPGRADED, - MODELCONDITION_PANICKING, // yes, it's spelled with a "k". look it up. - MODELCONDITION_AFLAME, - MODELCONDITION_SMOLDERING, - MODELCONDITION_BURNED, - MODELCONDITION_DOCKING, ///< This encloses the whole time you are Entering, Actioning, and Exiting a dock - MODELCONDITION_DOCKING_BEGINNING, ///< From Enter to Action - MODELCONDITION_DOCKING_ACTIVE, ///< From Action to Exit - MODELCONDITION_DOCKING_ENDING, ///< Exit all the way to next enter (use only animations that end with this) - MODELCONDITION_CARRYING, - MODELCONDITION_FLOODED, - MODELCONDITION_LOADED, // loaded woot! ... like a transport is loaded - MODELCONDITION_JETAFTERBURNER,// shows "flames" for extra motive force (eg, when taking off) - MODELCONDITION_JETEXHAUST, // shows "exhaust" for motive force - MODELCONDITION_PACKING, // packs an object - MODELCONDITION_UNPACKING, // unpacks an object - MODELCONDITION_DEPLOYED, // a deployed object state - MODELCONDITION_OVER_WATER, // Units that can go over water want cool effects for doing so - MODELCONDITION_POWER_PLANT_UPGRADED, // to show special control rods on the cold fusion plant - MODELCONDITION_CLIMBING, //For units climbing up or down cliffs. - MODELCONDITION_SOLD, // object is being sold -#ifdef ALLOW_SURRENDER - MODELCONDITION_SURRENDER, //When units surrender... -#endif - MODELCONDITION_RAPPELLING, - MODELCONDITION_ARMED, // armed like a mine or bomb is armed (not like a human is armed) - MODELCONDITION_POWER_PLANT_UPGRADING, // while special control rods on the cold fusion plant are extending - - //Special model conditions work as following: - //Something turns it on... but a timer in the object will turn them off after a given - //amount of time. If you add any more special animations, then you'll need to add the - //code to turn off the state. - MODELCONDITION_SPECIAL_CHEERING, //When units do a victory cheer (or player initiated cheer). - - MODELCONDITION_CONTINUOUS_FIRE_SLOW, - MODELCONDITION_CONTINUOUS_FIRE_MEAN, - MODELCONDITION_CONTINUOUS_FIRE_FAST, - - MODELCONDITION_RAISING_FLAG, - MODELCONDITION_CAPTURED, - - MODELCONDITION_EXPLODED_FLAILING, - MODELCONDITION_EXPLODED_BOUNCING, - MODELCONDITION_SPLATTED, - - // this is an easier-to-use variant on the whole FIRING_A deal... - // these bits are set if firing, reloading, between shots, or preattack. - MODELCONDITION_USING_WEAPON_A, - MODELCONDITION_USING_WEAPON_B, - MODELCONDITION_USING_WEAPON_C, - - MODELCONDITION_PREORDER, - - MODELCONDITION_CENTER_TO_LEFT, - MODELCONDITION_LEFT_TO_CENTER, - MODELCONDITION_CENTER_TO_RIGHT, - MODELCONDITION_RIGHT_TO_CENTER, - - MODELCONDITION_RIDER1, //Added these for different riders - MODELCONDITION_RIDER2, - MODELCONDITION_RIDER3, - MODELCONDITION_RIDER4, - MODELCONDITION_RIDER5, - MODELCONDITION_RIDER6, - MODELCONDITION_RIDER7, - MODELCONDITION_RIDER8, - - MODELCONDITION_STUNNED_FLAILING, // Daniel Teh's idea, added by Lorenzen, 5/28/03 - MODELCONDITION_STUNNED, - MODELCONDITION_SECOND_LIFE, - MODELCONDITION_JAMMED, ///< Jammed as in missile jammed by ECM - MODELCONDITION_ARMORSET_CRATEUPGRADE_ONE, - MODELCONDITION_ARMORSET_CRATEUPGRADE_TWO, - - MODELCONDITION_USER_1, ///< Wildcard flag to use with upgrade modules or other random little things - MODELCONDITION_USER_2, - - MODELCONDITION_DISGUISED, - - // --- - // New Weaponsets - MODELCONDITION_WEAPONSET_PLAYER_UPGRADE2, - MODELCONDITION_WEAPONSET_PLAYER_UPGRADE3, - MODELCONDITION_WEAPONSET_PLAYER_UPGRADE4, - - // MODELCONDITION_WEAPONSET_CONTAINED, // for new Garrisoned and Contained weaponsets - // MODELCONDITION_WEAPONSET_GARRISONED, // somewhat obsolote since we are usually not visible when contained. - - - // New Weaponslots (4 to 8 -- D to H) - MODELCONDITION_PREATTACK_D, - MODELCONDITION_FIRING_D, - MODELCONDITION_BETWEEN_FIRING_SHOTS_D, - MODELCONDITION_RELOADING_D, - MODELCONDITION_USING_WEAPON_D, - - MODELCONDITION_PREATTACK_E, - MODELCONDITION_FIRING_E, - MODELCONDITION_BETWEEN_FIRING_SHOTS_E, - MODELCONDITION_RELOADING_E, - MODELCONDITION_USING_WEAPON_E, - - MODELCONDITION_PREATTACK_F, - MODELCONDITION_FIRING_F, - MODELCONDITION_BETWEEN_FIRING_SHOTS_F, - MODELCONDITION_RELOADING_F, - MODELCONDITION_USING_WEAPON_F, - - MODELCONDITION_PREATTACK_G, - MODELCONDITION_FIRING_G, - MODELCONDITION_BETWEEN_FIRING_SHOTS_G, - MODELCONDITION_RELOADING_G, - MODELCONDITION_USING_WEAPON_G, - - MODELCONDITION_PREATTACK_H, - MODELCONDITION_FIRING_H, - MODELCONDITION_BETWEEN_FIRING_SHOTS_H, - MODELCONDITION_RELOADING_H, - MODELCONDITION_USING_WEAPON_H, - - // VTOL - MODELCONDITION_TAKEOFF, - MODELCONDITION_LANDING, - - // Teleporter / Chrono Legionnaire - MODELCONDITION_TELEPORT_RECOVER, - -// -// Note: these values are saved in save files, so you MUST NOT REMOVE OR CHANGE -// existing values! -// - - MODELCONDITION_COUNT // keep last! -}; - -//------------------------------------------------------------------------------------------------- - -typedef BitFlags ModelConditionFlags; - -#define MAKE_MODELCONDITION_MASK(k) ModelConditionFlags(ModelConditionFlags::kInit, (k)) -#define MAKE_MODELCONDITION_MASK2(k,a) ModelConditionFlags(ModelConditionFlags::kInit, (k), (a)) -#define MAKE_MODELCONDITION_MASK3(k,a,b) ModelConditionFlags(ModelConditionFlags::kInit, (k), (a), (b)) -#define MAKE_MODELCONDITION_MASK4(k,a,b,c) ModelConditionFlags(ModelConditionFlags::kInit, (k), (a), (b), (c)) -#define MAKE_MODELCONDITION_MASK5(k,a,b,c,d) ModelConditionFlags(ModelConditionFlags::kInit, (k), (a), (b), (c), (d)) -#define MAKE_MODELCONDITION_MASK12(a,b,c,d,e,f,g,h,i,j,k,l) ModelConditionFlags(ModelConditionFlags::kInit, (a), (b), (c), (d), (e), (f), (g), (h), (i), (j), (k), (l)) - -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- - -#endif // _ModelState_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// ModelState.h +// Basic data types needed for the game engine. This is an extension of BaseType.h. +// Author: Michael S. Booth, April 2001 + +#pragma once + +#ifndef _ModelState_H_ +#define _ModelState_H_ + +#include "Lib/BaseType.h" +#include "Common/INI.h" +#include "Common/BitFlags.h" +#include "Common/BitFlagsIO.h" + +//------------------------------------------------------------------------------------------------- + +/** + THE PROBLEM + ----------- + + -- there are lots of different states. (consider that structures can be: day/night, snow/nosnow, + powered/not, garrisoned/empty, damaged/not... you do the math.) + + -- some states are mutually exclusive (idle vs. moving), others are not (snow/nosnow, day/night). + generally, humanoid units have mostly the former, while structure units have mostly the latter. + The current ModelState system really only supports the mutually-exclusive states well. + + -- we'd rather not have to specify every state in the INI files, BUT we do want to be able + to intelligently choose the best model for a given state, whether or not a "real" state exists for it. + + -- it would be desirable to have a unified way of representing "ModelState" so that we don't + have multiple similar-yet-different systems. + + YUCK, WHAT NOW + -------------- + + Let's represent the Model State with two dictinct pieces: + + -- an "ActionState" piece, representing the mutually-exclusive states, which are almost + always an action of some sort + + -- and a "ConditionState" piece, which is a set of bitflags to indicate the static "condition" + of the model. + + Note that these are usually set independently in code, but they are lumped together in order to + determine the actual model to be used. + + (Let's require all objects would be required to have an "Idle" ActionState, which is the + normal, just-sitting there condition.) + + From a code point of view, this becomes an issue of requesting a certain state, and + finding the best-fit match for it. So, what are the rules for finding a good match? + + -- Action states must match exactly. If the desired action state is not found, then the + IDLE state is substituted (but this should generally be considered an error condition). + + -- Condition states choose the match with the closest match among the "Condition" bits in the + INI file, based on satisfying the most of the "required" conditions and the fewest of the + "forbidden" conditions. + +*/ + +#define NUM_MODELCONDITION_DOOR_STATES 4 + +//------------------------------------------------------------------------------------------------- +// IMPORTANT NOTE: you should endeavor to set up states such that the most "normal" +// state is defined by the bit being off. That is, the typical "normal" condition +// has all condition flags set to zero. +enum ModelConditionFlagType CPP_11(: Int) +{ + MODELCONDITION_INVALID = -1, + + MODELCONDITION_FIRST = 0, + +// +// Note: these values are saved in save files, so you MUST NOT REMOVE OR CHANGE +// existing values! +// + MODELCONDITION_TOPPLED = MODELCONDITION_FIRST, + MODELCONDITION_FRONTCRUSHED, + MODELCONDITION_BACKCRUSHED, + MODELCONDITION_DAMAGED, + MODELCONDITION_REALLY_DAMAGED, + MODELCONDITION_RUBBLE, + MODELCONDITION_SPECIAL_DAMAGED, + MODELCONDITION_NIGHT, + MODELCONDITION_SNOW, + MODELCONDITION_PARACHUTING, + MODELCONDITION_GARRISONED, + MODELCONDITION_ENEMYNEAR, + MODELCONDITION_WEAPONSET_VETERAN, + MODELCONDITION_WEAPONSET_ELITE, + MODELCONDITION_WEAPONSET_HERO, + MODELCONDITION_WEAPONSET_CRATEUPGRADE_ONE, + MODELCONDITION_WEAPONSET_CRATEUPGRADE_TWO, + MODELCONDITION_WEAPONSET_PLAYER_UPGRADE, + MODELCONDITION_DOOR_1_OPENING, + MODELCONDITION_DOOR_1_CLOSING, + MODELCONDITION_DOOR_1_WAITING_OPEN, + MODELCONDITION_DOOR_1_WAITING_TO_CLOSE, + MODELCONDITION_DOOR_2_OPENING, + MODELCONDITION_DOOR_2_CLOSING, + MODELCONDITION_DOOR_2_WAITING_OPEN, + MODELCONDITION_DOOR_2_WAITING_TO_CLOSE, + MODELCONDITION_DOOR_3_OPENING, + MODELCONDITION_DOOR_3_CLOSING, + MODELCONDITION_DOOR_3_WAITING_OPEN, + MODELCONDITION_DOOR_3_WAITING_TO_CLOSE, + MODELCONDITION_DOOR_4_OPENING, + MODELCONDITION_DOOR_4_CLOSING, + MODELCONDITION_DOOR_4_WAITING_OPEN, + MODELCONDITION_DOOR_4_WAITING_TO_CLOSE, + MODELCONDITION_ATTACKING, //Simply set when a unit is fighting -- terrorist moving with a target will flail arms like a psycho. + MODELCONDITION_PREATTACK_A, //Use for pre-attack animations (like aiming, pulling out a knife, or detonating explosives). + MODELCONDITION_FIRING_A, + MODELCONDITION_BETWEEN_FIRING_SHOTS_A, + MODELCONDITION_RELOADING_A, + MODELCONDITION_PREATTACK_B, //Use for pre-attack animations (like aiming, pulling out a knife, or detonating explosives). + MODELCONDITION_FIRING_B, + MODELCONDITION_BETWEEN_FIRING_SHOTS_B, + MODELCONDITION_RELOADING_B, + MODELCONDITION_PREATTACK_C, //Use for pre-attack animations (like aiming, pulling out a knife, or detonating explosives). + MODELCONDITION_FIRING_C, + MODELCONDITION_BETWEEN_FIRING_SHOTS_C, + MODELCONDITION_RELOADING_C, + MODELCONDITION_TURRET_ROTATE, + MODELCONDITION_POST_COLLAPSE, + MODELCONDITION_MOVING, + MODELCONDITION_DYING, + MODELCONDITION_AWAITING_CONSTRUCTION, + MODELCONDITION_PARTIALLY_CONSTRUCTED, + MODELCONDITION_ACTIVELY_BEING_CONSTRUCTED, + MODELCONDITION_PRONE, + MODELCONDITION_FREEFALL, + MODELCONDITION_ACTIVELY_CONSTRUCTING, + MODELCONDITION_CONSTRUCTION_COMPLETE, + MODELCONDITION_RADAR_EXTENDING, + MODELCONDITION_RADAR_UPGRADED, + MODELCONDITION_PANICKING, // yes, it's spelled with a "k". look it up. + MODELCONDITION_AFLAME, + MODELCONDITION_SMOLDERING, + MODELCONDITION_BURNED, + MODELCONDITION_DOCKING, ///< This encloses the whole time you are Entering, Actioning, and Exiting a dock + MODELCONDITION_DOCKING_BEGINNING, ///< From Enter to Action + MODELCONDITION_DOCKING_ACTIVE, ///< From Action to Exit + MODELCONDITION_DOCKING_ENDING, ///< Exit all the way to next enter (use only animations that end with this) + MODELCONDITION_CARRYING, + MODELCONDITION_FLOODED, + MODELCONDITION_LOADED, // loaded woot! ... like a transport is loaded + MODELCONDITION_JETAFTERBURNER,// shows "flames" for extra motive force (eg, when taking off) + MODELCONDITION_JETEXHAUST, // shows "exhaust" for motive force + MODELCONDITION_PACKING, // packs an object + MODELCONDITION_UNPACKING, // unpacks an object + MODELCONDITION_DEPLOYED, // a deployed object state + MODELCONDITION_OVER_WATER, // Units that can go over water want cool effects for doing so + MODELCONDITION_POWER_PLANT_UPGRADED, // to show special control rods on the cold fusion plant + MODELCONDITION_CLIMBING, //For units climbing up or down cliffs. + MODELCONDITION_SOLD, // object is being sold +#ifdef ALLOW_SURRENDER + MODELCONDITION_SURRENDER, //When units surrender... +#endif + MODELCONDITION_RAPPELLING, + MODELCONDITION_ARMED, // armed like a mine or bomb is armed (not like a human is armed) + MODELCONDITION_POWER_PLANT_UPGRADING, // while special control rods on the cold fusion plant are extending + + //Special model conditions work as following: + //Something turns it on... but a timer in the object will turn them off after a given + //amount of time. If you add any more special animations, then you'll need to add the + //code to turn off the state. + MODELCONDITION_SPECIAL_CHEERING, //When units do a victory cheer (or player initiated cheer). + + MODELCONDITION_CONTINUOUS_FIRE_SLOW, + MODELCONDITION_CONTINUOUS_FIRE_MEAN, + MODELCONDITION_CONTINUOUS_FIRE_FAST, + + MODELCONDITION_RAISING_FLAG, + MODELCONDITION_CAPTURED, + + MODELCONDITION_EXPLODED_FLAILING, + MODELCONDITION_EXPLODED_BOUNCING, + MODELCONDITION_SPLATTED, + + // this is an easier-to-use variant on the whole FIRING_A deal... + // these bits are set if firing, reloading, between shots, or preattack. + MODELCONDITION_USING_WEAPON_A, + MODELCONDITION_USING_WEAPON_B, + MODELCONDITION_USING_WEAPON_C, + + MODELCONDITION_PREORDER, + + MODELCONDITION_CENTER_TO_LEFT, + MODELCONDITION_LEFT_TO_CENTER, + MODELCONDITION_CENTER_TO_RIGHT, + MODELCONDITION_RIGHT_TO_CENTER, + + MODELCONDITION_RIDER1, //Added these for different riders + MODELCONDITION_RIDER2, + MODELCONDITION_RIDER3, + MODELCONDITION_RIDER4, + MODELCONDITION_RIDER5, + MODELCONDITION_RIDER6, + MODELCONDITION_RIDER7, + MODELCONDITION_RIDER8, + + MODELCONDITION_STUNNED_FLAILING, // Daniel Teh's idea, added by Lorenzen, 5/28/03 + MODELCONDITION_STUNNED, + MODELCONDITION_SECOND_LIFE, + MODELCONDITION_JAMMED, ///< Jammed as in missile jammed by ECM + MODELCONDITION_ARMORSET_CRATEUPGRADE_ONE, + MODELCONDITION_ARMORSET_CRATEUPGRADE_TWO, + + MODELCONDITION_USER_1, ///< Wildcard flag to use with upgrade modules or other random little things + MODELCONDITION_USER_2, + + MODELCONDITION_DISGUISED, + + // --- + // New Weaponsets + MODELCONDITION_WEAPONSET_PLAYER_UPGRADE2, + MODELCONDITION_WEAPONSET_PLAYER_UPGRADE3, + MODELCONDITION_WEAPONSET_PLAYER_UPGRADE4, + + // MODELCONDITION_WEAPONSET_CONTAINED, // for new Garrisoned and Contained weaponsets + // MODELCONDITION_WEAPONSET_GARRISONED, // somewhat obsolote since we are usually not visible when contained. + + + // New Weaponslots (4 to 8 -- D to H) + MODELCONDITION_PREATTACK_D, + MODELCONDITION_FIRING_D, + MODELCONDITION_BETWEEN_FIRING_SHOTS_D, + MODELCONDITION_RELOADING_D, + MODELCONDITION_USING_WEAPON_D, + + MODELCONDITION_PREATTACK_E, + MODELCONDITION_FIRING_E, + MODELCONDITION_BETWEEN_FIRING_SHOTS_E, + MODELCONDITION_RELOADING_E, + MODELCONDITION_USING_WEAPON_E, + + MODELCONDITION_PREATTACK_F, + MODELCONDITION_FIRING_F, + MODELCONDITION_BETWEEN_FIRING_SHOTS_F, + MODELCONDITION_RELOADING_F, + MODELCONDITION_USING_WEAPON_F, + + MODELCONDITION_PREATTACK_G, + MODELCONDITION_FIRING_G, + MODELCONDITION_BETWEEN_FIRING_SHOTS_G, + MODELCONDITION_RELOADING_G, + MODELCONDITION_USING_WEAPON_G, + + MODELCONDITION_PREATTACK_H, + MODELCONDITION_FIRING_H, + MODELCONDITION_BETWEEN_FIRING_SHOTS_H, + MODELCONDITION_RELOADING_H, + MODELCONDITION_USING_WEAPON_H, + + // VTOL + MODELCONDITION_TAKEOFF, + MODELCONDITION_LANDING, + + // Teleporter / Chrono Legionnaire + MODELCONDITION_TELEPORT_RECOVER, + +// +// Note: these values are saved in save files, so you MUST NOT REMOVE OR CHANGE +// existing values! +// + + MODELCONDITION_COUNT // keep last! +}; + +//------------------------------------------------------------------------------------------------- + +typedef BitFlags ModelConditionFlags; + +#define MAKE_MODELCONDITION_MASK(k) ModelConditionFlags(ModelConditionFlags::kInit, (k)) +#define MAKE_MODELCONDITION_MASK2(k,a) ModelConditionFlags(ModelConditionFlags::kInit, (k), (a)) +#define MAKE_MODELCONDITION_MASK3(k,a,b) ModelConditionFlags(ModelConditionFlags::kInit, (k), (a), (b)) +#define MAKE_MODELCONDITION_MASK4(k,a,b,c) ModelConditionFlags(ModelConditionFlags::kInit, (k), (a), (b), (c)) +#define MAKE_MODELCONDITION_MASK5(k,a,b,c,d) ModelConditionFlags(ModelConditionFlags::kInit, (k), (a), (b), (c), (d)) +#define MAKE_MODELCONDITION_MASK12(a,b,c,d,e,f,g,h,i,j,k,l) ModelConditionFlags(ModelConditionFlags::kInit, (a), (b), (c), (d), (e), (f), (g), (h), (i), (j), (k), (l)) + +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- + +#endif // _ModelState_H_ + diff --git a/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp b/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp index f515d93e9bf..cf50f5ac7bb 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp @@ -1,235 +1,235 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: BitFlags.cpp /////////////////////////////////////////////////////////// -// -// Used to set detail levels of various game systems. -// Steven Johnson, Sept 2002 -// -// -/////////////////////////////////////////////////////////////////////////////// - -#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine - -#include "Common/BitFlags.h" -#include "Common/BitFlagsIO.h" -#include "Common/ModelState.h" -#include "GameLogic/ArmorSet.h" - -const char* ModelConditionFlags::s_bitNameList[] = -{ - "TOPPLED", - "FRONTCRUSHED", - "BACKCRUSHED", - "DAMAGED", - "REALLYDAMAGED", - "RUBBLE", - "SPECIAL_DAMAGED", - "NIGHT", - "SNOW", - "PARACHUTING", - "GARRISONED", - "ENEMYNEAR", - "WEAPONSET_VETERAN", - "WEAPONSET_ELITE", - "WEAPONSET_HERO", - "WEAPONSET_CRATEUPGRADE_ONE", - "WEAPONSET_CRATEUPGRADE_TWO", - "WEAPONSET_PLAYER_UPGRADE", - "DOOR_1_OPENING", - "DOOR_1_CLOSING", - "DOOR_1_WAITING_OPEN", - "DOOR_1_WAITING_TO_CLOSE", - "DOOR_2_OPENING", - "DOOR_2_CLOSING", - "DOOR_2_WAITING_OPEN", - "DOOR_2_WAITING_TO_CLOSE", - "DOOR_3_OPENING", - "DOOR_3_CLOSING", - "DOOR_3_WAITING_OPEN", - "DOOR_3_WAITING_TO_CLOSE", - "DOOR_4_OPENING", - "DOOR_4_CLOSING", - "DOOR_4_WAITING_OPEN", - "DOOR_4_WAITING_TO_CLOSE", - "ATTACKING", - "PREATTACK_A", - "FIRING_A", - "BETWEEN_FIRING_SHOTS_A", - "RELOADING_A", - "PREATTACK_B", - "FIRING_B", - "BETWEEN_FIRING_SHOTS_B", - "RELOADING_B", - "PREATTACK_C", - "FIRING_C", - "BETWEEN_FIRING_SHOTS_C", - "RELOADING_C", - "TURRET_ROTATE", - "POST_COLLAPSE", - "MOVING", - "DYING", - "AWAITING_CONSTRUCTION", - "PARTIALLY_CONSTRUCTED", - "ACTIVELY_BEING_CONSTRUCTED", - "PRONE", - "FREEFALL", - "ACTIVELY_CONSTRUCTING", - "CONSTRUCTION_COMPLETE", - "RADAR_EXTENDING", - "RADAR_UPGRADED", - "PANICKING", // yes, it's spelled with a "k". look it up. - "AFLAME", - "SMOLDERING", - "BURNED", - "DOCKING", - "DOCKING_BEGINNING", - "DOCKING_ACTIVE", - "DOCKING_ENDING", - "CARRYING", - "FLOODED", - "LOADED", - "JETAFTERBURNER", - "JETEXHAUST", - "PACKING", - "UNPACKING", - "DEPLOYED", - "OVER_WATER", - "POWER_PLANT_UPGRADED", - "CLIMBING", - "SOLD", -#ifdef ALLOW_SURRENDER - "SURRENDER", -#endif - "RAPPELLING", - "ARMED", - "POWER_PLANT_UPGRADING", - - "SPECIAL_CHEERING", - - "CONTINUOUS_FIRE_SLOW", - "CONTINUOUS_FIRE_MEAN", - "CONTINUOUS_FIRE_FAST", - - "RAISING_FLAG", - "CAPTURED", - - "EXPLODED_FLAILING", - "EXPLODED_BOUNCING", - "SPLATTED", - - "USING_WEAPON_A", - "USING_WEAPON_B", - "USING_WEAPON_C", - - "PREORDER", - - "CENTER_TO_LEFT", - "LEFT_TO_CENTER", - "CENTER_TO_RIGHT", - "RIGHT_TO_CENTER", - - "RIDER1", //Kris: Added these for different combat-bike riders, but feel free to use these for anything. - "RIDER2", - "RIDER3", - "RIDER4", - "RIDER5", - "RIDER6", - "RIDER7", - "RIDER8", - - "STUNNED_FLAILING", // Daniel Teh's idea, added by Lorenzen, 5/28/03 - "STUNNED", - "SECOND_LIFE", - "JAMMED", - "ARMORSET_CRATEUPGRADE_ONE", - "ARMORSET_CRATEUPGRADE_TWO", - - "USER_1", - "USER_2", - - "DISGUISED", - - // New Weaponsets - "WEAPONSET_PLAYER_UPGRADE2", - "WEAPONSET_PLAYER_UPGRADE3", - "WEAPONSET_PLAYER_UPGRADE4", - - // New Weaponslots (D-H) - - "PREATTACK_D", - "FIRING_D", - "BETWEEN_FIRING_SHOTS_D", - "RELOADING_D", - "USING_WEAPON_D", - - "PREATTACK_E", - "FIRING_E", - "BETWEEN_FIRING_SHOTS_E", - "RELOADING_E", - "USING_WEAPON_E", - - "PREATTACK_F", - "FIRING_F", - "BETWEEN_FIRING_SHOTS_F", - "RELOADING_F", - "USING_WEAPON_F", - - "PREATTACK_G", - "FIRING_G", - "BETWEEN_FIRING_SHOTS_G", - "RELOADING_G", - "USING_WEAPON_G", - - "PREATTACK_H", - "FIRING_H", - "BETWEEN_FIRING_SHOTS_H", - "RELOADING_H", - "USING_WEAPON_H", - - "TAKEOFF", - "LANDING", - - "TELEPORT_RECOVER", - - NULL -}; - -const char* ArmorSetFlags::s_bitNameList[] = -{ - "VETERAN", - "ELITE", - "HERO", - "PLAYER_UPGRADE", - "WEAK_VERSUS_BASEDEFENSES", - "SECOND_LIFE", - "CRATE_UPGRADE_ONE", - "CRATE_UPGRADE_TWO", - "PLAYER_UPGRADE2", - "PLAYER_UPGRADE3", - "PLAYER_UPGRADE4", - - NULL -}; - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: BitFlags.cpp /////////////////////////////////////////////////////////// +// +// Used to set detail levels of various game systems. +// Steven Johnson, Sept 2002 +// +// +/////////////////////////////////////////////////////////////////////////////// + +#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine + +#include "Common/BitFlags.h" +#include "Common/BitFlagsIO.h" +#include "Common/ModelState.h" +#include "GameLogic/ArmorSet.h" + +const char* ModelConditionFlags::s_bitNameList[] = +{ + "TOPPLED", + "FRONTCRUSHED", + "BACKCRUSHED", + "DAMAGED", + "REALLYDAMAGED", + "RUBBLE", + "SPECIAL_DAMAGED", + "NIGHT", + "SNOW", + "PARACHUTING", + "GARRISONED", + "ENEMYNEAR", + "WEAPONSET_VETERAN", + "WEAPONSET_ELITE", + "WEAPONSET_HERO", + "WEAPONSET_CRATEUPGRADE_ONE", + "WEAPONSET_CRATEUPGRADE_TWO", + "WEAPONSET_PLAYER_UPGRADE", + "DOOR_1_OPENING", + "DOOR_1_CLOSING", + "DOOR_1_WAITING_OPEN", + "DOOR_1_WAITING_TO_CLOSE", + "DOOR_2_OPENING", + "DOOR_2_CLOSING", + "DOOR_2_WAITING_OPEN", + "DOOR_2_WAITING_TO_CLOSE", + "DOOR_3_OPENING", + "DOOR_3_CLOSING", + "DOOR_3_WAITING_OPEN", + "DOOR_3_WAITING_TO_CLOSE", + "DOOR_4_OPENING", + "DOOR_4_CLOSING", + "DOOR_4_WAITING_OPEN", + "DOOR_4_WAITING_TO_CLOSE", + "ATTACKING", + "PREATTACK_A", + "FIRING_A", + "BETWEEN_FIRING_SHOTS_A", + "RELOADING_A", + "PREATTACK_B", + "FIRING_B", + "BETWEEN_FIRING_SHOTS_B", + "RELOADING_B", + "PREATTACK_C", + "FIRING_C", + "BETWEEN_FIRING_SHOTS_C", + "RELOADING_C", + "TURRET_ROTATE", + "POST_COLLAPSE", + "MOVING", + "DYING", + "AWAITING_CONSTRUCTION", + "PARTIALLY_CONSTRUCTED", + "ACTIVELY_BEING_CONSTRUCTED", + "PRONE", + "FREEFALL", + "ACTIVELY_CONSTRUCTING", + "CONSTRUCTION_COMPLETE", + "RADAR_EXTENDING", + "RADAR_UPGRADED", + "PANICKING", // yes, it's spelled with a "k". look it up. + "AFLAME", + "SMOLDERING", + "BURNED", + "DOCKING", + "DOCKING_BEGINNING", + "DOCKING_ACTIVE", + "DOCKING_ENDING", + "CARRYING", + "FLOODED", + "LOADED", + "JETAFTERBURNER", + "JETEXHAUST", + "PACKING", + "UNPACKING", + "DEPLOYED", + "OVER_WATER", + "POWER_PLANT_UPGRADED", + "CLIMBING", + "SOLD", +#ifdef ALLOW_SURRENDER + "SURRENDER", +#endif + "RAPPELLING", + "ARMED", + "POWER_PLANT_UPGRADING", + + "SPECIAL_CHEERING", + + "CONTINUOUS_FIRE_SLOW", + "CONTINUOUS_FIRE_MEAN", + "CONTINUOUS_FIRE_FAST", + + "RAISING_FLAG", + "CAPTURED", + + "EXPLODED_FLAILING", + "EXPLODED_BOUNCING", + "SPLATTED", + + "USING_WEAPON_A", + "USING_WEAPON_B", + "USING_WEAPON_C", + + "PREORDER", + + "CENTER_TO_LEFT", + "LEFT_TO_CENTER", + "CENTER_TO_RIGHT", + "RIGHT_TO_CENTER", + + "RIDER1", //Kris: Added these for different combat-bike riders, but feel free to use these for anything. + "RIDER2", + "RIDER3", + "RIDER4", + "RIDER5", + "RIDER6", + "RIDER7", + "RIDER8", + + "STUNNED_FLAILING", // Daniel Teh's idea, added by Lorenzen, 5/28/03 + "STUNNED", + "SECOND_LIFE", + "JAMMED", + "ARMORSET_CRATEUPGRADE_ONE", + "ARMORSET_CRATEUPGRADE_TWO", + + "USER_1", + "USER_2", + + "DISGUISED", + + // New Weaponsets + "WEAPONSET_PLAYER_UPGRADE2", + "WEAPONSET_PLAYER_UPGRADE3", + "WEAPONSET_PLAYER_UPGRADE4", + + // New Weaponslots (D-H) + + "PREATTACK_D", + "FIRING_D", + "BETWEEN_FIRING_SHOTS_D", + "RELOADING_D", + "USING_WEAPON_D", + + "PREATTACK_E", + "FIRING_E", + "BETWEEN_FIRING_SHOTS_E", + "RELOADING_E", + "USING_WEAPON_E", + + "PREATTACK_F", + "FIRING_F", + "BETWEEN_FIRING_SHOTS_F", + "RELOADING_F", + "USING_WEAPON_F", + + "PREATTACK_G", + "FIRING_G", + "BETWEEN_FIRING_SHOTS_G", + "RELOADING_G", + "USING_WEAPON_G", + + "PREATTACK_H", + "FIRING_H", + "BETWEEN_FIRING_SHOTS_H", + "RELOADING_H", + "USING_WEAPON_H", + + "TAKEOFF", + "LANDING", + + "TELEPORT_RECOVER", + + NULL +}; + +const char* ArmorSetFlags::s_bitNameList[] = +{ + "VETERAN", + "ELITE", + "HERO", + "PLAYER_UPGRADE", + "WEAK_VERSUS_BASEDEFENSES", + "SECOND_LIFE", + "CRATE_UPGRADE_ONE", + "CRATE_UPGRADE_TWO", + "PLAYER_UPGRADE2", + "PLAYER_UPGRADE3", + "PLAYER_UPGRADE4", + + NULL +}; + diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp index a05408e3058..b9783f48d00 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp @@ -1,214 +1,214 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// Kindof.cpp ///////////////////////////////////////////////////////////////////////////////////// -// Part of header detangling -// John McDonald, Aug 2002 - -#include "PreRTS.h" - -#include "Common/KindOf.h" -#include "Common/BitFlagsIO.h" - -const char* KindOfMaskType::s_bitNameList[] = -{ - "OBSTACLE", - "SELECTABLE", - "IMMOBILE", - "CAN_ATTACK", - "STICK_TO_TERRAIN_SLOPE", - "CAN_CAST_REFLECTIONS", - "SHRUBBERY", - "STRUCTURE", - "INFANTRY", - "VEHICLE", - "AIRCRAFT", - "HUGE_VEHICLE", - "DOZER", - "HARVESTER", - "COMMANDCENTER", -#ifdef ALLOW_SURRENDER - "PRISON", - "COLLECTS_PRISON_BOUNTY", - "POW_TRUCK", -#endif - "LINEBUILD", - "SALVAGER", - "WEAPON_SALVAGER", - "TRANSPORT", - "BRIDGE", - "LANDMARK_BRIDGE", - "BRIDGE_TOWER", - "PROJECTILE", - "PRELOAD", - "NO_GARRISON", - "WAVEGUIDE", - "WAVE_EFFECT", - "NO_COLLIDE", - "REPAIR_PAD", - "HEAL_PAD", - "STEALTH_GARRISON", - "CASH_GENERATOR", - "DRAWABLE_ONLY", - "MP_COUNT_FOR_VICTORY", - "REBUILD_HOLE", - "SCORE", - "SCORE_CREATE", - "SCORE_DESTROY", - "NO_HEAL_ICON", - "CAN_RAPPEL", - "PARACHUTABLE", -#ifdef ALLOW_SURRENDER - "CAN_SURRENDER", -#endif - "CAN_BE_REPULSED", - "MOB_NEXUS", - "IGNORED_IN_GUI", - "CRATE", - "CAPTURABLE", - "CLEARED_BY_BUILD", - "SMALL_MISSILE", - "ALWAYS_VISIBLE", - "UNATTACKABLE", - "MINE", - "CLEANUP_HAZARD", - "PORTABLE_STRUCTURE", - "ALWAYS_SELECTABLE", - "ATTACK_NEEDS_LINE_OF_SIGHT", - "WALK_ON_TOP_OF_WALL", - "DEFENSIVE_WALL", - "FS_POWER", - "FS_FACTORY", - "FS_BASE_DEFENSE", - "FS_TECHNOLOGY", - "AIRCRAFT_PATH_AROUND", - "LOW_OVERLAPPABLE", - "FORCEATTACKABLE", - "AUTO_RALLYPOINT", - "TECH_BUILDING", - "POWERED", - "PRODUCED_AT_HELIPAD", - "DRONE", - "CAN_SEE_THROUGH_STRUCTURE", - "BALLISTIC_MISSILE", - "CLICK_THROUGH", - "SUPPLY_SOURCE_ON_PREVIEW", - "PARACHUTE", - "GARRISONABLE_UNTIL_DESTROYED", - "BOAT", - "IMMUNE_TO_CAPTURE", - "HULK", - "SHOW_PORTRAIT_WHEN_CONTROLLED", - "SPAWNS_ARE_THE_WEAPONS", - "CANNOT_BUILD_NEAR_SUPPLIES", - "SUPPLY_SOURCE", - "REVEAL_TO_ALL", - "DISGUISER", - "INERT", - "HERO", - "IGNORES_SELECT_ALL", - "DONT_AUTO_CRUSH_INFANTRY", - "CLIFF_JUMPER", - "FS_SUPPLY_DROPZONE", - "FS_SUPERWEAPON", - "FS_BLACK_MARKET", - "FS_SUPPLY_CENTER", - "FS_STRATEGY_CENTER", - "MONEY_HACKER", - "ARMOR_SALVAGER", - "REVEALS_ENEMY_PATHS", - "BOOBY_TRAP", - "FS_FAKE", - "FS_INTERNET_CENTER", - "BLAST_CRATER", - "PROP", - "OPTIMIZED_TREE", - "FS_ADVANCED_TECH", - "FS_BARRACKS", - "FS_WARFACTORY", - "FS_AIRFIELD", - "AIRCRAFT_CARRIER", - "NO_SELECT", - "REJECT_UNMANNED", - "CANNOT_RETALIATE", - "TECH_BASE_DEFENSE", - "EMP_HARDENED", - "DEMOTRAP", - "CONSERVATIVE_BUILDING", - "IGNORE_DOCKING_BONES", - - "VTOL", - "LARGE_AIRCRAFT", - "MEDIUM_AIRCRAFT", - "SMALL_AIRCRAFT", - "ARTILLERY", - "HEAVY_ARTILLERY", - "ANTI_AIR", - "SCOUT", - "COMMANDO", - "HEAVY_INFANTRY", - "SUPERHEAVY_VEHICLE", - - "TELEPORTER", - - "EXTRA1", - "EXTRA2", - "EXTRA3", - "EXTRA4", - "EXTRA5", - "EXTRA6", - "EXTRA7", - "EXTRA8", - "EXTRA9", - "EXTRA10", - "EXTRA11", - "EXTRA12", - "EXTRA13", - "EXTRA14", - "EXTRA15", - "EXTRA16", - - NULL -}; - -KindOfMaskType KINDOFMASK_NONE; // inits to all zeroes -KindOfMaskType KINDOFMASK_FS; // inits to all zeroes - -void initKindOfMasks() -{ - KINDOFMASK_FS.set( KINDOF_FS_FACTORY ); - KINDOFMASK_FS.set( KINDOF_FS_BASE_DEFENSE ); - KINDOFMASK_FS.set( KINDOF_FS_TECHNOLOGY ); - KINDOFMASK_FS.set( KINDOF_FS_SUPPLY_DROPZONE ); - KINDOFMASK_FS.set( KINDOF_FS_SUPERWEAPON ); - KINDOFMASK_FS.set( KINDOF_FS_BLACK_MARKET ); - KINDOFMASK_FS.set( KINDOF_FS_SUPPLY_CENTER ); - KINDOFMASK_FS.set( KINDOF_FS_STRATEGY_CENTER ); - KINDOFMASK_FS.set( KINDOF_FS_FAKE ); - KINDOFMASK_FS.set( KINDOF_FS_INTERNET_CENTER ); - KINDOFMASK_FS.set( KINDOF_FS_ADVANCED_TECH ); - KINDOFMASK_FS.set( KINDOF_FS_BARRACKS ); - KINDOFMASK_FS.set( KINDOF_FS_WARFACTORY ); - KINDOFMASK_FS.set( KINDOF_FS_AIRFIELD ); -} +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// Kindof.cpp ///////////////////////////////////////////////////////////////////////////////////// +// Part of header detangling +// John McDonald, Aug 2002 + +#include "PreRTS.h" + +#include "Common/KindOf.h" +#include "Common/BitFlagsIO.h" + +const char* KindOfMaskType::s_bitNameList[] = +{ + "OBSTACLE", + "SELECTABLE", + "IMMOBILE", + "CAN_ATTACK", + "STICK_TO_TERRAIN_SLOPE", + "CAN_CAST_REFLECTIONS", + "SHRUBBERY", + "STRUCTURE", + "INFANTRY", + "VEHICLE", + "AIRCRAFT", + "HUGE_VEHICLE", + "DOZER", + "HARVESTER", + "COMMANDCENTER", +#ifdef ALLOW_SURRENDER + "PRISON", + "COLLECTS_PRISON_BOUNTY", + "POW_TRUCK", +#endif + "LINEBUILD", + "SALVAGER", + "WEAPON_SALVAGER", + "TRANSPORT", + "BRIDGE", + "LANDMARK_BRIDGE", + "BRIDGE_TOWER", + "PROJECTILE", + "PRELOAD", + "NO_GARRISON", + "WAVEGUIDE", + "WAVE_EFFECT", + "NO_COLLIDE", + "REPAIR_PAD", + "HEAL_PAD", + "STEALTH_GARRISON", + "CASH_GENERATOR", + "DRAWABLE_ONLY", + "MP_COUNT_FOR_VICTORY", + "REBUILD_HOLE", + "SCORE", + "SCORE_CREATE", + "SCORE_DESTROY", + "NO_HEAL_ICON", + "CAN_RAPPEL", + "PARACHUTABLE", +#ifdef ALLOW_SURRENDER + "CAN_SURRENDER", +#endif + "CAN_BE_REPULSED", + "MOB_NEXUS", + "IGNORED_IN_GUI", + "CRATE", + "CAPTURABLE", + "CLEARED_BY_BUILD", + "SMALL_MISSILE", + "ALWAYS_VISIBLE", + "UNATTACKABLE", + "MINE", + "CLEANUP_HAZARD", + "PORTABLE_STRUCTURE", + "ALWAYS_SELECTABLE", + "ATTACK_NEEDS_LINE_OF_SIGHT", + "WALK_ON_TOP_OF_WALL", + "DEFENSIVE_WALL", + "FS_POWER", + "FS_FACTORY", + "FS_BASE_DEFENSE", + "FS_TECHNOLOGY", + "AIRCRAFT_PATH_AROUND", + "LOW_OVERLAPPABLE", + "FORCEATTACKABLE", + "AUTO_RALLYPOINT", + "TECH_BUILDING", + "POWERED", + "PRODUCED_AT_HELIPAD", + "DRONE", + "CAN_SEE_THROUGH_STRUCTURE", + "BALLISTIC_MISSILE", + "CLICK_THROUGH", + "SUPPLY_SOURCE_ON_PREVIEW", + "PARACHUTE", + "GARRISONABLE_UNTIL_DESTROYED", + "BOAT", + "IMMUNE_TO_CAPTURE", + "HULK", + "SHOW_PORTRAIT_WHEN_CONTROLLED", + "SPAWNS_ARE_THE_WEAPONS", + "CANNOT_BUILD_NEAR_SUPPLIES", + "SUPPLY_SOURCE", + "REVEAL_TO_ALL", + "DISGUISER", + "INERT", + "HERO", + "IGNORES_SELECT_ALL", + "DONT_AUTO_CRUSH_INFANTRY", + "CLIFF_JUMPER", + "FS_SUPPLY_DROPZONE", + "FS_SUPERWEAPON", + "FS_BLACK_MARKET", + "FS_SUPPLY_CENTER", + "FS_STRATEGY_CENTER", + "MONEY_HACKER", + "ARMOR_SALVAGER", + "REVEALS_ENEMY_PATHS", + "BOOBY_TRAP", + "FS_FAKE", + "FS_INTERNET_CENTER", + "BLAST_CRATER", + "PROP", + "OPTIMIZED_TREE", + "FS_ADVANCED_TECH", + "FS_BARRACKS", + "FS_WARFACTORY", + "FS_AIRFIELD", + "AIRCRAFT_CARRIER", + "NO_SELECT", + "REJECT_UNMANNED", + "CANNOT_RETALIATE", + "TECH_BASE_DEFENSE", + "EMP_HARDENED", + "DEMOTRAP", + "CONSERVATIVE_BUILDING", + "IGNORE_DOCKING_BONES", + + "VTOL", + "LARGE_AIRCRAFT", + "MEDIUM_AIRCRAFT", + "SMALL_AIRCRAFT", + "ARTILLERY", + "HEAVY_ARTILLERY", + "ANTI_AIR", + "SCOUT", + "COMMANDO", + "HEAVY_INFANTRY", + "SUPERHEAVY_VEHICLE", + + "TELEPORTER", + + "EXTRA1", + "EXTRA2", + "EXTRA3", + "EXTRA4", + "EXTRA5", + "EXTRA6", + "EXTRA7", + "EXTRA8", + "EXTRA9", + "EXTRA10", + "EXTRA11", + "EXTRA12", + "EXTRA13", + "EXTRA14", + "EXTRA15", + "EXTRA16", + + NULL +}; + +KindOfMaskType KINDOFMASK_NONE; // inits to all zeroes +KindOfMaskType KINDOFMASK_FS; // inits to all zeroes + +void initKindOfMasks() +{ + KINDOFMASK_FS.set( KINDOF_FS_FACTORY ); + KINDOFMASK_FS.set( KINDOF_FS_BASE_DEFENSE ); + KINDOFMASK_FS.set( KINDOF_FS_TECHNOLOGY ); + KINDOFMASK_FS.set( KINDOF_FS_SUPPLY_DROPZONE ); + KINDOFMASK_FS.set( KINDOF_FS_SUPERWEAPON ); + KINDOFMASK_FS.set( KINDOF_FS_BLACK_MARKET ); + KINDOFMASK_FS.set( KINDOF_FS_SUPPLY_CENTER ); + KINDOFMASK_FS.set( KINDOF_FS_STRATEGY_CENTER ); + KINDOFMASK_FS.set( KINDOF_FS_FAKE ); + KINDOFMASK_FS.set( KINDOF_FS_INTERNET_CENTER ); + KINDOFMASK_FS.set( KINDOF_FS_ADVANCED_TECH ); + KINDOFMASK_FS.set( KINDOF_FS_BARRACKS ); + KINDOFMASK_FS.set( KINDOF_FS_WARFACTORY ); + KINDOFMASK_FS.set( KINDOF_FS_AIRFIELD ); +} diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp index 7a7f1c45c12..8e69cffbe68 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp @@ -1,814 +1,814 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: MemoryInit.cpp -//----------------------------------------------------------------------------- -// -// Westwood Studios Pacific. -// -// Confidential Information -// Copyright (C) 2001 - All Rights Reserved -// -//----------------------------------------------------------------------------- -// -// Project: RTS3 -// -// File name: MemoryInit.cpp -// -// Created: Steven Johnson, August 2001 -// -// Desc: Memory manager -// -// ---------------------------------------------------------------------------- -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -// SYSTEM INCLUDES - -// USER INCLUDES -#include "Lib/BaseType.h" -#include "Common/GameMemory.h" - -#ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -//----------------------------------------------------------------------------- -void userMemoryManagerGetDmaParms(Int *numSubPools, const PoolInitRec **pParms) -{ - static const PoolInitRec defaultDMA[7] = - { - // name, allocsize, initialcount, overflowcount - { "dmaPool_16", 16, 130000, 10000 }, - { "dmaPool_32", 32, 250000, 10000 }, - { "dmaPool_64", 64, 100000, 10000 }, - { "dmaPool_128", 128, 80000, 10000 }, - { "dmaPool_256", 256, 20000, 5000 }, - { "dmaPool_512", 512, 16000, 5000 }, - { "dmaPool_1024", 1024, 6000, 1024} - }; - - *numSubPools = 7; - *pParms = defaultDMA; -} - -//----------------------------------------------------------------------------- -struct PoolSizeRec -{ - const char* name; - Int initial; - Int overflow; -}; - -//----------------------------------------------------------------------------- -// And please be careful of duplicates. They are not rejected. -// not const -- we might override from INI -static PoolSizeRec sizes[] = -{ - { "PartitionContactListNode", 2048, 512 }, - { "BattleshipUpdate", 32, 32 }, - { "FlyToDestAndDestroyUpdate", 32, 32 }, - { "MusicTrack", 32, 32 }, - { "PositionalSoundPool", 32, 32 }, - { "GameMessage", 2048, 32 }, - { "NameKeyBucketPool", 9000, 1024 }, - { "ObjectSellInfo", 16, 16 }, - { "ProductionPrerequisitePool", 1024, 32 }, - { "RadarObject", 512, 32 }, - { "ResourceGatheringManager", 16, 16 }, - { "SightingInfo", 8192, 2048 },// Looks big, but all 3000 objects used to have 4 just built in. - { "SpecialPowerTemplate", 84, 32 }, - { "StateMachinePool", 32, 32 }, - { "TeamPool", 128, 32 }, // if you increase this, increase player/team relation map pools - { "PlayerRelationMapPool", 128, 32 }, - { "TeamRelationMapPool", 128, 32 }, - { "TeamPrototypePool", 256, 32 }, - { "TerrainType", 256, 32 }, - { "ThingTemplatePool", 2120, 32 }, - { "TunnelTracker", 16, 16 }, - { "Upgrade", 16, 16 }, - { "UpgradeTemplate", 128, 16 }, - { "Anim2D", 32, 32 }, - { "CommandButton", 1024, 256 }, - { "CommandSet", 820, 16 }, - { "DisplayString", 32, 32 }, - { "WebBrowserURL", 16, 16 }, - { "Drawable", 4096, 32 }, - { "Image", 2048, 32 }, - { "ParticlePool", 1400, 1024 }, - { "ParticleSystemTemplatePool", 1100, 32 }, - { "ParticleSystemPool", 1024, 32 }, - { "TerrainRoadType", 100, 32, }, - { "WindowLayoutPool", 32, 32 }, - { "AnimatedParticleSysBoneClientUpdate", 16, 16 }, - { "SwayClientUpdate", 32, 32 }, - { "BeaconClientUpdate", 64, 32 }, - { "AIGroupPool", 64, 32 }, - { "AIDockMachinePool", 256, 32 }, - { "AIGuardMachinePool", 32, 32 }, - { "AIGuardRetaliateMachinePool", 32, 32 }, - { "AITNGuardMachinePool", 32, 32 }, - { "PathNodePool", 8192, 1024 }, - { "PathPool", 256, 16 }, - { "WorkOrder", 32, 32 }, - { "TeamInQueue", 32, 32 }, - { "AIPlayer", 12, 4 }, - { "AISkirmishPlayer", 8, 8 }, - { "AIStateMachine", 600, 32 }, - { "JetAIStateMachine", 64, 32 }, - { "HeliAIStateMachine", 64, 32 }, - { "VtolAIStateMachine", 64, 32 }, - { "AIAttackMoveStateMachine", 2048, 32 }, - { "AIAttackThenIdleStateMachine", 512, 32 }, - { "AttackStateMachine", 512, 32 }, - { "CrateTemplate", 32, 32 }, - { "ExperienceTrackerPool", 2048, 512 }, - { "FiringTrackerPool", 4096, 256 }, - { "ObjectRepulsorHelper", 1024, 256 }, - { "ObjectSMCHelperPool", 2048, 256 }, - { "ObjectWeaponStatusHelperPool", 4096, 256 }, - { "ObjectDefectionHelperPool", 2048, 256 }, - { "StatusDamageHelper", 1500, 256 }, - { "SubdualDamageHelper", 1500, 256 }, - { "TempWeaponBonusHelper", 4096, 256 }, - { "Locomotor", 2048, 32 }, - { "LocomotorTemplate", 192, 32 }, - { "ObjectPool", 1500, 256 }, - { "SimpleObjectIteratorPool", 32, 32 }, - { "SimpleObjectIteratorClumpPool", 4096, 32 }, - { "PartitionDataPool", 2048, 512 }, - { "BuildEntry", 32, 32 }, - { "Weapon", 4096, 32 }, - { "WeaponTemplate", 360, 32 }, - { "AIUpdateInterface", 600, 32 }, - { "ActiveBody", 1024, 32 }, - { "ActiveShroudUpgrade", 32, 32 }, - { "AssistedTargetingUpdate", 32, 32 }, - { "AudioEventInfo", 4096, 64 }, - { "AudioRequest", 256, 8 }, - { "AutoHealBehavior", 1024, 256 }, - { "WeaponBonusUpdate", 16, 16 }, - { "GrantStealthBehavior", 4096, 32 }, - { "NeutronBlastBehavior", 4096, 32 }, - { "CountermeasuresBehavior", 256, 32 }, - { "BaseRegenerateUpdate", 128, 32 }, - { "BoneFXDamage", 64, 32 }, - { "BoneFXUpdate", 64, 32 }, - { "BridgeBehavior", 4, 4 }, - { "BridgeTowerBehavior", 32, 32 }, - { "BridgeScaffoldBehavior", 32, 32 }, - { "CaveContain", 16, 16 }, - { "HealContain", 32, 32 }, - { "CreateCrateDie", 256, 128 }, - { "CreateObjectDie", 1024, 32 }, - { "EjectPilotDie", 1024, 32 }, - { "CrushDie", 1024, 32 }, - { "DamDie", 8, 8 }, - { "DeliverPayloadStateMachine", 32, 32 }, - { "DeliverPayloadAIUpdate", 32, 32 }, - { "DeletionUpdate", 128, 32 }, - { "SmartBombTargetHomingUpdate", 8, 8 }, - { "DynamicAudioEventInfo", 16, 256 }, // Note: some levels have none, some have lots. Since all are allocated at level load time, we can set this low for the levels with none. - { "HackInternetStateMachine", 32, 32 }, - { "HackInternetAIUpdate", 32, 32 }, - { "MissileAIUpdate", 512, 32 }, - { "DumbProjectileBehavior", 64, 32 }, - { "FreeFallProjectileBehavior", 32, 32 }, - { "DestroyDie", 1024, 32 }, - { "UpgradeDie", 128, 32 }, - { "KeepObjectDie", 128, 32 }, - { "DozerAIUpdate", 32, 32 }, - { "DynamicGeometryInfoUpdate", 16, 16 }, - { "DynamicShroudClearingRangeUpdate", 128, 16 }, - { "FXListDie", 1024, 32 }, - { "FireSpreadUpdate", 2048, 128 }, - { "FirestormDynamicGeometryInfoUpdate", 16, 16 }, - { "FireWeaponCollide", 2048, 32 }, - { "FireWeaponUpdate", 32, 32 }, - { "FlammableUpdate", 512, 256 }, - { "FloatUpdate", 512, 128 }, - { "TensileFormationUpdate", 256, 32 }, - { "GarrisonContain", 256, 32 }, - { "HealCrateCollide", 32, 32 }, - { "HeightDieUpdate", 32, 32 }, - { "ScatterShotUpdate", 128, 64 }, - { "FireWeaponWhenDamagedBehavior", 32, 32 }, - { "FireWeaponWhenDeadBehavior", 128, 64 }, - { "GenerateMinefieldBehavior", 32, 32 }, - { "HelicopterSlowDeathBehavior", 64, 32 }, - { "ParkingPlaceBehavior", 32, 32 }, - { "FlightDeckBehavior", 8, 8 }, -#ifdef ALLOW_SURRENDER - { "POWTruckAIUpdate", 32, 32, }, - { "POWTruckBehavior", 32, 32, }, - { "PrisonBehavior", 32, 32 }, - { "PrisonVisual", 32, 32 }, - { "PropagandaCenterBehavior", 16, 16 }, -#endif - { "PropagandaTowerBehavior", 16, 16 }, - { "BunkerBusterBehavior", 16, 16 }, - { "ObjectTracker", 128, 32 }, - { "OCLUpdate", 16, 16 }, - { "BodyParticleSystem", 196, 64 }, - { "HighlanderBody", 2048, 128 }, - { "UndeadBody", 32, 32 }, - { "HordeUpdate", 128, 32 }, - { "ImmortalBody", 128, 256 }, - { "InactiveBody", 2048, 32 }, - { "InstantDeathBehavior", 512, 32 }, - { "LaserUpdate", 32, 32 }, - { "PointDefenseLaserUpdate", 32, 32 }, - { "CleanupHazardUpdate", 32, 32 }, - { "AutoFindHealingUpdate", 256, 32 }, - { "CommandButtonHuntUpdate", 512, 8 }, - { "PilotFindVehicleUpdate", 256, 32 }, - { "DemoTrapUpdate", 32, 32 }, - { "ParticleUplinkCannonUpdate", 16, 16 }, - { "SpectreGunshipUpdate", 8, 8 }, - { "SpectreGunshipDeploymentUpdate", 8, 8 }, - { "BaikonurLaunchPower", 4, 4 }, - { "RadiusDecalUpdate", 16, 16 }, - { "RadiusDecalBehavior", 32, 32 }, - { "BattlePlanUpdate", 32, 32 }, - { "LifetimeUpdate", 32, 32 }, - { "LocomotorSetUpgrade", 512, 128 }, - { "LockWeaponCreate", 64, 128 }, - { "AutoDepositUpdate", 256, 32 }, - { "NeutronMissileUpdate", 512, 32 }, - { "MoneyCrateCollide", 48, 16 }, - { "NeutronMissileSlowDeathBehavior", 8, 8 }, - { "OpenContain", 128, 32 }, - { "OverchargeBehavior", 32, 32 }, - { "OverlordContain", 32, 32 }, - { "HelixContain", 32, 32 }, - { "ParachuteContain", 128, 32 }, - { "PhysicsBehavior", 600, 32 }, - { "PoisonedBehavior", 512, 64 }, - { "ProductionEntry", 32, 32 }, - { "ProductionUpdate", 256, 32 }, - { "ProjectileStreamUpdate", 32, 32 }, - { "ProneUpdate", 128, 32 }, - { "QueueProductionExitUpdate", 32, 32 }, - { "RadarUpdate", 16, 16 }, - { "RadarUpgrade", 16, 16 }, - { "AnimationSteeringUpdate", 1024, 32 }, - { "SupplyWarehouseCripplingBehavior", 16, 16 }, - { "CostModifierUpgrade", 32, 32 }, - { "ProductionTimeModifierUpgrade", 32, 32 }, - { "UnitProductionBonusUpgrade", 64, 32 }, - { "CashBountyPower", 32, 32 }, - { "CleanupAreaPower", 32, 32 }, - { "ObjectCreationUpgrade", 196, 32 }, - { "MinefieldBehavior", 256, 32 }, - { "JetSlowDeathBehavior", 64, 32 }, - { "BattleBusSlowDeathBehavior", 64, 32 }, - { "RebuildHoleBehavior", 64, 32 }, - { "RebuildHoleExposeDie", 64, 32 }, - { "RepairDockUpdate", 32, 32 }, -#ifdef ALLOW_SURRENDER - { "PrisonDockUpdate", 32, 32 }, -#endif - { "RailedTransportDockUpdate", 16, 16 }, - { "RailedTransportAIUpdate", 16, 16 }, - { "RailedTransportContain", 16, 16 }, - { "RailroadBehavior", 16, 16 }, - { "SalvageCrateCollide", 32, 32 }, - { "ShroudCrateCollide", 32, 32 }, - { "SlavedUpdate", 64, 32 }, - { "SlowDeathBehavior", 1400, 256 }, - { "SpyVisionUpdate", 16, 16 }, - { "DefaultProductionExitUpdate", 32, 32 }, - { "SpawnPointProductionExitUpdate", 32, 32 }, - { "SpawnBehavior", 32, 32 }, - { "SpecialPowerCompletionDie", 32, 32 }, - { "SpecialPowerCreate", 32, 32 }, - { "PreorderCreate", 32, 32 }, - { "SpecialAbility", 512, 32 }, - { "SpecialAbilityUpdate", 512, 32 }, - { "MissileLauncherBuildingUpdate", 32, 32 }, - { "SquishCollide", 512, 32 }, - { "StructureBody", 512, 64 }, - { "HiveStructureBody", 64, 32 }, //Stinger sites - { "StructureCollapseUpdate", 32, 32 }, - { "StructureToppleUpdate", 32, 32 }, - { "SupplyCenterCreate", 32, 32 }, - { "SupplyCenterDockUpdate", 32, 32 }, - { "SupplyCenterProductionExitUpdate", 32, 32 }, - { "SupplyTruckStateMachine", 256, 32 }, - { "SupplyTruckAIUpdate", 32, 32 }, - { "SupplyWarehouseCreate", 48, 16 }, - { "SupplyWarehouseDockUpdate", 48, 16 }, - { "EnemyNearUpdate", 1024, 32 }, - { "TechBuildingBehavior", 32, 32 }, - { "ToppleUpdate", 256, 128 }, - { "TransitionDamageFX", 384, 128 }, - { "TransportAIUpdate", 64, 32 }, - { "TransportContain", 128, 32 }, - { "RiderChangeContain", 128, 32 }, - { "InternetHackContain", 16, 16 }, - { "TunnelContain", 8, 8 }, - { "TunnelContainDie", 32, 32 }, - { "TunnelCreate", 32, 32 }, - { "TurretAI", 256, 32 }, - { "TurretStateMachine", 128, 32 }, - { "TurretSwapUpgrade", 512, 128 }, - { "UnitCrateCollide", 32, 32 }, - { "UnpauseSpecialPowerUpgrade", 32, 32 }, - { "VeterancyCrateCollide", 32, 32 }, - { "VeterancyGainCreate", 512, 128 }, - { "ConvertToCarBombCrateCollide", 256, 128 }, - { "ConvertToHijackedVehicleCrateCollide", 256, 128 }, - { "SabotageCommandCenterCrateCollide", 256, 128 }, - { "SabotageFakeBuildingCrateCollide", 256, 128 }, - { "SabotageInternetCenterCrateCollide", 256, 128 }, - { "SabotageMilitaryFactoryCrateCollide", 256, 128 }, - { "SabotagePowerPlantCrateCollide", 256, 128 }, - { "SabotageSuperweaponCrateCollide", 256, 128 }, - { "SabotageSupplyCenterCrateCollide", 256, 128 }, - { "SabotageSupplyDropzoneCrateCollide", 256, 128 }, - { "JetAIUpdate", 64, 32 }, - { "ChinookAIUpdate", 32, 32 }, - { "WanderAIUpdate", 32, 32 }, - { "TeleporterAIUpdate", 64, 32 }, - { "WaveGuideUpdate", 16, 16 }, - { "ArmorDamageScalarUpdate", 256, 32 }, - { "WeaponBonusUpgrade", 512, 128 }, - { "WeaponSetUpgrade", 512, 128 }, - { "ArmorUpgrade", 512, 128 }, - { "WorkerAIUpdate", 128, 128 }, - { "WorkerStateMachine", 128, 128 }, - { "ChinookAIStateMachine", 32, 32 }, - { "DeployStyleAIUpdate", 32, 32 }, - { "AssaultTransportAIUpdate", 64, 32 }, - { "StreamingArchiveFile", 8, 8 }, - - { "DozerActionStateMachine", 256, 32 }, - { "DozerPrimaryStateMachine", 256, 32 }, - { "W3DDisplayString", 1400, 128 }, - { "W3DDefaultDraw", 1024, 128 }, - { "W3DDebrisDraw", 128, 128 }, - { "W3DDependencyModelDraw", 64, 64 }, - { "W3DLaserDraw", 32, 32 }, - { "W3DModelDraw", 2048, 512 }, - { "W3DOverlordTankDraw", 64, 64 }, - { "W3DOverlordTruckDraw", 64, 64 }, - { "W3DOverlordAircraftDraw", 64, 64 }, - { "W3DPoliceCarDraw", 32, 32 }, - { "W3DProjectileStreamDraw", 32, 32 }, - { "W3DRopeDraw", 32, 32 }, - { "W3DScienceModelDraw", 32, 32 }, - { "W3DSupplyDraw", 40, 16 }, - { "W3DTankDraw", 256, 32 }, - { "W3DTreeDraw", 16, 16 }, - { "W3DPropDraw", 16, 16 }, - { "W3DTracerDraw", 64, 32 }, - { "W3DTruckDraw", 128, 32 }, - { "W3DTankTruckDraw", 32, 16 }, - { "W3DTreeTextureClass", 4, 4 }, - { "DefaultSpecialPower", 32, 32 }, - { "OCLSpecialPower", 96, 32 }, - { "FireWeaponPower", 32, 32 }, -#ifdef ALLOW_DEMORALIZE - { "DemoralizeSpecialPower", 16, 16, }, -#endif - { "CashHackSpecialPower", 32, 32 }, - { "CommandSetUpgrade", 32, 32 }, - { "PassengersFireUpgrade", 32, 32 }, - { "GrantUpgradeCreate", 256, 32 }, - { "GrantScienceUpgrade", 256, 32 }, - { "ReplaceObjectUpgrade", 32, 32 }, - { "ModelConditionUpgrade", 32, 32 }, - { "SpyVisionSpecialPower", 256, 32 }, - { "StealthDetectorUpdate", 256, 32 }, - { "StealthUpdate", 512, 128 }, - { "StealthUpgrade", 256, 32 }, - { "StatusBitsUpgrade", 128, 128 }, - { "SubObjectsUpgrade", 128, 128 }, - { "ExperienceScalarUpgrade", 256, 128 }, - { "MaxHealthUpgrade", 128, 128 }, - { "WeaponBonusUpgrade", 128, 64 }, - { "StickyBombUpdate", 64, 32 }, - { "FireOCLAfterWeaponCooldownUpdate", 64, 32 }, - { "HijackerUpdate", 64, 32 }, - { "ChinaMinesUpgrade", 64, 32 }, - { "PowerPlantUpdate", 48, 16 }, - { "PowerPlantUpgrade", 48, 16 }, - { "DefectorSpecialPower", 16, 16 }, - { "CheckpointUpdate", 16, 16 }, - { "MobNexusContain", 128, 32 }, - { "MobMemberSlavedUpdate", 64, 32 }, - { "EMPUpdate", 64, 32 }, - { "LeafletDropBehavior", 64, 32 }, - { "Overridable", 32, 32 }, - - { "W3DGameWindow", 700, 256 }, - { "SuccessState", 32, 32 }, - { "FailureState", 32, 32 }, - { "ContinueState", 32, 32 }, - { "SleepState", 32, 32 }, - - { "AIDockWaitForClearanceState", 256, 32 }, - { "AIDockProcessDockState", 256, 32 }, - { "AIGuardInnerState", 32, 32 }, - { "AIGuardIdleState", 32, 32 }, - { "AIGuardOuterState", 32, 32 }, - { "AIGuardReturnState", 32, 32 }, - { "AIGuardPickUpCrateState", 32, 32 }, - { "AIGuardAttackAggressorState", 32, 32 }, - { "AIGuardRetaliateInnerState", 32, 32 }, - { "AIGuardRetaliateIdleState", 32, 32 }, - { "AIGuardRetaliateOuterState", 32, 32 }, - { "AIGuardRetaliateReturnState", 32, 32 }, - { "AIGuardRetaliatePickUpCrateState", 32, 32 }, - { "AIGuardRetaliateAttackAggressorState", 32, 32 }, - { "AITNGuardInnerState", 32, 32 }, - { "AITNGuardIdleState", 32, 32 }, - { "AITNGuardOuterState", 32, 32 }, - { "AITNGuardReturnState", 32, 32 }, - { "AITNGuardPickUpCrateState", 32, 32 }, - { "AITNGuardAttackAggressorState", 32, 32 }, - { "AIIdleState", 2400, 32 }, - { "AIRappelState", 600, 32 }, - { "AIBusyState", 600, 32 }, - { "AIWaitState", 600, 32 }, - { "AIAttackState", 4096, 32 }, - { "AIAttackSquadState", 600, 32 }, - { "AIDeadState", 600, 32 }, - { "AIDockState", 600, 32 }, - { "AIExitState", 600, 32 }, - { "AIExitInstantlyState", 600, 32 }, - { "AIGuardState", 600, 32 }, - { "AIGuardRetaliateState", 600, 32 }, - { "AITunnelNetworkGuardState", 600, 32 }, - { "AIHuntState", 600, 32 }, - { "AIAttackAreaState", 600, 32 }, - { "AIFaceState", 1200, 32 }, - { "ApproachState", 600, 32 }, - { "DeliveringState", 600, 32 }, - { "ConsiderNewApproachState", 600, 32 }, - { "RecoverFromOffMapState", 600, 32 }, - { "HeadOffMapState", 600, 32 }, - { "CleanUpState", 600, 32 }, - { "HackInternetState", 600, 32 }, - { "PackingState", 600, 32 }, - { "UnpackingState", 600, 32 }, - { "SupplyTruckWantsToPickUpOrDeliverBoxesState", 600, 32 }, - { "RegroupingState", 600, 32 }, - { "DockingState", 600, 32 }, - { "ChinookEvacuateState", 32, 32 }, - { "ChinookHeadOffMapState", 32, 32 }, - { "ChinookTakeoffOrLandingState", 32, 32 }, - { "ChinookCombatDropState", 32, 32 }, - { "DozerActionPickActionPosState", 256, 32 }, - { "DozerActionMoveToActionPosState", 256, 32 }, - { "DozerActionDoActionState", 256, 32 }, - { "DozerPrimaryIdleState", 256, 32 }, - { "DozerActionState", 256, 32 }, - { "DozerPrimaryGoingHomeState", 256, 32 }, - { "JetAwaitingRunwayState", 64, 32 }, - { "JetOrHeliCirclingDeadAirfieldState", 64, 32 }, - { "HeliTakeoffOrLandingState", 64, 32 }, - { "VtolTakeoffOrLandingState", 64, 32 }, - { "JetOrHeliParkOrientState", 64, 32 }, - { "VtolParkOrientState", 64, 32 }, - { "JetOrHeliReloadAmmoState", 64, 32 }, - { "SupplyTruckBusyState", 600, 32 }, - { "SupplyTruckIdleState", 600, 32 }, - { "ActAsDozerState", 600, 32 }, - { "ActAsSupplyTruckState", 600, 32 }, - { "AIDockApproachState", 256, 32 }, - { "AIDockAdvancePositionState", 256, 32 }, - { "AIDockMoveToEntryState", 256, 32 }, - { "AIDockMoveToDockState", 256, 32 }, - { "AIDockMoveToExitState", 256, 32 }, - { "AIDockMoveToRallyState", 256, 32 }, - { "AIMoveToState", 600, 32 }, - { "AIMoveOutOfTheWayState", 600, 32 }, - { "AIMoveAndTightenState", 600, 32 }, - { "AIMoveAwayFromRepulsorsState", 600, 32 }, - { "AIAttackApproachTargetState", 96, 32 }, - { "AIAttackPursueTargetState", 96, 32 }, - { "AIAttackAimAtTargetState", 96, 32 }, - { "AIAttackFireWeaponState", 256, 32 }, - { "AIPickUpCrateState", 4096, 32 }, - { "AIFollowWaypointPathState", 1200, 32 }, - { "AIFollowWaypointPathExactState", 1200, 32 }, - { "AIWanderInPlaceState", 600, 32 }, - { "AIFollowPathState", 1200, 32 }, - { "AIMoveAndEvacuateState", 1200, 32 }, - { "AIMoveAndDeleteState", 600, 32 }, - { "AIEnterState", 600, 32 }, - { "JetOrHeliReturningToDeadAirfieldState", 64, 32 }, - { "JetOrHeliReturnForLandingState", 64, 32 }, - { "TurretAIIdleState", 600, 32 }, - { "TurretAIIdleScanState", 600, 32 }, - { "TurretAIAimTurretState", 600, 32 }, - { "TurretAIRecenterTurretState", 600, 32 }, - { "TurretAIHoldTurretState", 600, 32 }, - { "JetOrHeliTaxiState", 64, 32 }, - { "JetTakeoffOrLandingState", 64, 32 }, - { "JetPauseBeforeTakeoffState", 64, 32 }, - { "AIAttackMoveToState", 600, 32 }, - { "AIAttackFollowWaypointPathState", 1200, 32 }, - { "AIWanderState", 600, 32 }, - { "AIPanicState", 600, 32 }, - { "ChinookMoveToBldgState", 32, 32 }, - { "ChinookRecordCreationState", 32, 32 }, - { "ScienceInfo", 96, 32 }, - { "RankInfo", 32, 32 }, - - { "FireWeaponNugget", 32, 32 }, - { "AttackNugget", 32, 32 }, - { "DeliverPayloadNugget", 48, 32 }, - { "ApplyRandomForceNugget", 32, 32 }, - { "GenericObjectCreationNugget", 632, 32 }, - { "SoundFXNugget", 320, 32 }, - { "TracerFXNugget", 32, 32 }, - { "RayEffectFXNugget", 32, 32 }, - { "LightPulseFXNugget", 68, 32 }, - { "ViewShakeFXNugget", 140, 32 }, - { "TerrainScorchFXNugget", 48, 32 }, - { "ParticleSystemFXNugget", 832, 32 }, - { "FXListAtBonePosFXNugget", 32, 32 }, - { "Squad", 256, 32 }, - { "BuildListInfo", 400, 64 }, - - { "ScriptGroup", 128, 32 }, - { "OrCondition", 1024, 256 }, - { "ScriptAction", 2600, 512 }, - { "Script", 1024, 256 }, - { "Parameter", 8192, 1024 }, - { "Condition", 2048, 256 }, - { "Template", 32, 32 }, - { "ScriptList", 32, 32 }, - { "AttackPriorityInfo", 32, 32 }, - { "SequentialScript", 32, 32 }, - { "Win32LocalFile", 1024, 256 }, - { "StdLocalFile", 1024, 256 }, - { "RAMFile", 32, 32 }, - { "BattlePlanBonuses", 32, 32 }, - { "KindOfPercentProductionChange", 32, 32 }, - { "UserParser", 4096, 256 }, - { "XferBlockData", 32, 32 }, - { "EvaCheckInfo", 52, 16 }, - { "SuperweaponInfo", 32, 32 }, - { "NamedTimerInfo", 32, 32 }, - { "PopupMessageData", 32, 32 }, - { "FloatingTextData", 32, 32 }, - { "MapObject", 5000, 1024 }, - { "Waypoint", 1024, 32 }, - { "PolygonTrigger", 64, 64 }, - { "Bridge", 32, 32 }, - { "Mapping", 384, 64 }, - { "OutputChunk", 32, 32 }, - { "InputChunk", 32, 32 }, - { "AnimateWindow", 32, 32 }, - { "GameFont", 32, 32 }, - { "NetCommandRef", 256, 32 }, - { "GameMessageArgument", 1024, 256 }, - { "GameMessageParserArgumentType", 32, 32 }, - { "GameMessageParser", 32, 32 }, - { "WeaponBonusSet", 96, 32 }, - { "Campaign", 32, 32 }, - { "Mission", 88, 32 }, - { "ModalWindow", 32, 32 }, - { "NetPacket", 32, 32 }, - { "AISideInfo", 32, 32 }, - { "AISideBuildList", 32, 32 }, - { "MetaMapRec", 256, 32 }, - { "TransportStatus", 32, 32 }, - { "Anim2DTemplate", 32, 32 }, - { "ObjectTypes", 32, 32 }, - { "NetCommandList", 512, 32 }, - { "TurretAIData", 256, 32 }, - { "NetCommandMsg", 32, 32 }, - { "NetGameCommandMsg", 64, 32 }, - { "NetAckBothCommandMsg", 32, 32 }, - { "NetAckStage1CommandMsg", 32, 32 }, - { "NetAckStage2CommandMsg", 32, 32 }, - { "NetFrameCommandMsg", 32, 32 }, - { "NetPlayerLeaveCommandMsg", 32, 32 }, - { "NetRunAheadMetricsCommandMsg", 32, 32 }, - { "NetRunAheadCommandMsg", 32, 32 }, - { "NetDestroyPlayerCommandMsg", 32, 32 }, - { "NetDisconnectFrameCommandMsg", 32, 32 }, - { "NetDisconnectScreenOffCommandMsg", 32, 32 }, - { "NetFrameResendRequestCommandMsg", 32, 32 }, - { "NetKeepAliveCommandMsg", 32, 32 }, - { "NetDisconnectKeepAliveCommandMsg", 32, 32 }, - { "NetDisconnectPlayerCommandMsg", 32, 32 }, - { "NetPacketRouterQueryCommandMsg", 32, 32 }, - { "NetPacketRouterAckCommandMsg", 32, 32 }, - { "NetDisconnectChatCommandMsg", 32, 32 }, - { "NetChatCommandMsg", 32, 32 }, - { "NetDisconnectVoteCommandMsg", 32, 32 }, - { "NetProgressCommandMsg", 32, 32 }, - { "NetWrapperCommandMsg", 32, 32 }, - { "NetFileCommandMsg", 32, 32 }, - { "NetFileAnnounceCommandMsg", 32, 32 }, - { "NetFileProgressCommandMsg", 32, 32 }, - { "NetCommandWrapperListNode", 32, 32 }, - { "NetCommandWrapperList", 32, 32 }, - { "Connection", 32, 32 }, - { "User", 32, 32 }, - { "FrameDataManager", 32, 32 }, - { "DrawableIconInfo", 32, 32 }, - { "TintEnvelope", 128, 32 }, - { "DynamicAudioEventRTS", 4000, 256 }, - { "DrawableLocoInfo", 128, 32 }, - { "W3DPrototypeClass", 512, 256 }, - { "EnumeratedIP", 32, 32 }, - { "WaterTransparencySetting", 4, 4 }, - { "WeatherSetting", 4, 4 }, - - // W3D pools! - { "BoxPrototypeClass", 128, 128 }, - { "SpherePrototypeClass", 32, 32 }, - { "SoundRenderObjPrototypeClass", 32, 32 }, - { "RingPrototypeClass", 32, 32 }, - { "PrimitivePrototypeClass", 8192, 32 }, - { "HModelPrototypeClass", 256, 32 }, - { "ParticleEmitterPrototypeClass", 32, 32 }, - { "NullPrototypeClass", 32, 32 }, - { "HLodPrototypeClass", 700, 128 }, - { "HLodDefClass", 700, 128 }, - { "DistLODPrototypeClass", 32, 32 }, - { "DazzlePrototypeClass", 32, 32 }, - { "CollectionPrototypeClass", 32, 32 }, - { "BoxPrototypeClass", 256, 32 }, - { "AggregatePrototypeClass", 32, 32 }, - { "OBBoxRenderObjClass", 512, 128 }, - { "AABoxRenderObjClass", 32, 32 }, - { "VertexMaterialClass", 6000, 2048 }, - { "TextureClass", 1200, 256 }, - { "CloudMapTerrainTextureClass", 4, 4 }, - { "ScorchTextureClass", 4, 4 }, - { "LightMapTerrainTextureClass", 4, 4 }, - { "AlphaEdgeTextureClass", 4, 4 }, - { "AlphaTerrainTextureClass", 4, 4 }, - { "TerrainTextureClass", 4, 4 }, - { "MeshClass", 14000, 2000 }, - { "HTreeClass", 2048, 512 }, - { "HLodClass", 2048, 512 }, - { "MeshModelClass", 8192, 32 }, - { "ShareBufferClass", 32768, 1024 }, - { "AABTreeClass", 300, 128 }, - { "MotionChannelClass", 16384, 32 }, - { "BitChannelClass", 84, 32 }, - { "TimeCodedMotionChannelClass", 116, 32 }, - { "AdaptiveDeltaMotionChannelClass", 32, 32 }, - { "TimeCodedBitChannelClass", 32, 32 }, - { "UVBufferClass", 8192, 32 }, - { "TexBufferClass", 384, 128 }, - { "MatBufferClass", 256, 128 }, - { "MatrixMapperClass", 32, 32 }, - { "ScaleTextureMapperClass", 32, 32 }, - { "LinearOffsetTextureMapperClass", 96, 32 }, - { "GridTextureMapperClass", 32, 32 }, - { "RotateTextureMapperClass", 32, 32 }, - { "SineLinearOffsetTextureMapperClass", 32, 32 }, - { "StepLinearOffsetTextureMapperClass", 32, 32 }, - { "ZigZagLinearOffsetTextureMapperClass", 32, 32 }, - { "ClassicEnvironmentMapperClass", 32, 32 }, - { "EnvironmentMapperClass", 256, 32 }, - { "EdgeMapperClass", 32, 32 }, - { "WSClassicEnvironmentMapperClass", 32, 32 }, - { "WSEnvironmentMapperClass", 32, 32 }, - { "GridClassicEnvironmentMapperClass", 32, 32 }, - { "GridEnvironmentMapperClass", 32, 32 }, - { "ScreenMapperClass", 32, 32 }, - { "RandomTextureMapperClass", 32, 32 }, - { "BumpEnvTextureMapperClass", 32, 32 }, - { "MeshLoadContextClass", 4, 4 }, - { "MaterialInfoClass", 8192, 32 }, - { "MeshMatDescClass", 8192, 32 }, - { "TextureLoadTaskClass", 256, 32 }, - { "SortingNodeStruct", 288, 32 }, - { "ProxyArrayClass", 32, 32 }, - { "Line3DClass", 8, 8 }, - { "Render2DClass", 64, 32 }, - { "SurfaceClass", 128, 32 }, - { "FontCharsClassCharDataStruct", 1024, 32 }, - { "FontCharsBuffer", 16, 4 }, - { "FVFInfoClass", 152, 64 }, - { "TerrainTracksRenderObjClass", 128, 32 }, - { "DynamicIBAccessClass", 32, 32 }, - { "DX8IndexBufferClass", 128, 32 }, - { "SortingIndexBufferClass", 32, 32 }, - { "DX8VertexBufferClass", 128, 32 }, - { "SortingVertexBufferClass", 32, 32 }, - { "DynD3DMATERIAL8", 8192, 32 }, - { "DynamicMatrix3D", 512, 32 }, - { "MeshGeometryClass", 32, 32 }, - { "DynamicMeshModel", 32, 32 }, - { "GapFillerClass", 32, 32 }, - { "FontCharsClass", 64, 32 }, - { "ThumbnailManagerClass", 32, 32}, - { "SmudgeSet", 32, 32}, - { "Smudge", 128, 32}, - { 0, 0, 0 } -}; - -//----------------------------------------------------------------------------- -void userMemoryAdjustPoolSize(const char *poolName, Int& initialAllocationCount, Int& overflowAllocationCount) -{ - if (initialAllocationCount > 0) - return; - - for (const PoolSizeRec* p = sizes; p->name != NULL; ++p) - { - if (strcmp(p->name, poolName) == 0) - { - initialAllocationCount = p->initial; - overflowAllocationCount = p->overflow; - return; - } - } - - DEBUG_CRASH(("Initial size for pool %s not found -- you should add it to MemoryInit.cpp\n",poolName)); -} - -//----------------------------------------------------------------------------- -static Int roundUpMemBound(Int i) -{ - const int MEM_BOUND_ALIGNMENT = 4; - - if (i < MEM_BOUND_ALIGNMENT) - return MEM_BOUND_ALIGNMENT; - else - return (i + (MEM_BOUND_ALIGNMENT-1)) & ~(MEM_BOUND_ALIGNMENT-1); -} - -//----------------------------------------------------------------------------- -void userMemoryManagerInitPools() -{ - // note that we MUST use stdio stuff here, and not the normal game file system - // (with bigfile support, etc), because that relies on memory pools, which - // aren't yet initialized properly! so rely ONLY on straight stdio stuff here. - // (not even AsciiString. thanks.) - - // since we're called prior to main, the cur dir might not be what - // we expect. so do it the hard way. - char buf[_MAX_PATH]; - ::GetModuleFileName(NULL, buf, sizeof(buf)); - char* pEnd = buf + strlen(buf); - while (pEnd != buf) - { - if (*pEnd == '\\') - { - *pEnd = 0; - break; - } - --pEnd; - } - strcat(buf, "\\Data\\INI\\MemoryPools.ini"); - - FILE* fp = fopen(buf, "r"); - if (fp) - { - char poolName[256]; - int initial, overflow; - while (fgets(buf, _MAX_PATH, fp)) - { - if (buf[0] == ';') - continue; - if (sscanf(buf, "%s %d %d", poolName, &initial, &overflow ) == 3) - { - for (PoolSizeRec* p = sizes; p->name != NULL; ++p) - { - if (stricmp(p->name, poolName) == 0) - { - // currently, these must be multiples of 4. so round up. - p->initial = roundUpMemBound(initial); - p->overflow = roundUpMemBound(overflow); - break; // from for-p - } - } - } - } - fclose(fp); - } -} - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: MemoryInit.cpp +//----------------------------------------------------------------------------- +// +// Westwood Studios Pacific. +// +// Confidential Information +// Copyright (C) 2001 - All Rights Reserved +// +//----------------------------------------------------------------------------- +// +// Project: RTS3 +// +// File name: MemoryInit.cpp +// +// Created: Steven Johnson, August 2001 +// +// Desc: Memory manager +// +// ---------------------------------------------------------------------------- +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +// SYSTEM INCLUDES + +// USER INCLUDES +#include "Lib/BaseType.h" +#include "Common/GameMemory.h" + +#ifdef RTS_INTERNAL +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +//----------------------------------------------------------------------------- +void userMemoryManagerGetDmaParms(Int *numSubPools, const PoolInitRec **pParms) +{ + static const PoolInitRec defaultDMA[7] = + { + // name, allocsize, initialcount, overflowcount + { "dmaPool_16", 16, 130000, 10000 }, + { "dmaPool_32", 32, 250000, 10000 }, + { "dmaPool_64", 64, 100000, 10000 }, + { "dmaPool_128", 128, 80000, 10000 }, + { "dmaPool_256", 256, 20000, 5000 }, + { "dmaPool_512", 512, 16000, 5000 }, + { "dmaPool_1024", 1024, 6000, 1024} + }; + + *numSubPools = 7; + *pParms = defaultDMA; +} + +//----------------------------------------------------------------------------- +struct PoolSizeRec +{ + const char* name; + Int initial; + Int overflow; +}; + +//----------------------------------------------------------------------------- +// And please be careful of duplicates. They are not rejected. +// not const -- we might override from INI +static PoolSizeRec sizes[] = +{ + { "PartitionContactListNode", 2048, 512 }, + { "BattleshipUpdate", 32, 32 }, + { "FlyToDestAndDestroyUpdate", 32, 32 }, + { "MusicTrack", 32, 32 }, + { "PositionalSoundPool", 32, 32 }, + { "GameMessage", 2048, 32 }, + { "NameKeyBucketPool", 9000, 1024 }, + { "ObjectSellInfo", 16, 16 }, + { "ProductionPrerequisitePool", 1024, 32 }, + { "RadarObject", 512, 32 }, + { "ResourceGatheringManager", 16, 16 }, + { "SightingInfo", 8192, 2048 },// Looks big, but all 3000 objects used to have 4 just built in. + { "SpecialPowerTemplate", 84, 32 }, + { "StateMachinePool", 32, 32 }, + { "TeamPool", 128, 32 }, // if you increase this, increase player/team relation map pools + { "PlayerRelationMapPool", 128, 32 }, + { "TeamRelationMapPool", 128, 32 }, + { "TeamPrototypePool", 256, 32 }, + { "TerrainType", 256, 32 }, + { "ThingTemplatePool", 2120, 32 }, + { "TunnelTracker", 16, 16 }, + { "Upgrade", 16, 16 }, + { "UpgradeTemplate", 128, 16 }, + { "Anim2D", 32, 32 }, + { "CommandButton", 1024, 256 }, + { "CommandSet", 820, 16 }, + { "DisplayString", 32, 32 }, + { "WebBrowserURL", 16, 16 }, + { "Drawable", 4096, 32 }, + { "Image", 2048, 32 }, + { "ParticlePool", 1400, 1024 }, + { "ParticleSystemTemplatePool", 1100, 32 }, + { "ParticleSystemPool", 1024, 32 }, + { "TerrainRoadType", 100, 32, }, + { "WindowLayoutPool", 32, 32 }, + { "AnimatedParticleSysBoneClientUpdate", 16, 16 }, + { "SwayClientUpdate", 32, 32 }, + { "BeaconClientUpdate", 64, 32 }, + { "AIGroupPool", 64, 32 }, + { "AIDockMachinePool", 256, 32 }, + { "AIGuardMachinePool", 32, 32 }, + { "AIGuardRetaliateMachinePool", 32, 32 }, + { "AITNGuardMachinePool", 32, 32 }, + { "PathNodePool", 8192, 1024 }, + { "PathPool", 256, 16 }, + { "WorkOrder", 32, 32 }, + { "TeamInQueue", 32, 32 }, + { "AIPlayer", 12, 4 }, + { "AISkirmishPlayer", 8, 8 }, + { "AIStateMachine", 600, 32 }, + { "JetAIStateMachine", 64, 32 }, + { "HeliAIStateMachine", 64, 32 }, + { "VtolAIStateMachine", 64, 32 }, + { "AIAttackMoveStateMachine", 2048, 32 }, + { "AIAttackThenIdleStateMachine", 512, 32 }, + { "AttackStateMachine", 512, 32 }, + { "CrateTemplate", 32, 32 }, + { "ExperienceTrackerPool", 2048, 512 }, + { "FiringTrackerPool", 4096, 256 }, + { "ObjectRepulsorHelper", 1024, 256 }, + { "ObjectSMCHelperPool", 2048, 256 }, + { "ObjectWeaponStatusHelperPool", 4096, 256 }, + { "ObjectDefectionHelperPool", 2048, 256 }, + { "StatusDamageHelper", 1500, 256 }, + { "SubdualDamageHelper", 1500, 256 }, + { "TempWeaponBonusHelper", 4096, 256 }, + { "Locomotor", 2048, 32 }, + { "LocomotorTemplate", 192, 32 }, + { "ObjectPool", 1500, 256 }, + { "SimpleObjectIteratorPool", 32, 32 }, + { "SimpleObjectIteratorClumpPool", 4096, 32 }, + { "PartitionDataPool", 2048, 512 }, + { "BuildEntry", 32, 32 }, + { "Weapon", 4096, 32 }, + { "WeaponTemplate", 360, 32 }, + { "AIUpdateInterface", 600, 32 }, + { "ActiveBody", 1024, 32 }, + { "ActiveShroudUpgrade", 32, 32 }, + { "AssistedTargetingUpdate", 32, 32 }, + { "AudioEventInfo", 4096, 64 }, + { "AudioRequest", 256, 8 }, + { "AutoHealBehavior", 1024, 256 }, + { "WeaponBonusUpdate", 16, 16 }, + { "GrantStealthBehavior", 4096, 32 }, + { "NeutronBlastBehavior", 4096, 32 }, + { "CountermeasuresBehavior", 256, 32 }, + { "BaseRegenerateUpdate", 128, 32 }, + { "BoneFXDamage", 64, 32 }, + { "BoneFXUpdate", 64, 32 }, + { "BridgeBehavior", 4, 4 }, + { "BridgeTowerBehavior", 32, 32 }, + { "BridgeScaffoldBehavior", 32, 32 }, + { "CaveContain", 16, 16 }, + { "HealContain", 32, 32 }, + { "CreateCrateDie", 256, 128 }, + { "CreateObjectDie", 1024, 32 }, + { "EjectPilotDie", 1024, 32 }, + { "CrushDie", 1024, 32 }, + { "DamDie", 8, 8 }, + { "DeliverPayloadStateMachine", 32, 32 }, + { "DeliverPayloadAIUpdate", 32, 32 }, + { "DeletionUpdate", 128, 32 }, + { "SmartBombTargetHomingUpdate", 8, 8 }, + { "DynamicAudioEventInfo", 16, 256 }, // Note: some levels have none, some have lots. Since all are allocated at level load time, we can set this low for the levels with none. + { "HackInternetStateMachine", 32, 32 }, + { "HackInternetAIUpdate", 32, 32 }, + { "MissileAIUpdate", 512, 32 }, + { "DumbProjectileBehavior", 64, 32 }, + { "FreeFallProjectileBehavior", 32, 32 }, + { "DestroyDie", 1024, 32 }, + { "UpgradeDie", 128, 32 }, + { "KeepObjectDie", 128, 32 }, + { "DozerAIUpdate", 32, 32 }, + { "DynamicGeometryInfoUpdate", 16, 16 }, + { "DynamicShroudClearingRangeUpdate", 128, 16 }, + { "FXListDie", 1024, 32 }, + { "FireSpreadUpdate", 2048, 128 }, + { "FirestormDynamicGeometryInfoUpdate", 16, 16 }, + { "FireWeaponCollide", 2048, 32 }, + { "FireWeaponUpdate", 32, 32 }, + { "FlammableUpdate", 512, 256 }, + { "FloatUpdate", 512, 128 }, + { "TensileFormationUpdate", 256, 32 }, + { "GarrisonContain", 256, 32 }, + { "HealCrateCollide", 32, 32 }, + { "HeightDieUpdate", 32, 32 }, + { "ScatterShotUpdate", 128, 64 }, + { "FireWeaponWhenDamagedBehavior", 32, 32 }, + { "FireWeaponWhenDeadBehavior", 128, 64 }, + { "GenerateMinefieldBehavior", 32, 32 }, + { "HelicopterSlowDeathBehavior", 64, 32 }, + { "ParkingPlaceBehavior", 32, 32 }, + { "FlightDeckBehavior", 8, 8 }, +#ifdef ALLOW_SURRENDER + { "POWTruckAIUpdate", 32, 32, }, + { "POWTruckBehavior", 32, 32, }, + { "PrisonBehavior", 32, 32 }, + { "PrisonVisual", 32, 32 }, + { "PropagandaCenterBehavior", 16, 16 }, +#endif + { "PropagandaTowerBehavior", 16, 16 }, + { "BunkerBusterBehavior", 16, 16 }, + { "ObjectTracker", 128, 32 }, + { "OCLUpdate", 16, 16 }, + { "BodyParticleSystem", 196, 64 }, + { "HighlanderBody", 2048, 128 }, + { "UndeadBody", 32, 32 }, + { "HordeUpdate", 128, 32 }, + { "ImmortalBody", 128, 256 }, + { "InactiveBody", 2048, 32 }, + { "InstantDeathBehavior", 512, 32 }, + { "LaserUpdate", 32, 32 }, + { "PointDefenseLaserUpdate", 32, 32 }, + { "CleanupHazardUpdate", 32, 32 }, + { "AutoFindHealingUpdate", 256, 32 }, + { "CommandButtonHuntUpdate", 512, 8 }, + { "PilotFindVehicleUpdate", 256, 32 }, + { "DemoTrapUpdate", 32, 32 }, + { "ParticleUplinkCannonUpdate", 16, 16 }, + { "SpectreGunshipUpdate", 8, 8 }, + { "SpectreGunshipDeploymentUpdate", 8, 8 }, + { "BaikonurLaunchPower", 4, 4 }, + { "RadiusDecalUpdate", 16, 16 }, + { "RadiusDecalBehavior", 32, 32 }, + { "BattlePlanUpdate", 32, 32 }, + { "LifetimeUpdate", 32, 32 }, + { "LocomotorSetUpgrade", 512, 128 }, + { "LockWeaponCreate", 64, 128 }, + { "AutoDepositUpdate", 256, 32 }, + { "NeutronMissileUpdate", 512, 32 }, + { "MoneyCrateCollide", 48, 16 }, + { "NeutronMissileSlowDeathBehavior", 8, 8 }, + { "OpenContain", 128, 32 }, + { "OverchargeBehavior", 32, 32 }, + { "OverlordContain", 32, 32 }, + { "HelixContain", 32, 32 }, + { "ParachuteContain", 128, 32 }, + { "PhysicsBehavior", 600, 32 }, + { "PoisonedBehavior", 512, 64 }, + { "ProductionEntry", 32, 32 }, + { "ProductionUpdate", 256, 32 }, + { "ProjectileStreamUpdate", 32, 32 }, + { "ProneUpdate", 128, 32 }, + { "QueueProductionExitUpdate", 32, 32 }, + { "RadarUpdate", 16, 16 }, + { "RadarUpgrade", 16, 16 }, + { "AnimationSteeringUpdate", 1024, 32 }, + { "SupplyWarehouseCripplingBehavior", 16, 16 }, + { "CostModifierUpgrade", 32, 32 }, + { "ProductionTimeModifierUpgrade", 32, 32 }, + { "UnitProductionBonusUpgrade", 64, 32 }, + { "CashBountyPower", 32, 32 }, + { "CleanupAreaPower", 32, 32 }, + { "ObjectCreationUpgrade", 196, 32 }, + { "MinefieldBehavior", 256, 32 }, + { "JetSlowDeathBehavior", 64, 32 }, + { "BattleBusSlowDeathBehavior", 64, 32 }, + { "RebuildHoleBehavior", 64, 32 }, + { "RebuildHoleExposeDie", 64, 32 }, + { "RepairDockUpdate", 32, 32 }, +#ifdef ALLOW_SURRENDER + { "PrisonDockUpdate", 32, 32 }, +#endif + { "RailedTransportDockUpdate", 16, 16 }, + { "RailedTransportAIUpdate", 16, 16 }, + { "RailedTransportContain", 16, 16 }, + { "RailroadBehavior", 16, 16 }, + { "SalvageCrateCollide", 32, 32 }, + { "ShroudCrateCollide", 32, 32 }, + { "SlavedUpdate", 64, 32 }, + { "SlowDeathBehavior", 1400, 256 }, + { "SpyVisionUpdate", 16, 16 }, + { "DefaultProductionExitUpdate", 32, 32 }, + { "SpawnPointProductionExitUpdate", 32, 32 }, + { "SpawnBehavior", 32, 32 }, + { "SpecialPowerCompletionDie", 32, 32 }, + { "SpecialPowerCreate", 32, 32 }, + { "PreorderCreate", 32, 32 }, + { "SpecialAbility", 512, 32 }, + { "SpecialAbilityUpdate", 512, 32 }, + { "MissileLauncherBuildingUpdate", 32, 32 }, + { "SquishCollide", 512, 32 }, + { "StructureBody", 512, 64 }, + { "HiveStructureBody", 64, 32 }, //Stinger sites + { "StructureCollapseUpdate", 32, 32 }, + { "StructureToppleUpdate", 32, 32 }, + { "SupplyCenterCreate", 32, 32 }, + { "SupplyCenterDockUpdate", 32, 32 }, + { "SupplyCenterProductionExitUpdate", 32, 32 }, + { "SupplyTruckStateMachine", 256, 32 }, + { "SupplyTruckAIUpdate", 32, 32 }, + { "SupplyWarehouseCreate", 48, 16 }, + { "SupplyWarehouseDockUpdate", 48, 16 }, + { "EnemyNearUpdate", 1024, 32 }, + { "TechBuildingBehavior", 32, 32 }, + { "ToppleUpdate", 256, 128 }, + { "TransitionDamageFX", 384, 128 }, + { "TransportAIUpdate", 64, 32 }, + { "TransportContain", 128, 32 }, + { "RiderChangeContain", 128, 32 }, + { "InternetHackContain", 16, 16 }, + { "TunnelContain", 8, 8 }, + { "TunnelContainDie", 32, 32 }, + { "TunnelCreate", 32, 32 }, + { "TurretAI", 256, 32 }, + { "TurretStateMachine", 128, 32 }, + { "TurretSwapUpgrade", 512, 128 }, + { "UnitCrateCollide", 32, 32 }, + { "UnpauseSpecialPowerUpgrade", 32, 32 }, + { "VeterancyCrateCollide", 32, 32 }, + { "VeterancyGainCreate", 512, 128 }, + { "ConvertToCarBombCrateCollide", 256, 128 }, + { "ConvertToHijackedVehicleCrateCollide", 256, 128 }, + { "SabotageCommandCenterCrateCollide", 256, 128 }, + { "SabotageFakeBuildingCrateCollide", 256, 128 }, + { "SabotageInternetCenterCrateCollide", 256, 128 }, + { "SabotageMilitaryFactoryCrateCollide", 256, 128 }, + { "SabotagePowerPlantCrateCollide", 256, 128 }, + { "SabotageSuperweaponCrateCollide", 256, 128 }, + { "SabotageSupplyCenterCrateCollide", 256, 128 }, + { "SabotageSupplyDropzoneCrateCollide", 256, 128 }, + { "JetAIUpdate", 64, 32 }, + { "ChinookAIUpdate", 32, 32 }, + { "WanderAIUpdate", 32, 32 }, + { "TeleporterAIUpdate", 64, 32 }, + { "WaveGuideUpdate", 16, 16 }, + { "ArmorDamageScalarUpdate", 256, 32 }, + { "WeaponBonusUpgrade", 512, 128 }, + { "WeaponSetUpgrade", 512, 128 }, + { "ArmorUpgrade", 512, 128 }, + { "WorkerAIUpdate", 128, 128 }, + { "WorkerStateMachine", 128, 128 }, + { "ChinookAIStateMachine", 32, 32 }, + { "DeployStyleAIUpdate", 32, 32 }, + { "AssaultTransportAIUpdate", 64, 32 }, + { "StreamingArchiveFile", 8, 8 }, + + { "DozerActionStateMachine", 256, 32 }, + { "DozerPrimaryStateMachine", 256, 32 }, + { "W3DDisplayString", 1400, 128 }, + { "W3DDefaultDraw", 1024, 128 }, + { "W3DDebrisDraw", 128, 128 }, + { "W3DDependencyModelDraw", 64, 64 }, + { "W3DLaserDraw", 32, 32 }, + { "W3DModelDraw", 2048, 512 }, + { "W3DOverlordTankDraw", 64, 64 }, + { "W3DOverlordTruckDraw", 64, 64 }, + { "W3DOverlordAircraftDraw", 64, 64 }, + { "W3DPoliceCarDraw", 32, 32 }, + { "W3DProjectileStreamDraw", 32, 32 }, + { "W3DRopeDraw", 32, 32 }, + { "W3DScienceModelDraw", 32, 32 }, + { "W3DSupplyDraw", 40, 16 }, + { "W3DTankDraw", 256, 32 }, + { "W3DTreeDraw", 16, 16 }, + { "W3DPropDraw", 16, 16 }, + { "W3DTracerDraw", 64, 32 }, + { "W3DTruckDraw", 128, 32 }, + { "W3DTankTruckDraw", 32, 16 }, + { "W3DTreeTextureClass", 4, 4 }, + { "DefaultSpecialPower", 32, 32 }, + { "OCLSpecialPower", 96, 32 }, + { "FireWeaponPower", 32, 32 }, +#ifdef ALLOW_DEMORALIZE + { "DemoralizeSpecialPower", 16, 16, }, +#endif + { "CashHackSpecialPower", 32, 32 }, + { "CommandSetUpgrade", 32, 32 }, + { "PassengersFireUpgrade", 32, 32 }, + { "GrantUpgradeCreate", 256, 32 }, + { "GrantScienceUpgrade", 256, 32 }, + { "ReplaceObjectUpgrade", 32, 32 }, + { "ModelConditionUpgrade", 32, 32 }, + { "SpyVisionSpecialPower", 256, 32 }, + { "StealthDetectorUpdate", 256, 32 }, + { "StealthUpdate", 512, 128 }, + { "StealthUpgrade", 256, 32 }, + { "StatusBitsUpgrade", 128, 128 }, + { "SubObjectsUpgrade", 128, 128 }, + { "ExperienceScalarUpgrade", 256, 128 }, + { "MaxHealthUpgrade", 128, 128 }, + { "WeaponBonusUpgrade", 128, 64 }, + { "StickyBombUpdate", 64, 32 }, + { "FireOCLAfterWeaponCooldownUpdate", 64, 32 }, + { "HijackerUpdate", 64, 32 }, + { "ChinaMinesUpgrade", 64, 32 }, + { "PowerPlantUpdate", 48, 16 }, + { "PowerPlantUpgrade", 48, 16 }, + { "DefectorSpecialPower", 16, 16 }, + { "CheckpointUpdate", 16, 16 }, + { "MobNexusContain", 128, 32 }, + { "MobMemberSlavedUpdate", 64, 32 }, + { "EMPUpdate", 64, 32 }, + { "LeafletDropBehavior", 64, 32 }, + { "Overridable", 32, 32 }, + + { "W3DGameWindow", 700, 256 }, + { "SuccessState", 32, 32 }, + { "FailureState", 32, 32 }, + { "ContinueState", 32, 32 }, + { "SleepState", 32, 32 }, + + { "AIDockWaitForClearanceState", 256, 32 }, + { "AIDockProcessDockState", 256, 32 }, + { "AIGuardInnerState", 32, 32 }, + { "AIGuardIdleState", 32, 32 }, + { "AIGuardOuterState", 32, 32 }, + { "AIGuardReturnState", 32, 32 }, + { "AIGuardPickUpCrateState", 32, 32 }, + { "AIGuardAttackAggressorState", 32, 32 }, + { "AIGuardRetaliateInnerState", 32, 32 }, + { "AIGuardRetaliateIdleState", 32, 32 }, + { "AIGuardRetaliateOuterState", 32, 32 }, + { "AIGuardRetaliateReturnState", 32, 32 }, + { "AIGuardRetaliatePickUpCrateState", 32, 32 }, + { "AIGuardRetaliateAttackAggressorState", 32, 32 }, + { "AITNGuardInnerState", 32, 32 }, + { "AITNGuardIdleState", 32, 32 }, + { "AITNGuardOuterState", 32, 32 }, + { "AITNGuardReturnState", 32, 32 }, + { "AITNGuardPickUpCrateState", 32, 32 }, + { "AITNGuardAttackAggressorState", 32, 32 }, + { "AIIdleState", 2400, 32 }, + { "AIRappelState", 600, 32 }, + { "AIBusyState", 600, 32 }, + { "AIWaitState", 600, 32 }, + { "AIAttackState", 4096, 32 }, + { "AIAttackSquadState", 600, 32 }, + { "AIDeadState", 600, 32 }, + { "AIDockState", 600, 32 }, + { "AIExitState", 600, 32 }, + { "AIExitInstantlyState", 600, 32 }, + { "AIGuardState", 600, 32 }, + { "AIGuardRetaliateState", 600, 32 }, + { "AITunnelNetworkGuardState", 600, 32 }, + { "AIHuntState", 600, 32 }, + { "AIAttackAreaState", 600, 32 }, + { "AIFaceState", 1200, 32 }, + { "ApproachState", 600, 32 }, + { "DeliveringState", 600, 32 }, + { "ConsiderNewApproachState", 600, 32 }, + { "RecoverFromOffMapState", 600, 32 }, + { "HeadOffMapState", 600, 32 }, + { "CleanUpState", 600, 32 }, + { "HackInternetState", 600, 32 }, + { "PackingState", 600, 32 }, + { "UnpackingState", 600, 32 }, + { "SupplyTruckWantsToPickUpOrDeliverBoxesState", 600, 32 }, + { "RegroupingState", 600, 32 }, + { "DockingState", 600, 32 }, + { "ChinookEvacuateState", 32, 32 }, + { "ChinookHeadOffMapState", 32, 32 }, + { "ChinookTakeoffOrLandingState", 32, 32 }, + { "ChinookCombatDropState", 32, 32 }, + { "DozerActionPickActionPosState", 256, 32 }, + { "DozerActionMoveToActionPosState", 256, 32 }, + { "DozerActionDoActionState", 256, 32 }, + { "DozerPrimaryIdleState", 256, 32 }, + { "DozerActionState", 256, 32 }, + { "DozerPrimaryGoingHomeState", 256, 32 }, + { "JetAwaitingRunwayState", 64, 32 }, + { "JetOrHeliCirclingDeadAirfieldState", 64, 32 }, + { "HeliTakeoffOrLandingState", 64, 32 }, + { "VtolTakeoffOrLandingState", 64, 32 }, + { "JetOrHeliParkOrientState", 64, 32 }, + { "VtolParkOrientState", 64, 32 }, + { "JetOrHeliReloadAmmoState", 64, 32 }, + { "SupplyTruckBusyState", 600, 32 }, + { "SupplyTruckIdleState", 600, 32 }, + { "ActAsDozerState", 600, 32 }, + { "ActAsSupplyTruckState", 600, 32 }, + { "AIDockApproachState", 256, 32 }, + { "AIDockAdvancePositionState", 256, 32 }, + { "AIDockMoveToEntryState", 256, 32 }, + { "AIDockMoveToDockState", 256, 32 }, + { "AIDockMoveToExitState", 256, 32 }, + { "AIDockMoveToRallyState", 256, 32 }, + { "AIMoveToState", 600, 32 }, + { "AIMoveOutOfTheWayState", 600, 32 }, + { "AIMoveAndTightenState", 600, 32 }, + { "AIMoveAwayFromRepulsorsState", 600, 32 }, + { "AIAttackApproachTargetState", 96, 32 }, + { "AIAttackPursueTargetState", 96, 32 }, + { "AIAttackAimAtTargetState", 96, 32 }, + { "AIAttackFireWeaponState", 256, 32 }, + { "AIPickUpCrateState", 4096, 32 }, + { "AIFollowWaypointPathState", 1200, 32 }, + { "AIFollowWaypointPathExactState", 1200, 32 }, + { "AIWanderInPlaceState", 600, 32 }, + { "AIFollowPathState", 1200, 32 }, + { "AIMoveAndEvacuateState", 1200, 32 }, + { "AIMoveAndDeleteState", 600, 32 }, + { "AIEnterState", 600, 32 }, + { "JetOrHeliReturningToDeadAirfieldState", 64, 32 }, + { "JetOrHeliReturnForLandingState", 64, 32 }, + { "TurretAIIdleState", 600, 32 }, + { "TurretAIIdleScanState", 600, 32 }, + { "TurretAIAimTurretState", 600, 32 }, + { "TurretAIRecenterTurretState", 600, 32 }, + { "TurretAIHoldTurretState", 600, 32 }, + { "JetOrHeliTaxiState", 64, 32 }, + { "JetTakeoffOrLandingState", 64, 32 }, + { "JetPauseBeforeTakeoffState", 64, 32 }, + { "AIAttackMoveToState", 600, 32 }, + { "AIAttackFollowWaypointPathState", 1200, 32 }, + { "AIWanderState", 600, 32 }, + { "AIPanicState", 600, 32 }, + { "ChinookMoveToBldgState", 32, 32 }, + { "ChinookRecordCreationState", 32, 32 }, + { "ScienceInfo", 96, 32 }, + { "RankInfo", 32, 32 }, + + { "FireWeaponNugget", 32, 32 }, + { "AttackNugget", 32, 32 }, + { "DeliverPayloadNugget", 48, 32 }, + { "ApplyRandomForceNugget", 32, 32 }, + { "GenericObjectCreationNugget", 632, 32 }, + { "SoundFXNugget", 320, 32 }, + { "TracerFXNugget", 32, 32 }, + { "RayEffectFXNugget", 32, 32 }, + { "LightPulseFXNugget", 68, 32 }, + { "ViewShakeFXNugget", 140, 32 }, + { "TerrainScorchFXNugget", 48, 32 }, + { "ParticleSystemFXNugget", 832, 32 }, + { "FXListAtBonePosFXNugget", 32, 32 }, + { "Squad", 256, 32 }, + { "BuildListInfo", 400, 64 }, + + { "ScriptGroup", 128, 32 }, + { "OrCondition", 1024, 256 }, + { "ScriptAction", 2600, 512 }, + { "Script", 1024, 256 }, + { "Parameter", 8192, 1024 }, + { "Condition", 2048, 256 }, + { "Template", 32, 32 }, + { "ScriptList", 32, 32 }, + { "AttackPriorityInfo", 32, 32 }, + { "SequentialScript", 32, 32 }, + { "Win32LocalFile", 1024, 256 }, + { "StdLocalFile", 1024, 256 }, + { "RAMFile", 32, 32 }, + { "BattlePlanBonuses", 32, 32 }, + { "KindOfPercentProductionChange", 32, 32 }, + { "UserParser", 4096, 256 }, + { "XferBlockData", 32, 32 }, + { "EvaCheckInfo", 52, 16 }, + { "SuperweaponInfo", 32, 32 }, + { "NamedTimerInfo", 32, 32 }, + { "PopupMessageData", 32, 32 }, + { "FloatingTextData", 32, 32 }, + { "MapObject", 5000, 1024 }, + { "Waypoint", 1024, 32 }, + { "PolygonTrigger", 64, 64 }, + { "Bridge", 32, 32 }, + { "Mapping", 384, 64 }, + { "OutputChunk", 32, 32 }, + { "InputChunk", 32, 32 }, + { "AnimateWindow", 32, 32 }, + { "GameFont", 32, 32 }, + { "NetCommandRef", 256, 32 }, + { "GameMessageArgument", 1024, 256 }, + { "GameMessageParserArgumentType", 32, 32 }, + { "GameMessageParser", 32, 32 }, + { "WeaponBonusSet", 96, 32 }, + { "Campaign", 32, 32 }, + { "Mission", 88, 32 }, + { "ModalWindow", 32, 32 }, + { "NetPacket", 32, 32 }, + { "AISideInfo", 32, 32 }, + { "AISideBuildList", 32, 32 }, + { "MetaMapRec", 256, 32 }, + { "TransportStatus", 32, 32 }, + { "Anim2DTemplate", 32, 32 }, + { "ObjectTypes", 32, 32 }, + { "NetCommandList", 512, 32 }, + { "TurretAIData", 256, 32 }, + { "NetCommandMsg", 32, 32 }, + { "NetGameCommandMsg", 64, 32 }, + { "NetAckBothCommandMsg", 32, 32 }, + { "NetAckStage1CommandMsg", 32, 32 }, + { "NetAckStage2CommandMsg", 32, 32 }, + { "NetFrameCommandMsg", 32, 32 }, + { "NetPlayerLeaveCommandMsg", 32, 32 }, + { "NetRunAheadMetricsCommandMsg", 32, 32 }, + { "NetRunAheadCommandMsg", 32, 32 }, + { "NetDestroyPlayerCommandMsg", 32, 32 }, + { "NetDisconnectFrameCommandMsg", 32, 32 }, + { "NetDisconnectScreenOffCommandMsg", 32, 32 }, + { "NetFrameResendRequestCommandMsg", 32, 32 }, + { "NetKeepAliveCommandMsg", 32, 32 }, + { "NetDisconnectKeepAliveCommandMsg", 32, 32 }, + { "NetDisconnectPlayerCommandMsg", 32, 32 }, + { "NetPacketRouterQueryCommandMsg", 32, 32 }, + { "NetPacketRouterAckCommandMsg", 32, 32 }, + { "NetDisconnectChatCommandMsg", 32, 32 }, + { "NetChatCommandMsg", 32, 32 }, + { "NetDisconnectVoteCommandMsg", 32, 32 }, + { "NetProgressCommandMsg", 32, 32 }, + { "NetWrapperCommandMsg", 32, 32 }, + { "NetFileCommandMsg", 32, 32 }, + { "NetFileAnnounceCommandMsg", 32, 32 }, + { "NetFileProgressCommandMsg", 32, 32 }, + { "NetCommandWrapperListNode", 32, 32 }, + { "NetCommandWrapperList", 32, 32 }, + { "Connection", 32, 32 }, + { "User", 32, 32 }, + { "FrameDataManager", 32, 32 }, + { "DrawableIconInfo", 32, 32 }, + { "TintEnvelope", 128, 32 }, + { "DynamicAudioEventRTS", 4000, 256 }, + { "DrawableLocoInfo", 128, 32 }, + { "W3DPrototypeClass", 512, 256 }, + { "EnumeratedIP", 32, 32 }, + { "WaterTransparencySetting", 4, 4 }, + { "WeatherSetting", 4, 4 }, + + // W3D pools! + { "BoxPrototypeClass", 128, 128 }, + { "SpherePrototypeClass", 32, 32 }, + { "SoundRenderObjPrototypeClass", 32, 32 }, + { "RingPrototypeClass", 32, 32 }, + { "PrimitivePrototypeClass", 8192, 32 }, + { "HModelPrototypeClass", 256, 32 }, + { "ParticleEmitterPrototypeClass", 32, 32 }, + { "NullPrototypeClass", 32, 32 }, + { "HLodPrototypeClass", 700, 128 }, + { "HLodDefClass", 700, 128 }, + { "DistLODPrototypeClass", 32, 32 }, + { "DazzlePrototypeClass", 32, 32 }, + { "CollectionPrototypeClass", 32, 32 }, + { "BoxPrototypeClass", 256, 32 }, + { "AggregatePrototypeClass", 32, 32 }, + { "OBBoxRenderObjClass", 512, 128 }, + { "AABoxRenderObjClass", 32, 32 }, + { "VertexMaterialClass", 6000, 2048 }, + { "TextureClass", 1200, 256 }, + { "CloudMapTerrainTextureClass", 4, 4 }, + { "ScorchTextureClass", 4, 4 }, + { "LightMapTerrainTextureClass", 4, 4 }, + { "AlphaEdgeTextureClass", 4, 4 }, + { "AlphaTerrainTextureClass", 4, 4 }, + { "TerrainTextureClass", 4, 4 }, + { "MeshClass", 14000, 2000 }, + { "HTreeClass", 2048, 512 }, + { "HLodClass", 2048, 512 }, + { "MeshModelClass", 8192, 32 }, + { "ShareBufferClass", 32768, 1024 }, + { "AABTreeClass", 300, 128 }, + { "MotionChannelClass", 16384, 32 }, + { "BitChannelClass", 84, 32 }, + { "TimeCodedMotionChannelClass", 116, 32 }, + { "AdaptiveDeltaMotionChannelClass", 32, 32 }, + { "TimeCodedBitChannelClass", 32, 32 }, + { "UVBufferClass", 8192, 32 }, + { "TexBufferClass", 384, 128 }, + { "MatBufferClass", 256, 128 }, + { "MatrixMapperClass", 32, 32 }, + { "ScaleTextureMapperClass", 32, 32 }, + { "LinearOffsetTextureMapperClass", 96, 32 }, + { "GridTextureMapperClass", 32, 32 }, + { "RotateTextureMapperClass", 32, 32 }, + { "SineLinearOffsetTextureMapperClass", 32, 32 }, + { "StepLinearOffsetTextureMapperClass", 32, 32 }, + { "ZigZagLinearOffsetTextureMapperClass", 32, 32 }, + { "ClassicEnvironmentMapperClass", 32, 32 }, + { "EnvironmentMapperClass", 256, 32 }, + { "EdgeMapperClass", 32, 32 }, + { "WSClassicEnvironmentMapperClass", 32, 32 }, + { "WSEnvironmentMapperClass", 32, 32 }, + { "GridClassicEnvironmentMapperClass", 32, 32 }, + { "GridEnvironmentMapperClass", 32, 32 }, + { "ScreenMapperClass", 32, 32 }, + { "RandomTextureMapperClass", 32, 32 }, + { "BumpEnvTextureMapperClass", 32, 32 }, + { "MeshLoadContextClass", 4, 4 }, + { "MaterialInfoClass", 8192, 32 }, + { "MeshMatDescClass", 8192, 32 }, + { "TextureLoadTaskClass", 256, 32 }, + { "SortingNodeStruct", 288, 32 }, + { "ProxyArrayClass", 32, 32 }, + { "Line3DClass", 8, 8 }, + { "Render2DClass", 64, 32 }, + { "SurfaceClass", 128, 32 }, + { "FontCharsClassCharDataStruct", 1024, 32 }, + { "FontCharsBuffer", 16, 4 }, + { "FVFInfoClass", 152, 64 }, + { "TerrainTracksRenderObjClass", 128, 32 }, + { "DynamicIBAccessClass", 32, 32 }, + { "DX8IndexBufferClass", 128, 32 }, + { "SortingIndexBufferClass", 32, 32 }, + { "DX8VertexBufferClass", 128, 32 }, + { "SortingVertexBufferClass", 32, 32 }, + { "DynD3DMATERIAL8", 8192, 32 }, + { "DynamicMatrix3D", 512, 32 }, + { "MeshGeometryClass", 32, 32 }, + { "DynamicMeshModel", 32, 32 }, + { "GapFillerClass", 32, 32 }, + { "FontCharsClass", 64, 32 }, + { "ThumbnailManagerClass", 32, 32}, + { "SmudgeSet", 32, 32}, + { "Smudge", 128, 32}, + { 0, 0, 0 } +}; + +//----------------------------------------------------------------------------- +void userMemoryAdjustPoolSize(const char *poolName, Int& initialAllocationCount, Int& overflowAllocationCount) +{ + if (initialAllocationCount > 0) + return; + + for (const PoolSizeRec* p = sizes; p->name != NULL; ++p) + { + if (strcmp(p->name, poolName) == 0) + { + initialAllocationCount = p->initial; + overflowAllocationCount = p->overflow; + return; + } + } + + DEBUG_CRASH(("Initial size for pool %s not found -- you should add it to MemoryInit.cpp\n",poolName)); +} + +//----------------------------------------------------------------------------- +static Int roundUpMemBound(Int i) +{ + const int MEM_BOUND_ALIGNMENT = 4; + + if (i < MEM_BOUND_ALIGNMENT) + return MEM_BOUND_ALIGNMENT; + else + return (i + (MEM_BOUND_ALIGNMENT-1)) & ~(MEM_BOUND_ALIGNMENT-1); +} + +//----------------------------------------------------------------------------- +void userMemoryManagerInitPools() +{ + // note that we MUST use stdio stuff here, and not the normal game file system + // (with bigfile support, etc), because that relies on memory pools, which + // aren't yet initialized properly! so rely ONLY on straight stdio stuff here. + // (not even AsciiString. thanks.) + + // since we're called prior to main, the cur dir might not be what + // we expect. so do it the hard way. + char buf[_MAX_PATH]; + ::GetModuleFileName(NULL, buf, sizeof(buf)); + char* pEnd = buf + strlen(buf); + while (pEnd != buf) + { + if (*pEnd == '\\') + { + *pEnd = 0; + break; + } + --pEnd; + } + strcat(buf, "\\Data\\INI\\MemoryPools.ini"); + + FILE* fp = fopen(buf, "r"); + if (fp) + { + char poolName[256]; + int initial, overflow; + while (fgets(buf, _MAX_PATH, fp)) + { + if (buf[0] == ';') + continue; + if (sscanf(buf, "%s %d %d", poolName, &initial, &overflow ) == 3) + { + for (PoolSizeRec* p = sizes; p->name != NULL; ++p) + { + if (stricmp(p->name, poolName) == 0) + { + // currently, these must be multiples of 4. so round up. + p->initial = roundUpMemBound(initial); + p->overflow = roundUpMemBound(overflow); + break; // from for-p + } + } + } + } + fclose(fp); + } +} + From 46045251382d0ba22e33303d601994fd794aa8a8 Mon Sep 17 00:00:00 2001 From: andreasw Date: Tue, 8 Jul 2025 18:48:39 +0200 Subject: [PATCH 33/42] Allow WeaponBonusUpdate to work on Enemies Fix USA Chemsuit issue Fix VeterancyCrateCollide --- .../Include/GameLogic/Module/ArmorUpgrade.h | 2 ++ .../GameLogic/Module/WeaponBonusUpdate.h | 2 ++ .../CrateCollide/VeterancyCrateCollide.cpp | 13 ++++++---- .../Object/Update/ArmorDamageScalarUpdate.cpp | 2 +- .../Object/Update/WeaponBonusUpdate.cpp | 24 +++++++++++++++---- .../GameLogic/Object/Upgrade/ArmorUpgrade.cpp | 15 ++++++++++++ 6 files changed, 48 insertions(+), 10 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ArmorUpgrade.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ArmorUpgrade.h index a62a3244c6e..035ec956c35 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ArmorUpgrade.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ArmorUpgrade.h @@ -96,6 +96,8 @@ class ArmorUpgrade : public UpgradeModule virtual void upgradeImplementation( ); ///< Here's the actual work of Upgrading virtual Bool isSubObjectsUpgrade() { return false; } + virtual Bool attemptUpgrade(UpgradeMaskType keyMask); + }; //----------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/WeaponBonusUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/WeaponBonusUpdate.h index 4e96ce96771..2de8a786a29 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/WeaponBonusUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/WeaponBonusUpdate.h @@ -75,6 +75,8 @@ class WeaponBonusUpdateModuleData : public UpdateModuleData KindOfMaskType m_requiredAffectKindOf; ///< Must be set on target KindOfMaskType m_forbiddenAffectKindOf; ///< Must be clear on target + Int m_targetsMask; ///< ALLIES, ENEMIES or NEUTRALS + Bool m_isAffectAirborne; ///< Affect Airborne targets UnsignedInt m_bonusDuration; ///< How long a hit lasts on target UnsignedInt m_bonusDelay; ///< How often to pulse Real m_bonusRange; ///< How far to affect diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/VeterancyCrateCollide.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/VeterancyCrateCollide.cpp index ae7b98d1a14..fe925ed337c 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/VeterancyCrateCollide.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/VeterancyCrateCollide.cpp @@ -129,10 +129,12 @@ Bool VeterancyCrateCollide::executeCrateBehavior( Object *other ) AIUpdateInterface *ai = (AIUpdateInterface*)getObject()->getAIUpdateInterface(); const VeterancyCrateCollideModuleData *md = getVeterancyCrateCollideModuleData(); - if( !ai || ai->getGoalObject() != other ) - { - return false; - } + if (md->m_isPilot) { + if (!ai || ai->getGoalObject() != other) + { + return false; + } + } Int levelsToGain = getLevelsToGain(); Real range = md->m_rangeOfEffect; @@ -149,7 +151,8 @@ Bool VeterancyCrateCollide::executeCrateBehavior( Object *other ) PartitionFilterSamePlayer othersPlayerFilter( other->getControllingPlayer() ); PartitionFilterSameMapStatus filterMapStatus(other); PartitionFilter *filters[] = { &othersPlayerFilter, &filterMapStatus, NULL }; - ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( other, range, FROM_CENTER_2D, filters, ITER_FASTEST ); + // ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( other, range, FROM_CENTER_2D, filters, ITER_FASTEST ); + ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( other->getPosition(), range, FROM_CENTER_2D, filters, ITER_FASTEST); MemoryPoolObjectHolder hold(iter); for( Object *potentialObject = iter->first(); potentialObject; potentialObject = iter->next() ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ArmorDamageScalarUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ArmorDamageScalarUpdate.cpp index f542bf64fcc..8f679b8c704 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ArmorDamageScalarUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ArmorDamageScalarUpdate.cpp @@ -191,7 +191,7 @@ void ArmorDamageScalarUpdate::applyEffect(void) { for( Object *currentObj = iter->first(); currentObj != NULL; currentObj = iter->next() ) { - if (data->m_isAffectAirborne || (!currentObj->isKindOf(KINDOF_AIRCRAFT) && !currentObj->isAirborneTarget())) { + if (data->m_isAffectAirborne || !currentObj->isAirborneTarget()) { if (currentObj->isAnyKindOf(data->m_allowAffectKindOf) && !currentObj->isAnyKindOf(data->m_forbiddenAffectKindOf)) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/WeaponBonusUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/WeaponBonusUpdate.cpp index 4fd6799cd0e..50d5d7fdfee 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/WeaponBonusUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/WeaponBonusUpdate.cpp @@ -68,6 +68,8 @@ WeaponBonusUpdateModuleData::WeaponBonusUpdateModuleData() { m_requiredAffectKindOf.clear(); m_forbiddenAffectKindOf.clear(); + m_targetsMask = 0; + m_isAffectAirborne = true; m_bonusDuration = 0; m_bonusDelay = 0; m_bonusRange = 0; @@ -83,6 +85,8 @@ void WeaponBonusUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) { { "RequiredAffectKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( WeaponBonusUpdateModuleData, m_requiredAffectKindOf ) }, { "ForbiddenAffectKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( WeaponBonusUpdateModuleData, m_forbiddenAffectKindOf ) }, + { "AffectsTargets", INI::parseBitString32, TheWeaponAffectsMaskNames, offsetof(WeaponBonusUpdateModuleData, m_targetsMask) }, + { "AffectAirborne", INI::parseBool, NULL, offsetof(WeaponBonusUpdateModuleData, m_isAffectAirborne) }, { "BonusDuration", INI::parseDurationUnsignedInt, NULL, offsetof( WeaponBonusUpdateModuleData, m_bonusDuration ) }, { "BonusDelay", INI::parseDurationUnsignedInt, NULL, offsetof( WeaponBonusUpdateModuleData, m_bonusDelay ) }, { "BonusRange", INI::parseReal, NULL, offsetof( WeaponBonusUpdateModuleData, m_bonusRange ) }, @@ -116,13 +120,17 @@ struct tempWeaponBonusData // Hey Steven, bite me! hahahaha _Lowercase_ since KindOfMaskType m_requiredMask; KindOfMaskType m_forbiddenMask; TintStatus m_tintStatus; + Bool m_isAffectAirborne; }; void containIteratingDoTempWeaponBonus( Object *passenger, void *voidData) { tempWeaponBonusData *data = (tempWeaponBonusData *)voidData; - if( passenger->isKindOfMulti(data->m_requiredMask, data->m_forbiddenMask) ) - passenger->doTempWeaponBonus(data->m_type, data->m_duration, data->m_tintStatus); + if (passenger->isKindOfMulti(data->m_requiredMask, data->m_forbiddenMask)) { + if (data->m_isAffectAirborne || !passenger->isAirborneTarget()) { + passenger->doTempWeaponBonus(data->m_type, data->m_duration, data->m_tintStatus); + } + } } //------------------------------------------------------------------------------------------------- @@ -132,7 +140,12 @@ UpdateSleepTime WeaponBonusUpdate::update( void ) const WeaponBonusUpdateModuleData * data = getWeaponBonusUpdateModuleData(); Object *me = getObject(); - PartitionFilterRelationship relationship( me, PartitionFilterRelationship::ALLOW_ALLIES ); + Int targetFlags = 0; + if (data->m_targetsMask & WEAPON_AFFECTS_ALLIES) targetFlags |= PartitionFilterRelationship::ALLOW_ALLIES; + if (data->m_targetsMask & WEAPON_AFFECTS_ENEMIES) targetFlags |= PartitionFilterRelationship::ALLOW_ENEMIES; + if (data->m_targetsMask & WEAPON_AFFECTS_NEUTRALS) targetFlags |= PartitionFilterRelationship::ALLOW_NEUTRAL; + + PartitionFilterRelationship relationship(me, targetFlags); PartitionFilterSameMapStatus filterMapStatus(me); PartitionFilterAlive filterAlive; @@ -153,13 +166,16 @@ UpdateSleepTime WeaponBonusUpdate::update( void ) weaponBonusData.m_requiredMask = data->m_requiredAffectKindOf; weaponBonusData.m_forbiddenMask = data->m_forbiddenAffectKindOf; weaponBonusData.m_tintStatus = data->m_tintStatus; + weaponBonusData.m_isAffectAirborne = data->m_isAffectAirborne; for( Object *currentObj = iter->first(); currentObj != NULL; currentObj = iter->next() ) { if( currentObj->isKindOfMulti(data->m_requiredAffectKindOf, data->m_forbiddenAffectKindOf) ) { - currentObj->doTempWeaponBonus(data->m_bonusConditionType, data->m_bonusDuration, data->m_tintStatus); + if (data->m_isAffectAirborne || !currentObj->isAirborneTarget()) { + currentObj->doTempWeaponBonus(data->m_bonusConditionType, data->m_bonusDuration, data->m_tintStatus); + } } if( currentObj->getContain() ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/ArmorUpgrade.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/ArmorUpgrade.cpp index b0c158ae459..51b8c177785 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/ArmorUpgrade.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/ArmorUpgrade.cpp @@ -98,6 +98,21 @@ ArmorUpgrade::~ArmorUpgrade( void ) { } +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +Bool ArmorUpgrade::attemptUpgrade(UpgradeMaskType keyMask) +{ + if (isTriggeredBy("Upgrade_AmericaChemicalSuits")) + { + Drawable* draw = getObject()->getDrawable(); + if (!draw) { + return false; + } + } + + return UpgradeMux::attemptUpgrade(keyMask); +} + //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void ArmorUpgrade::upgradeImplementation( ) From c249b35191d16d91a0f1140b936177d527bebf0c Mon Sep 17 00:00:00 2001 From: andreasw Date: Thu, 10 Jul 2025 17:54:03 +0200 Subject: [PATCH 34/42] fix bunker buster clearing tunnels under construction --- .../Source/GameLogic/Object/Behavior/BunkerBusterBehavior.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BunkerBusterBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BunkerBusterBehavior.cpp index 504c233b345..f3300d9dbed 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BunkerBusterBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BunkerBusterBehavior.cpp @@ -204,7 +204,7 @@ void BunkerBusterBehavior::bustTheBunker( void ) objectForFX = target; ContainModuleInterface *contain = target->getContain(); - if ( contain && contain->isBustable() ) // Was that object something that bunkerbusters bust? + if ( contain && contain->isBustable() && !target->testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION)) // Was that object something that bunkerbusters bust? { if ( modData->m_occupantDamageWeaponTemplate ) From fe310d1b1a2cbb50fac69e4eeae0d10416b89e03 Mon Sep 17 00:00:00 2001 From: andreasw Date: Tue, 15 Jul 2025 17:30:28 +0200 Subject: [PATCH 35/42] fix bug with scatterTarget MinScale --- GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp index 7c0745fbb28..9aee7fff840 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp @@ -2834,7 +2834,8 @@ Bool Weapon::privateFireWeapon( Coord2D scatterOffset = m_template->getScatterTargetsVector().at( targetIndex ); // Scale scatter target based on range - if (Real minScale = m_template->getScatterTargetMinScalar() > 0) { + Real minScale = m_template->getScatterTargetMinScalar(); + if (minScale > 0.0) { Real minRange = m_template->getMinimumAttackRange(); Real maxRange = m_template->getUnmodifiedAttackRange(); Real range = sqrt(ThePartitionManager->getDistanceSquared(sourceObj, victimPos, FROM_CENTER_2D)); From fae487a8362a25801c068863189d574db79eb106 Mon Sep 17 00:00:00 2001 From: Andi Date: Sun, 20 Jul 2025 13:26:35 +0200 Subject: [PATCH 36/42] Add delayed upgrade module --- GeneralsMD/Code/GameEngine/CMakeLists.txt | 2 + .../GameEngine/Include/GameLogic/Locomotor.h | 1027 +-- .../GameLogic/Module/DelayedUpgradeBehavior.h | 142 + .../GameLogic/Module/LocomotorSetUpgrade.h | 6 +- .../GameLogic/Module/ParkingPlaceBehavior.h | 476 +- .../Source/Common/System/MemoryInit.cpp | 1629 ++--- .../Source/Common/Thing/ModuleFactory.cpp | 1492 ++--- .../Behavior/DelayedUpgradeBehavior.cpp | 248 + .../Source/GameLogic/Object/Locomotor.cpp | 5674 +++++++++-------- .../Object/Upgrade/LocomotorSetUpgrade.cpp | 30 +- 10 files changed, 5582 insertions(+), 5144 deletions(-) create mode 100644 GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DelayedUpgradeBehavior.h create mode 100644 GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DelayedUpgradeBehavior.cpp diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index c846d683c87..40a8255edde 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -325,6 +325,7 @@ set(GAMEENGINE_SRC Include/GameLogic/Module/FireWeaponUpdate.h Include/GameLogic/Module/FireWeaponWhenDamagedBehavior.h Include/GameLogic/Module/FireWeaponWhenDeadBehavior.h + Include/GameLogic/Module/DelayedUpgradeBehavior.h Include/GameLogic/Module/FlammableUpdate.h Include/GameLogic/Module/FlightDeckBehavior.h Include/GameLogic/Module/FloatUpdate.h @@ -859,6 +860,7 @@ set(GAMEENGINE_SRC Source/GameLogic/Object/Behavior/FreeFallProjectileBehavior.cpp Source/GameLogic/Object/Behavior/FireWeaponWhenDamagedBehavior.cpp Source/GameLogic/Object/Behavior/FireWeaponWhenDeadBehavior.cpp + Source/GameLogic/Object/Behavior/DelayedUpgradeBehavior.cpp Source/GameLogic/Object/Behavior/FlightDeckBehavior.cpp Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h index a60a7d5a7e9..dfd03f1f638 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h @@ -1,511 +1,516 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: Locomotor.h ///////////////////////////////////////////////////////////////////////////////// -// Author: Steven Johnson, Feb 2002 -// Desc: Locomotor Descriptions -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __Locomotor_H_ -#define __Locomotor_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "Common/NameKeyGenerator.h" -#include "Common/Override.h" -#include "Common/Snapshot.h" -#include "GameLogic/Damage.h" -#include "GameLogic/LocomotorSet.h" - -// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// -class Locomotor; -class LocomotorTemplate; -class INI; -class PhysicsBehavior; -enum BodyDamageType CPP_11(: Int); -enum PhysicsTurningType CPP_11(: Int); - -// if we ever re-enable jets circling for landing, we need this. so keep in around just in case. (srj) -#define NO_CIRCLE_FOR_LANDING - -//------------------------------------------------------------------------------------------------- -enum LocomotorAppearance CPP_11(: Int) -{ - LOCO_LEGS_TWO, - LOCO_WHEELS_FOUR, - LOCO_TREADS, - LOCO_HOVER, - LOCO_THRUST, - LOCO_WINGS, - LOCO_CLIMBER, // human climber - backs down cliffs. - LOCO_OTHER, - LOCO_MOTORCYCLE -}; - -enum LocomotorPriority CPP_11(: Int) -{ - LOCO_MOVES_BACK=0, // In a group, this one moves toward the back - LOCO_MOVES_MIDDLE=1, // In a group, this one stays in the middle - LOCO_MOVES_FRONT=2 // In a group, this one moves toward the front of the group -}; - -#ifdef DEFINE_LOCO_APPEARANCE_NAMES -static const char *TheLocomotorAppearanceNames[] = -{ - "TWO_LEGS", - "FOUR_WHEELS", - "TREADS", - "HOVER", - "THRUST", - "WINGS", - "CLIMBER", - "OTHER", - "MOTORCYCLE", - - NULL -}; -#endif - -//------------------------------------------------------------------------------------------------- -enum LocomotorBehaviorZ CPP_11(: Int) -{ - Z_NO_Z_MOTIVE_FORCE, // does whatever physics tells it, but has no z-force of its own. - Z_SEA_LEVEL, // keep at surface-of-water level - Z_SURFACE_RELATIVE_HEIGHT, // try to follow a specific height relative to terrain/water height - Z_ABSOLUTE_HEIGHT, // try follow a specific height regardless of terrain/water height - Z_FIXED_SURFACE_RELATIVE_HEIGHT, // stays fixed at surface-rel height, regardless of physics - Z_FIXED_ABSOLUTE_HEIGHT, // stays fixed at absolute height, regardless of physics - Z_RELATIVE_TO_GROUND_AND_BUILDINGS, // stays fixed at surface-rel height including buildings, regardless of physics - Z_SMOOTH_RELATIVE_TO_HIGHEST_LAYER // try to follow a height relative to the highest layer. -}; - -#ifdef DEFINE_LOCO_Z_NAMES -static const char *TheLocomotorBehaviorZNames[] = -{ - "NO_Z_MOTIVE_FORCE", - "SEA_LEVEL", - "SURFACE_RELATIVE_HEIGHT", - "ABSOLUTE_HEIGHT", - "FIXED_SURFACE_RELATIVE_HEIGHT", - "FIXED_ABSOLUTE_HEIGHT", - "FIXED_RELATIVE_TO_GROUND_AND_BUILDINGS", - "RELATIVE_TO_HIGHEST_LAYER", - - NULL -}; -#endif - -//------------------------------------------------------------------------------------------------- -class LocomotorTemplate : public Overridable -{ - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( LocomotorTemplate, "LocomotorTemplate" ) - friend class Locomotor; - -public: - - LocomotorTemplate(); - - /// field table for loading the values from an INI - const FieldParse* getFieldParse() const; - - void friend_setName(const AsciiString& n) { m_name = n; } - - void validate(); - -protected: - - -private: - /** - Units check: - - -- Velocity: dist/frame - -- Acceleration: dist/(frame*frame) - -- Forces: (mass*dist)/(frame*frame) - */ - AsciiString m_name; - LocomotorSurfaceTypeMask m_surfaces; ///< flags indicating the kinds of surfaces we can use - Real m_maxSpeed; ///< max speed - Real m_maxSpeedDamaged; ///< max speed when "damaged" - Real m_minSpeed; ///< we should never brake past this - Real m_maxTurnRate; ///< max rate at which we can turn, in rads/frame - Real m_maxTurnRateDamaged; ///< max turn rate when "damaged" - Real m_acceleration; ///< max acceleration - Real m_accelerationDamaged; ///< max acceleration when damaged - Real m_lift; ///< max lifting acceleration (flying objects only) - Real m_liftDamaged; ///< max lift when damaged - Real m_braking; ///< max braking (deceleration) - Real m_minTurnSpeed; ///< we must be going >= this speed in order to turn - Real m_preferredHeight; ///< our preferred height (if flying) - Real m_preferredHeightDamping; ///< how aggressively to adjust to preferred height: 1.0 = very much so, 0.1 = gradually, etc - Real m_circlingRadius; ///< for flying things, the radius at which they circle their "maintain" destination. (pos = cw, neg = ccw, 0 = smallest possible) - Real m_speedLimitZ; ///< try to avoid going up or down at more than this speed, if possible - Real m_extra2DFriction; ///< extra 2dfriction to apply (via Physics) - Real m_maxThrustAngle; ///< THRUST locos only: how much we deflect our thrust angle - LocomotorBehaviorZ m_behaviorZ; ///< z-axis behavior - LocomotorAppearance m_appearance; ///< how we should diddle the Drawable to imitate this motion - LocomotorPriority m_movePriority; ///< Where we move - front, middle, back. - - Real m_accelPitchLimit; ///< Maximum amount we will pitch up under acceleration (including recoil.) - Real m_decelPitchLimit; ///< Maximum amount we will pitch down under deceleration (including recoil.) - Real m_bounceKick; ///< How much simulating rough terrain "bounces" a wheel up. - Real m_pitchStiffness; ///< How stiff the springs are forward & back. - Real m_rollStiffness; ///< How stiff the springs are side to side. - Real m_pitchDamping; ///< How good the shock absorbers are. - Real m_rollDamping; ///< How good the shock absorbers are. - Real m_pitchByZVelCoef; ///< How much we pitch in response to z-speed. - Real m_thrustRoll; ///< Thrust roll around X axis - Real m_wobbleRate; ///< how fast thrust things "wobble" - Real m_minWobble; ///< how much thrust things "wobble" - Real m_maxWobble; ///< how much thrust things "wobble" - Real m_forwardVelCoef; ///< How much we pitch in response to speed. - Real m_lateralVelCoef; ///< How much we roll in response to speed. - Real m_forwardAccelCoef; ///< How much we pitch in response to acceleration. - Real m_lateralAccelCoef; ///< How much we roll in response to acceleration. - Real m_uniformAxialDamping; ///< For Attenuating the pitch and roll rates - Real m_turnPivotOffset; ///< should we pivot around noncenter? (-1.0 = rear, 0.0 = center, 1.0 = front) - Int m_airborneTargetingHeight; ///< The height transition at witch I should mark myself as a AA target. - - Real m_closeEnoughDist; ///< How close we have to approach the end of a path before stopping - Bool m_isCloseEnoughDist3D; ///< And is that calculation 3D, for very rare cases that need to move straight down. - Real m_ultraAccurateSlideIntoPlaceFactor; ///< how much we can fudge turning when ultra-accurate - - Bool m_locomotorWorksWhenDead; ///< should locomotor continue working even when object is "dead"? - Bool m_allowMotiveForceWhileAirborne; ///< can we apply motive when airborne? - Bool m_apply2DFrictionWhenAirborne; // apply "2d friction" even when airborne... useful for realistic-looking movement - Bool m_downhillOnly; // pinewood derby, moves only by gravity pulling downhill - Bool m_stickToGround; // if true, can't leave ground - Bool m_canMoveBackward; // if true, can move backwards. - Bool m_hasSuspension; ///< If true, calculate 4 wheel independent suspension values. - Real m_maximumWheelExtension; ///< Maximum distance wheels can move down. (negative value) - Real m_maximumWheelCompression; ///< Maximum distance wheels can move up. (positive value) - Real m_wheelTurnAngle; ///< How far the front wheels can turn. - - // Fields for wander locomotor - Real m_wanderWidthFactor; - Real m_wanderLengthFactor; - Real m_wanderAboutPointRadius; - - - Real m_rudderCorrectionDegree; - Real m_rudderCorrectionRate; - Real m_elevatorCorrectionDegree; - Real m_elevatorCorrectionRate; -}; - -typedef OVERRIDE LocomotorTemplateOverride; - -// --------------------------------------------------------- -class Locomotor : public MemoryPoolObject, public Snapshot -{ - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(Locomotor, "Locomotor" ) - - friend class LocomotorStore; - -public: - - void setPhysicsOptions(Object* obj); - - void locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalPos, - Real onPathDistToGoal, Real desiredSpeed, Bool *blocked); - void locoUpdate_moveTowardsAngle(Object* obj, Real angle); - /** - Kill any current (2D) velocity (but stay at current position, or as close as possible) - - return true if we can maintain the position without being called every frame (eg, we are - resting on the ground), false if not (eg, we are hovering or circling) - */ - Bool locoUpdate_maintainCurrentPosition(Object* obj); - - Real getMaxSpeedForCondition(BodyDamageType condition) const; ///< get max speed given condition - Real getMaxTurnRate(BodyDamageType condition) const; ///< get max turning rate given condition - Real getMaxAcceleration(BodyDamageType condition) const; ///< get acceleration given condition - Real getMaxLift(BodyDamageType condition) const; ///< get acceleration given condition - Real getBraking() const; ///< get braking given condition - - inline Real getPreferredHeight() const { return m_preferredHeight;} ///< Just return preferredheight, no damage consideration - inline void restorePreferredHeightFromTemplate() { m_preferredHeight = m_template->m_preferredHeight; }; - inline Real getPreferredHeightDamping() const { return m_preferredHeightDamping;} - inline LocomotorAppearance getAppearance() const { return m_template->m_appearance; } - inline LocomotorPriority getMovePriority() const { return m_template->m_movePriority; } - inline LocomotorSurfaceTypeMask getLegalSurfaces() const { return m_template->m_surfaces; } - - inline AsciiString getTemplateName() const { return m_template->m_name;} - inline Real getMinSpeed() const { return m_template->m_minSpeed;} - inline Real getAccelPitchLimit() const { return m_template->m_accelPitchLimit;} ///< Maximum amount we will pitch up or down under acceleration (including recoil.) - inline Real getDecelPitchLimit() const { return m_template->m_decelPitchLimit;} ///< Maximum amount we will pitch down under deceleration (including recoil.) - inline Real getBounceKick() const { return m_template->m_bounceKick;} ///< How much simulating rough terrain "bounces" a wheel up. - inline Real getPitchStiffness() const { return m_template->m_pitchStiffness;} ///< How stiff the springs are forward & back. - inline Real getRollStiffness() const { return m_template->m_rollStiffness;} ///< How stiff the springs are side to side. - inline Real getPitchDamping() const { return m_template->m_pitchDamping;} ///< How good the shock absorbers are. - inline Real getRollDamping() const { return m_template->m_rollDamping;} ///< How good the shock absorbers are. - inline Real getPitchByZVelCoef() const { return m_template->m_pitchByZVelCoef;} ///< How much we pitch in response to speed. - inline Real getThrustRoll() const { return m_template->m_thrustRoll; } ///< Thrust roll - inline Real getWobbleRate() const { return m_template->m_wobbleRate; } ///< how fast thrust things "wobble" - inline Real getMaxWobble() const { return m_template->m_maxWobble; } ///< how much thrust things "wobble" - inline Real getMinWobble() const { return m_template->m_minWobble; } ///< how much thrust things "wobble" - - inline Real getForwardVelCoef() const { return m_template->m_forwardVelCoef;} ///< How much we pitch in response to speed. - inline Real getLateralVelCoef() const { return m_template->m_lateralVelCoef;} ///< How much we roll in response to speed. - inline Real getForwardAccelCoef() const { return m_template->m_forwardAccelCoef;} ///< How much we pitch in response to acceleration. - inline Real getLateralAccelCoef() const { return m_template->m_lateralAccelCoef;} ///< How much we roll in response to acceleration. - inline Real getUniformAxialDamping() const { return m_template->m_uniformAxialDamping;} ///< How much we roll in response to acceleration. - inline Real getTurnPivotOffset() const { return m_template->m_turnPivotOffset;} - inline Bool getApply2DFrictionWhenAirborne() const { return m_template->m_apply2DFrictionWhenAirborne; } - inline Bool getIsDownhillOnly() const { return m_template->m_downhillOnly; } - inline Bool getAllowMotiveForceWhileAirborne() const { return m_template->m_allowMotiveForceWhileAirborne; } - inline Int getAirborneTargetingHeight() const { return m_template->m_airborneTargetingHeight; } - inline Bool getLocomotorWorksWhenDead() const { return m_template->m_locomotorWorksWhenDead; } - inline Bool getStickToGround() const { return m_template->m_stickToGround; } - inline Real getCloseEnoughDist() const { return m_closeEnoughDist; } - inline Bool isCloseEnoughDist3D() const { return getFlag(IS_CLOSE_ENOUGH_DIST_3D); } - inline Bool hasSuspension() const {return m_template->m_hasSuspension;} - inline Bool canMoveBackwards() const {return m_template->m_canMoveBackward;} - inline Real getMaxWheelExtension() const {return m_template->m_maximumWheelExtension;} - inline Real getMaxWheelCompression() const {return m_template->m_maximumWheelCompression;} - inline Real getWheelTurnAngle() const {return m_template->m_wheelTurnAngle;} - - - inline Real getRudderCorrectionDegree() const { return m_template->m_rudderCorrectionDegree;} ///< How much we roll in response to acceleration. - inline Real getRudderCorrectionRate() const { return m_template->m_rudderCorrectionRate;} ///< How much we roll in response to acceleration. - inline Real getElevatorCorrectionDegree() const { return m_template->m_elevatorCorrectionDegree;} ///< How much we roll in response to acceleration. - inline Real getElevatorCorrectionRate() const { return m_template->m_elevatorCorrectionRate;} ///< How much we roll in response to acceleration. - - - inline Real getWanderWidthFactor() const {return m_template->m_wanderWidthFactor;} - inline Real getWanderAboutPointRadius() const {return m_template->m_wanderAboutPointRadius;} - - Real calcMinTurnRadius(BodyDamageType condition, Real* timeToTravelThatDist) const; - - /// this is handy for doing things like forcing helicopters to crash realistically: cut their lift. - inline void setMaxLift(Real lift) { m_maxLift = lift; } - inline void setMaxSpeed(Real speed) - { - DEBUG_ASSERTCRASH(!(speed <= 0.0f && m_template->m_appearance == LOCO_THRUST), ("THRUST locos may not have zero speeds!\n")); - m_maxSpeed = speed; - } - inline void setMaxAcceleration(Real accel) { m_maxAccel = accel; } - inline void setMaxBraking(Real braking) { m_maxBraking = braking; } - inline void setMaxTurnRate(Real turn) { m_maxTurnRate = turn; } - inline void setAllowInvalidPosition(Bool allow) { setFlag(ALLOW_INVALID_POSITION, allow); } - inline void setCloseEnoughDist( Real dist ) { m_closeEnoughDist = dist; } - inline void setCloseEnoughDist3D( Bool setting ) { setFlag(IS_CLOSE_ENOUGH_DIST_3D, setting); } - inline Bool isInvalidPositionAllowed() const { return getFlag( ALLOW_INVALID_POSITION ); } - - inline void setPreferredHeight( Real height ) { m_preferredHeight = height; } - -#ifdef CIRCLE_FOR_LANDING - /** - if we are climbing/diving more than this, circle as needed rather - than just diving or climbing directly. (only useful for Winged things) - */ - inline void setAltitudeChangeThresholdForCircling(Real a) { m_circleThresh = a; } -#endif - - /** - when off (the default), things get to adjust their z-pos as their - loco says (in particular, airborne things tend to try to fly at a preferred height). - - when on, they do their best to reach the specified zpos, even if it's not at their preferred height. - this is used mainly for force missiles to swoop in on their target, and to force airplane takeoff/landing - to go smoothly. - */ - inline void setUsePreciseZPos(Bool u) { setFlag(PRECISE_Z_POS, u); } - - /** - when off (the default), units slow down as they approach their target. - - when on, units go full speed till the end, and may overshoot their target. - this is useful mainly in some weird, temporary situations where we know we are - going to follow this move with another one... or for carbombs. - */ - inline void setNoSlowDownAsApproachingDest(Bool u) { setFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST, u); } - - /** - when off (the default), units do their normal stuff. - - when on, we cheat and make very precise motion, regardless of loco settings. - this is accomplished by cranking up the unit's turning rate, friction, lift (for airborne things), - and possibly other things. This is useful mainly when doing maneuvers where precision - is VITAL, such as airplane takeoff/landing. - - For ground units, it also allows units to have a destination off of a pathfing grid. - - */ - inline void setUltraAccurate(Bool u) { setFlag(ULTRA_ACCURATE, u); } - inline Bool isUltraAccurate() const { return getFlag(ULTRA_ACCURATE); } - - inline Bool isMovingBackwards(void) const {return getFlag(MOVING_BACKWARDS);} - - void startMove(void); ///< Indicates that a move is starting, primarily to reset the donut timer. jba. - - static Real getSurfaceHtAtPt(Real x, Real y); - -protected: - void moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionLegsWander(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionHover(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - - void maintainCurrentPositionThrust(Object* obj, PhysicsBehavior *physics); - void maintainCurrentPositionOther(Object* obj, PhysicsBehavior *physics); - void maintainCurrentPositionLegs(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } - void maintainCurrentPositionWheels(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } - void maintainCurrentPositionTreads(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } - void maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physics); - void maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physics); - - PhysicsTurningType rotateTowardsPosition(Object* obj, const Coord3D& goalPos, Real *relAngle=NULL); - - /* - return true if we can maintain the position without being called every frame (eg, we are - resting on the ground), false if not (eg, we are hovering or circling) - */ - Bool handleBehaviorZ(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos); - PhysicsTurningType rotateObjAroundLocoPivot(Object* obj, const Coord3D& goalPos, Real maxTurnRate, Real *relAngle = NULL); - - Real calcLiftToUseAtPt(Object* obj, PhysicsBehavior *physics, Real curZ, Real surfaceAtPt, Real preferredHeight); - - Bool fixInvalidPosition(Object* obj, PhysicsBehavior *physics); - -protected: - // snapshot methods - virtual void crc( Xfer *xfer ); - virtual void xfer( Xfer *xfer ); - virtual void loadPostProcess( void ); - -protected: - - Locomotor(const LocomotorTemplate* tmpl); - - // Note, "Law of the Big Three" applies here - //Locomotor(); -- nope, we don't have a default ctor. (srj) - Locomotor(const Locomotor& that); - Locomotor& operator=(const Locomotor& that); - //~Locomotor(); - -private: - - // - // Note: these values are saved in save files, so you MUST NOT REMOVE OR CHANGE - // existing values! - // - enum LocoFlag - { - IS_BRAKING = 0, - ALLOW_INVALID_POSITION, - MAINTAIN_POS_IS_VALID, - PRECISE_Z_POS, - NO_SLOW_DOWN_AS_APPROACHING_DEST, - OVER_WATER, // To allow things to move slower/faster over water and do special effects - ULTRA_ACCURATE, - MOVING_BACKWARDS, // If we are moving backwards. - DOING_THREE_POINT_TURN, // If we are doing a 3 pt turn. - CLIMBING, // If we are in the process of climbing. - IS_CLOSE_ENOUGH_DIST_3D, - OFFSET_INCREASING - }; - - inline Bool getFlag(LocoFlag f) const { return (m_flags & (1 << f)) != 0; } - inline void setFlag(LocoFlag f, Bool b) { if (b) m_flags |= (1< > LocomotorTemplateMap; - - LocomotorTemplateMap m_locomotorTemplates; - -}; - -// EXTERNALS ////////////////////////////////////////////////////////////////////////////////////// -extern LocomotorStore *TheLocomotorStore; - -#endif // __Locomotor_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: Locomotor.h ///////////////////////////////////////////////////////////////////////////////// +// Author: Steven Johnson, Feb 2002 +// Desc: Locomotor Descriptions +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __Locomotor_H_ +#define __Locomotor_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Common/NameKeyGenerator.h" +#include "Common/Override.h" +#include "Common/Snapshot.h" +#include "GameLogic/Damage.h" +#include "GameLogic/LocomotorSet.h" + +// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// +class Locomotor; +class LocomotorTemplate; +class INI; +class PhysicsBehavior; +enum BodyDamageType CPP_11(: Int); +enum PhysicsTurningType CPP_11(: Int); + +// if we ever re-enable jets circling for landing, we need this. so keep in around just in case. (srj) +#define NO_CIRCLE_FOR_LANDING + +//------------------------------------------------------------------------------------------------- +enum LocomotorAppearance CPP_11(: Int) +{ + LOCO_LEGS_TWO, + LOCO_WHEELS_FOUR, + LOCO_TREADS, + LOCO_HOVER, + LOCO_THRUST, + LOCO_WINGS, + LOCO_CLIMBER, // human climber - backs down cliffs. + LOCO_OTHER, + LOCO_MOTORCYCLE +}; + +enum LocomotorPriority CPP_11(: Int) +{ + LOCO_MOVES_BACK=0, // In a group, this one moves toward the back + LOCO_MOVES_MIDDLE=1, // In a group, this one stays in the middle + LOCO_MOVES_FRONT=2 // In a group, this one moves toward the front of the group +}; + +#ifdef DEFINE_LOCO_APPEARANCE_NAMES +static const char *TheLocomotorAppearanceNames[] = +{ + "TWO_LEGS", + "FOUR_WHEELS", + "TREADS", + "HOVER", + "THRUST", + "WINGS", + "CLIMBER", + "OTHER", + "MOTORCYCLE", + + NULL +}; +#endif + +//------------------------------------------------------------------------------------------------- +enum LocomotorBehaviorZ CPP_11(: Int) +{ + Z_NO_Z_MOTIVE_FORCE, // does whatever physics tells it, but has no z-force of its own. + Z_SEA_LEVEL, // keep at surface-of-water level + Z_SURFACE_RELATIVE_HEIGHT, // try to follow a specific height relative to terrain/water height + Z_ABSOLUTE_HEIGHT, // try follow a specific height regardless of terrain/water height + Z_FIXED_SURFACE_RELATIVE_HEIGHT, // stays fixed at surface-rel height, regardless of physics + Z_FIXED_ABSOLUTE_HEIGHT, // stays fixed at absolute height, regardless of physics + Z_RELATIVE_TO_GROUND_AND_BUILDINGS, // stays fixed at surface-rel height including buildings, regardless of physics + Z_SMOOTH_RELATIVE_TO_HIGHEST_LAYER // try to follow a height relative to the highest layer. +}; + +#ifdef DEFINE_LOCO_Z_NAMES +static const char *TheLocomotorBehaviorZNames[] = +{ + "NO_Z_MOTIVE_FORCE", + "SEA_LEVEL", + "SURFACE_RELATIVE_HEIGHT", + "ABSOLUTE_HEIGHT", + "FIXED_SURFACE_RELATIVE_HEIGHT", + "FIXED_ABSOLUTE_HEIGHT", + "FIXED_RELATIVE_TO_GROUND_AND_BUILDINGS", + "RELATIVE_TO_HIGHEST_LAYER", + + NULL +}; +#endif + +//------------------------------------------------------------------------------------------------- +class LocomotorTemplate : public Overridable +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( LocomotorTemplate, "LocomotorTemplate" ) + friend class Locomotor; + +public: + + LocomotorTemplate(); + + /// field table for loading the values from an INI + const FieldParse* getFieldParse() const; + + void friend_setName(const AsciiString& n) { m_name = n; } + + void validate(); + +protected: + + +private: + /** + Units check: + + -- Velocity: dist/frame + -- Acceleration: dist/(frame*frame) + -- Forces: (mass*dist)/(frame*frame) + */ + AsciiString m_name; + LocomotorSurfaceTypeMask m_surfaces; ///< flags indicating the kinds of surfaces we can use + Real m_maxSpeed; ///< max speed + Real m_maxSpeedDamaged; ///< max speed when "damaged" + Real m_minSpeed; ///< we should never brake past this + Real m_maxTurnRate; ///< max rate at which we can turn, in rads/frame + Real m_maxTurnRateDamaged; ///< max turn rate when "damaged" + Real m_acceleration; ///< max acceleration + Real m_accelerationDamaged; ///< max acceleration when damaged + Real m_lift; ///< max lifting acceleration (flying objects only) + Real m_liftDamaged; ///< max lift when damaged + Real m_braking; ///< max braking (deceleration) + Real m_minTurnSpeed; ///< we must be going >= this speed in order to turn + Real m_preferredHeight; ///< our preferred height (if flying) + Real m_preferredHeightDamping; ///< how aggressively to adjust to preferred height: 1.0 = very much so, 0.1 = gradually, etc + Real m_circlingRadius; ///< for flying things, the radius at which they circle their "maintain" destination. (pos = cw, neg = ccw, 0 = smallest possible) + Real m_speedLimitZ; ///< try to avoid going up or down at more than this speed, if possible + Real m_extra2DFriction; ///< extra 2dfriction to apply (via Physics) + Real m_maxThrustAngle; ///< THRUST locos only: how much we deflect our thrust angle + LocomotorBehaviorZ m_behaviorZ; ///< z-axis behavior + LocomotorAppearance m_appearance; ///< how we should diddle the Drawable to imitate this motion + LocomotorPriority m_movePriority; ///< Where we move - front, middle, back. + + Real m_accelPitchLimit; ///< Maximum amount we will pitch up under acceleration (including recoil.) + Real m_decelPitchLimit; ///< Maximum amount we will pitch down under deceleration (including recoil.) + Real m_bounceKick; ///< How much simulating rough terrain "bounces" a wheel up. + Real m_pitchStiffness; ///< How stiff the springs are forward & back. + Real m_rollStiffness; ///< How stiff the springs are side to side. + Real m_pitchDamping; ///< How good the shock absorbers are. + Real m_rollDamping; ///< How good the shock absorbers are. + Real m_pitchByZVelCoef; ///< How much we pitch in response to z-speed. + Real m_thrustRoll; ///< Thrust roll around X axis + Real m_wobbleRate; ///< how fast thrust things "wobble" + Real m_minWobble; ///< how much thrust things "wobble" + Real m_maxWobble; ///< how much thrust things "wobble" + Real m_forwardVelCoef; ///< How much we pitch in response to speed. + Real m_lateralVelCoef; ///< How much we roll in response to speed. + Real m_forwardAccelCoef; ///< How much we pitch in response to acceleration. + Real m_lateralAccelCoef; ///< How much we roll in response to acceleration. + Real m_uniformAxialDamping; ///< For Attenuating the pitch and roll rates + Real m_turnPivotOffset; ///< should we pivot around noncenter? (-1.0 = rear, 0.0 = center, 1.0 = front) + Int m_airborneTargetingHeight; ///< The height transition at witch I should mark myself as a AA target. + + Real m_closeEnoughDist; ///< How close we have to approach the end of a path before stopping + Bool m_isCloseEnoughDist3D; ///< And is that calculation 3D, for very rare cases that need to move straight down. + Real m_ultraAccurateSlideIntoPlaceFactor; ///< how much we can fudge turning when ultra-accurate + + Bool m_locomotorWorksWhenDead; ///< should locomotor continue working even when object is "dead"? + Bool m_allowMotiveForceWhileAirborne; ///< can we apply motive when airborne? + Bool m_apply2DFrictionWhenAirborne; // apply "2d friction" even when airborne... useful for realistic-looking movement + Bool m_downhillOnly; // pinewood derby, moves only by gravity pulling downhill + Bool m_stickToGround; // if true, can't leave ground + Bool m_canMoveBackward; // if true, can move backwards. + Bool m_hasSuspension; ///< If true, calculate 4 wheel independent suspension values. + Real m_maximumWheelExtension; ///< Maximum distance wheels can move down. (negative value) + Real m_maximumWheelCompression; ///< Maximum distance wheels can move up. (positive value) + Real m_wheelTurnAngle; ///< How far the front wheels can turn. + + // Fields for wander locomotor + Real m_wanderWidthFactor; + Real m_wanderLengthFactor; + Real m_wanderAboutPointRadius; + + + Real m_rudderCorrectionDegree; + Real m_rudderCorrectionRate; + Real m_elevatorCorrectionDegree; + Real m_elevatorCorrectionRate; +}; + +typedef OVERRIDE LocomotorTemplateOverride; + +// --------------------------------------------------------- +class Locomotor : public MemoryPoolObject, public Snapshot +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(Locomotor, "Locomotor" ) + + friend class LocomotorStore; + +public: + + void setPhysicsOptions(Object* obj); + + void locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalPos, + Real onPathDistToGoal, Real desiredSpeed, Bool *blocked); + void locoUpdate_moveTowardsAngle(Object* obj, Real angle); + /** + Kill any current (2D) velocity (but stay at current position, or as close as possible) + + return true if we can maintain the position without being called every frame (eg, we are + resting on the ground), false if not (eg, we are hovering or circling) + */ + Bool locoUpdate_maintainCurrentPosition(Object* obj); + + Real getMaxSpeedForCondition(BodyDamageType condition) const; ///< get max speed given condition + Real getMaxTurnRate(BodyDamageType condition) const; ///< get max turning rate given condition + Real getMaxAcceleration(BodyDamageType condition) const; ///< get acceleration given condition + Real getMaxLift(BodyDamageType condition) const; ///< get acceleration given condition + Real getBraking() const; ///< get braking given condition + + inline Real getPreferredHeight() const { return m_preferredHeight;} ///< Just return preferredheight, no damage consideration + inline void restorePreferredHeightFromTemplate() { m_preferredHeight = m_template->m_preferredHeight; }; + inline Real getPreferredHeightDamping() const { return m_preferredHeightDamping;} + inline LocomotorAppearance getAppearance() const { return m_template->m_appearance; } + inline LocomotorPriority getMovePriority() const { return m_template->m_movePriority; } + inline LocomotorSurfaceTypeMask getLegalSurfaces() const { return m_template->m_surfaces; } + + inline AsciiString getTemplateName() const { return m_template->m_name;} + inline Real getMinSpeed() const { return m_template->m_minSpeed;} + inline Real getAccelPitchLimit() const { return m_template->m_accelPitchLimit;} ///< Maximum amount we will pitch up or down under acceleration (including recoil.) + inline Real getDecelPitchLimit() const { return m_template->m_decelPitchLimit;} ///< Maximum amount we will pitch down under deceleration (including recoil.) + inline Real getBounceKick() const { return m_template->m_bounceKick;} ///< How much simulating rough terrain "bounces" a wheel up. + inline Real getPitchStiffness() const { return m_template->m_pitchStiffness;} ///< How stiff the springs are forward & back. + inline Real getRollStiffness() const { return m_template->m_rollStiffness;} ///< How stiff the springs are side to side. + inline Real getPitchDamping() const { return m_template->m_pitchDamping;} ///< How good the shock absorbers are. + inline Real getRollDamping() const { return m_template->m_rollDamping;} ///< How good the shock absorbers are. + inline Real getPitchByZVelCoef() const { return m_template->m_pitchByZVelCoef;} ///< How much we pitch in response to speed. + inline Real getThrustRoll() const { return m_template->m_thrustRoll; } ///< Thrust roll + inline Real getWobbleRate() const { return m_template->m_wobbleRate; } ///< how fast thrust things "wobble" + inline Real getMaxWobble() const { return m_template->m_maxWobble; } ///< how much thrust things "wobble" + inline Real getMinWobble() const { return m_template->m_minWobble; } ///< how much thrust things "wobble" + + inline Real getForwardVelCoef() const { return m_template->m_forwardVelCoef;} ///< How much we pitch in response to speed. + inline Real getLateralVelCoef() const { return m_template->m_lateralVelCoef;} ///< How much we roll in response to speed. + inline Real getForwardAccelCoef() const { return m_template->m_forwardAccelCoef;} ///< How much we pitch in response to acceleration. + inline Real getLateralAccelCoef() const { return m_template->m_lateralAccelCoef;} ///< How much we roll in response to acceleration. + inline Real getUniformAxialDamping() const { return m_template->m_uniformAxialDamping;} ///< How much we roll in response to acceleration. + inline Real getTurnPivotOffset() const { return m_template->m_turnPivotOffset;} + inline Bool getApply2DFrictionWhenAirborne() const { return m_template->m_apply2DFrictionWhenAirborne; } + inline Bool getIsDownhillOnly() const { return m_template->m_downhillOnly; } + inline Bool getAllowMotiveForceWhileAirborne() const { return m_template->m_allowMotiveForceWhileAirborne; } + inline Int getAirborneTargetingHeight() const { return m_template->m_airborneTargetingHeight; } + inline Bool getLocomotorWorksWhenDead() const { return m_template->m_locomotorWorksWhenDead; } + inline Bool getStickToGround() const { return m_template->m_stickToGround; } + inline Real getCloseEnoughDist() const { return m_closeEnoughDist; } + inline Bool isCloseEnoughDist3D() const { return getFlag(IS_CLOSE_ENOUGH_DIST_3D); } + inline Bool hasSuspension() const {return m_template->m_hasSuspension;} + inline Bool canMoveBackwards() const {return m_template->m_canMoveBackward;} + inline Real getMaxWheelExtension() const {return m_template->m_maximumWheelExtension;} + inline Real getMaxWheelCompression() const {return m_template->m_maximumWheelCompression;} + inline Real getWheelTurnAngle() const {return m_template->m_wheelTurnAngle;} + + + inline Real getRudderCorrectionDegree() const { return m_template->m_rudderCorrectionDegree;} ///< How much we roll in response to acceleration. + inline Real getRudderCorrectionRate() const { return m_template->m_rudderCorrectionRate;} ///< How much we roll in response to acceleration. + inline Real getElevatorCorrectionDegree() const { return m_template->m_elevatorCorrectionDegree;} ///< How much we roll in response to acceleration. + inline Real getElevatorCorrectionRate() const { return m_template->m_elevatorCorrectionRate;} ///< How much we roll in response to acceleration. + + + inline Real getWanderWidthFactor() const {return m_template->m_wanderWidthFactor;} + inline Real getWanderAboutPointRadius() const {return m_template->m_wanderAboutPointRadius;} + + Real calcMinTurnRadius(BodyDamageType condition, Real* timeToTravelThatDist) const; + + /// this is handy for doing things like forcing helicopters to crash realistically: cut their lift. + inline void setMaxLift(Real lift) { m_maxLift = lift; } + inline void setMaxSpeed(Real speed) + { + DEBUG_ASSERTCRASH(!(speed <= 0.0f && m_template->m_appearance == LOCO_THRUST), ("THRUST locos may not have zero speeds!\n")); + m_maxSpeed = speed; + } + inline void setMaxAcceleration(Real accel) { m_maxAccel = accel; } + inline void setMaxBraking(Real braking) { m_maxBraking = braking; } + inline void setMaxTurnRate(Real turn) { m_maxTurnRate = turn; } + inline void setAllowInvalidPosition(Bool allow) { setFlag(ALLOW_INVALID_POSITION, allow); } + inline void setCloseEnoughDist( Real dist ) { m_closeEnoughDist = dist; } + inline void setCloseEnoughDist3D( Bool setting ) { setFlag(IS_CLOSE_ENOUGH_DIST_3D, setting); } + inline Bool isInvalidPositionAllowed() const { return getFlag( ALLOW_INVALID_POSITION ); } + + inline void setPreferredHeight( Real height ) { m_preferredHeight = height; } + +#ifdef CIRCLE_FOR_LANDING + /** + if we are climbing/diving more than this, circle as needed rather + than just diving or climbing directly. (only useful for Winged things) + */ + inline void setAltitudeChangeThresholdForCircling(Real a) { m_circleThresh = a; } +#endif + + /** + when off (the default), things get to adjust their z-pos as their + loco says (in particular, airborne things tend to try to fly at a preferred height). + + when on, they do their best to reach the specified zpos, even if it's not at their preferred height. + this is used mainly for force missiles to swoop in on their target, and to force airplane takeoff/landing + to go smoothly. + */ + inline void setUsePreciseZPos(Bool u) { setFlag(PRECISE_Z_POS, u); } + + /** + when off (the default), units slow down as they approach their target. + + when on, units go full speed till the end, and may overshoot their target. + this is useful mainly in some weird, temporary situations where we know we are + going to follow this move with another one... or for carbombs. + */ + inline void setNoSlowDownAsApproachingDest(Bool u) { setFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST, u); } + + /** + when off (the default), units do their normal stuff. + + when on, we cheat and make very precise motion, regardless of loco settings. + this is accomplished by cranking up the unit's turning rate, friction, lift (for airborne things), + and possibly other things. This is useful mainly when doing maneuvers where precision + is VITAL, such as airplane takeoff/landing. + + For ground units, it also allows units to have a destination off of a pathfing grid. + + */ + inline void setUltraAccurate(Bool u) { setFlag(ULTRA_ACCURATE, u); } + inline Bool isUltraAccurate() const { return getFlag(ULTRA_ACCURATE); } + + inline Bool isMovingBackwards(void) const {return getFlag(MOVING_BACKWARDS);} + + void startMove(void); ///< Indicates that a move is starting, primarily to reset the donut timer. jba. + + static Real getSurfaceHtAtPt(Real x, Real y); + + inline void applySpeedMultiplier(Real scalar) { m_speedMultiplier *= scalar; } + // inline void setSpeedMultiplier(Real value) { m_speedMultiplier = value; } + inline Real getSpeedMultiplier(void) const { return m_speedMultiplier; } + +protected: + void moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionLegsWander(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionHover(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + + void maintainCurrentPositionThrust(Object* obj, PhysicsBehavior *physics); + void maintainCurrentPositionOther(Object* obj, PhysicsBehavior *physics); + void maintainCurrentPositionLegs(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } + void maintainCurrentPositionWheels(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } + void maintainCurrentPositionTreads(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } + void maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physics); + void maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physics); + + PhysicsTurningType rotateTowardsPosition(Object* obj, const Coord3D& goalPos, Real *relAngle=NULL); + + /* + return true if we can maintain the position without being called every frame (eg, we are + resting on the ground), false if not (eg, we are hovering or circling) + */ + Bool handleBehaviorZ(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos); + PhysicsTurningType rotateObjAroundLocoPivot(Object* obj, const Coord3D& goalPos, Real maxTurnRate, Real *relAngle = NULL); + + Real calcLiftToUseAtPt(Object* obj, PhysicsBehavior *physics, Real curZ, Real surfaceAtPt, Real preferredHeight); + + Bool fixInvalidPosition(Object* obj, PhysicsBehavior *physics); + +protected: + // snapshot methods + virtual void crc( Xfer *xfer ); + virtual void xfer( Xfer *xfer ); + virtual void loadPostProcess( void ); + +protected: + + Locomotor(const LocomotorTemplate* tmpl); + + // Note, "Law of the Big Three" applies here + //Locomotor(); -- nope, we don't have a default ctor. (srj) + Locomotor(const Locomotor& that); + Locomotor& operator=(const Locomotor& that); + //~Locomotor(); + +private: + + // + // Note: these values are saved in save files, so you MUST NOT REMOVE OR CHANGE + // existing values! + // + enum LocoFlag + { + IS_BRAKING = 0, + ALLOW_INVALID_POSITION, + MAINTAIN_POS_IS_VALID, + PRECISE_Z_POS, + NO_SLOW_DOWN_AS_APPROACHING_DEST, + OVER_WATER, // To allow things to move slower/faster over water and do special effects + ULTRA_ACCURATE, + MOVING_BACKWARDS, // If we are moving backwards. + DOING_THREE_POINT_TURN, // If we are doing a 3 pt turn. + CLIMBING, // If we are in the process of climbing. + IS_CLOSE_ENOUGH_DIST_3D, + OFFSET_INCREASING + }; + + inline Bool getFlag(LocoFlag f) const { return (m_flags & (1 << f)) != 0; } + inline void setFlag(LocoFlag f, Bool b) { if (b) m_flags |= (1< > LocomotorTemplateMap; + + LocomotorTemplateMap m_locomotorTemplates; + +}; + +// EXTERNALS ////////////////////////////////////////////////////////////////////////////////////// +extern LocomotorStore *TheLocomotorStore; + +#endif // __Locomotor_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DelayedUpgradeBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DelayedUpgradeBehavior.h new file mode 100644 index 00000000000..74228c5902d --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DelayedUpgradeBehavior.h @@ -0,0 +1,142 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: DelayedUpgradeBehavior.h ///////////////////////////////////////////////////////////////////////// +// Author: Colin Day, December 2001 +// Desc: Update that will count down a lifetime and destroy object when it reaches zero +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __DelayedUpgradeBehavior_H_ +#define __DelayedUpgradeBehavior_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// + +#include "GameLogic/Module/BehaviorModule.h" +#include "GameLogic/Module/UpdateModule.h" +#include "GameLogic/Module/UpgradeModule.h" + + +//------------------------------------------------------------------------------------------------- +class DelayedUpgradeBehaviorModuleData : public UpdateModuleData +{ +public: + UpgradeMuxData m_upgradeMuxData; + + Bool m_initiallyActive; + AsciiString m_upgradeToTrigger; + UnsignedInt m_triggerDelay; + UnsignedInt m_triggerNumShots; + + DelayedUpgradeBehaviorModuleData() + { + m_initiallyActive = false; + m_upgradeToTrigger.clear(); + m_triggerDelay = 0; + m_triggerNumShots = 0; + } + + static void buildFieldParse(MultiIniFieldParse& p) + { + static const FieldParse dataFieldParse[] = + { + { "StartsActive", INI::parseBool, NULL, offsetof(DelayedUpgradeBehaviorModuleData, m_initiallyActive) }, + { "UpgradeToTrigger", INI::parseAsciiString, NULL, offsetof(DelayedUpgradeBehaviorModuleData, m_upgradeToTrigger) }, + { "TriggerAfterTime", INI::parseDurationUnsignedInt, NULL, offsetof(DelayedUpgradeBehaviorModuleData, m_triggerDelay) }, + { "TriggerAfterShotsFired", INI::parseUnsignedInt, NULL, offsetof(DelayedUpgradeBehaviorModuleData, m_triggerNumShots) }, + { 0, 0, 0, 0 } + }; + + UpdateModuleData::buildFieldParse(p); + p.add(dataFieldParse); + p.add(UpgradeMuxData::getFieldParse(), offsetof(DelayedUpgradeBehaviorModuleData, m_upgradeMuxData)); + } +}; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class DelayedUpgradeBehavior : public UpdateModule, public UpgradeMux +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(DelayedUpgradeBehavior, "DelayedUpgradeBehavior") + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA(DelayedUpgradeBehavior, DelayedUpgradeBehaviorModuleData) + +private: + UnsignedInt m_triggerFrame; + // UnsignedInt m_shotsLeft; + // TODO: Which weaponslot + Bool m_triggerCompleted; + +public: + + DelayedUpgradeBehavior(Thing* thing, const ModuleData* moduleData); + // virtual destructor prototype provided by memory pool declaration + + // module methods + static Int getInterfaceMask() { return UpdateModule::getInterfaceMask() | MODULEINTERFACE_UPGRADE; } + + // BehaviorModule + virtual UpgradeModuleInterface* getUpgrade() { return this; } + + // UpdateModule + virtual UpdateSleepTime update(); + +protected: + + void triggerUpgrade(); + + virtual Bool resetUpgrade(UpgradeMaskType keyMask); // When this upgrade is removed, we reset our triggers. + + virtual void upgradeImplementation(); + + virtual void getUpgradeActivationMasks(UpgradeMaskType& activation, UpgradeMaskType& conflicting) const + { + getDelayedUpgradeBehaviorModuleData()->m_upgradeMuxData.getUpgradeActivationMasks(activation, conflicting); + } + + virtual void performUpgradeFX() + { + getDelayedUpgradeBehaviorModuleData()->m_upgradeMuxData.performUpgradeFX(getObject()); + } + + virtual void processUpgradeRemoval() + { + // I can't take it any more. Let the record show that I think the UpgradeMux multiple inheritence is CRAP. + getDelayedUpgradeBehaviorModuleData()->m_upgradeMuxData.muxDataProcessUpgradeRemoval(getObject()); + } + + virtual Bool requiresAllActivationUpgrades() const + { + return getDelayedUpgradeBehaviorModuleData()->m_upgradeMuxData.m_requiresAllTriggers; + } + + inline Bool isUpgradeActive() const { return isAlreadyUpgraded(); } + + virtual Bool isSubObjectsUpgrade() { return false; } + +}; + +#endif // __DelayedUpgradeBehavior_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/LocomotorSetUpgrade.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/LocomotorSetUpgrade.h index a093521ffd6..983dd021bbe 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/LocomotorSetUpgrade.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/LocomotorSetUpgrade.h @@ -37,6 +37,7 @@ // FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// class Thing; +enum LocomotorSetType CPP_11(: Int); // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ @@ -47,8 +48,11 @@ class LocomotorSetUpgradeModuleData : public UpgradeModuleData LocomotorSetUpgradeModuleData(void); static void buildFieldParse(MultiIniFieldParse& p); + static void parseLocomotorType(INI* ini, void* instance, void* store, const void* /*userData*/); - Bool m_setUpgraded; ///< Enable or Disable upgraded locomotor + Bool m_setUpgraded; ///< Enable or Disable upgraded locomotor + Bool m_useLocomotorType; ///< Use explicit locomotor type + LocomotorSetType m_LocomotorType; ///< explicit lomotor type //Bool m_needsParkedAircraft; ///< Aircraft attempting this upgrade needs to be stationary in hangar }; //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h index 37d6a164bbe..fb8e53b3809 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h @@ -1,238 +1,238 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: ParkingPlaceBehavior.h ///////////////////////////////////////////////////////////////////////// -// Author: Steven Johnson, June 2002 -// Desc: -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __ParkingPlaceBehavior_H_ -#define __ParkingPlaceBehavior_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "GameLogic/Module/BehaviorModule.h" -#include "GameLogic/Module/DieModule.h" -#include "GameLogic/Module/UpdateModule.h" - -//------------------------------------------------------------------------------------------------- -class ParkingPlaceBehaviorModuleData : public UpdateModuleData -{ -public: - //UnsignedInt m_framesForFullHeal; - Real m_healAmount; -// Real m_extraHealAmount4Helicopters; - Int m_numRows; - Int m_numCols; - Real m_approachHeight; - Real m_landingDeckHeightOffset; - Bool m_hasRunways; // if true, each col has a runway in front of it - Bool m_parkInHangars; // if true, park at the hangar production spot, not the "real" parking place - Real m_damageScalar; // Damage reduction for parked aircraft - Real m_damageScalarUpgraded; // Damage reduction for parked aircraft - AsciiString m_damageScalarUpgradeTrigger; // Upgrade template for damageScalar upgrade - - KindOfMaskType m_kindof; ///< the kind(s) of units that can land here - KindOfMaskType m_kindofnot; ///< the kind(s) of units that must not land here - - ParkingPlaceBehaviorModuleData() - { - m_damageScalarUpgradeTrigger.clear(); - //m_framesForFullHeal = 0; - m_healAmount = 0; -// m_extraHealAmount4Helicopters = 0; - m_numRows = 0; - m_numCols = 0; - m_approachHeight = 0.0f; - m_landingDeckHeightOffset = 0.0f; - m_hasRunways = false; - m_parkInHangars = false; - m_damageScalar = 1.0f; - m_damageScalarUpgraded = 1.0f; - } - - static void buildFieldParse(MultiIniFieldParse& p) - { - UpdateModuleData::buildFieldParse(p); - - static const FieldParse dataFieldParse[] = - { - { "NumRows", INI::parseInt, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_numRows ) }, - { "NumCols", INI::parseInt, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_numCols ) }, - { "ApproachHeight", INI::parseReal, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_approachHeight ) }, - { "LandingDeckHeightOffset", INI::parseReal, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_landingDeckHeightOffset ) }, - { "HasRunways", INI::parseBool, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_hasRunways ) }, - { "ParkInHangars", INI::parseBool, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_parkInHangars ) }, - { "HealAmountPerSecond", INI::parseReal, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_healAmount ) }, -// { "ExtraHealAmount4Helicopters", INI::parseReal, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_extraHealAmount4Helicopters ) }, - { "ParkedUnitsDamageScalar", INI::parseReal, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalar) }, - { "ParkedUnitsDamageScalarUpgraded", INI::parseReal, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalarUpgraded) }, - { "DamageScalarUpgradedTriggeredBy", INI::parseAsciiString, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalarUpgradeTrigger) }, - - { "RequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_kindof) }, - { "ForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_kindofnot) }, - - //{ "TimeForFullHeal", INI::parseDurationUnsignedInt, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_framesForFullHeal ) }, - { 0, 0, 0, 0 } - }; - p.add(dataFieldParse); - } - -private: - -}; - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -class ParkingPlaceBehavior : public UpdateModule, - public DieModuleInterface, - public ParkingPlaceBehaviorInterface, - public ExitInterface -{ - - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( ParkingPlaceBehavior, "ParkingPlaceBehavior" ) - MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( ParkingPlaceBehavior, ParkingPlaceBehaviorModuleData ) - -public: - - ParkingPlaceBehavior( Thing *thing, const ModuleData* moduleData ); - // virtual destructor prototype provided by memory pool declaration - - static Int getInterfaceMask() { return UpdateModule::getInterfaceMask() | (MODULEINTERFACE_DIE); } - - // BehaviorModule - virtual DieModuleInterface *getDie( void ) { return this; } - virtual ParkingPlaceBehaviorInterface* getParkingPlaceBehaviorInterface() { return this; } - virtual ExitInterface* getUpdateExitInterface() { return this; } - - // ExitInterface - virtual Bool isExitBusy() const {return FALSE;} ///< Contain style exiters are getting the ability to space out exits, so ask this before reserveDoor as a kind of no-commitment check. - virtual ExitDoorType reserveDoorForExit( const ThingTemplate* objType, Object *specificObject ); - virtual void exitObjectViaDoor( Object *newObj, ExitDoorType exitDoor ); - virtual void unreserveDoorForExit( ExitDoorType exitDoor ); - virtual void exitObjectByBudding( Object *newObj, Object *budHost ) { return; } - - virtual Bool getExitPosition( Coord3D& rallyPoint ) const; - virtual Bool getNaturalRallyPoint( Coord3D& rallyPoint, Bool offset = TRUE ) const; - virtual void setRallyPoint( const Coord3D *pos ); ///< define a "rally point" for units to move towards - virtual const Coord3D *getRallyPoint( void ) const; ///< define a "rally point" for units to move towards - - // UpdateModule - virtual UpdateSleepTime update(); - - // DieModule - virtual void onDie( const DamageInfo *damageInfo ); - - // ParkingPlaceBehaviorInterface - virtual Bool shouldReserveDoorWhenQueued(const ThingTemplate* thing) const; - virtual Bool hasAvailableSpaceFor(const ThingTemplate* thing) const; - virtual Bool hasReservedSpace(ObjectID id) const; - virtual Int getSpaceIndex( ObjectID id ) const; - virtual Bool reserveSpace(ObjectID id, Real parkingOffset, PPInfo* info); - virtual void releaseSpace(ObjectID id); - virtual Bool reserveRunway(ObjectID id, Bool forLanding); - virtual void releaseRunway(ObjectID id); - virtual void calcPPInfo( ObjectID id, PPInfo *info ); - virtual Int getRunwayCount() const { return m_runways.size(); } - virtual ObjectID getRunwayReservation( Int r, RunwayReservationType type ); - virtual void transferRunwayReservationToNextInLineForTakeoff(ObjectID id); - virtual Real getApproachHeight() const { return getParkingPlaceBehaviorModuleData()->m_approachHeight; } - virtual Real getLandingDeckHeightOffset() const { return getParkingPlaceBehaviorModuleData()->m_landingDeckHeightOffset; } - virtual void setHealee(Object* healee, Bool add); - virtual void killAllParkedUnits(); - virtual void defectAllParkedUnits(Team* newTeam, UnsignedInt detectionTime); - virtual Bool calcBestParkingAssignment( ObjectID id, Coord3D *pos, Int *oldIndex = NULL, Int *newIndex = NULL ) { return FALSE; } - virtual const std::vector* getTaxiLocations( ObjectID id ) const { return NULL; } - virtual const std::vector* getCreationLocations( ObjectID id ) const { return NULL; } - -private: - - struct ParkingPlaceInfo - { - Coord3D m_hangarStart; - Real m_hangarStartOrient; - Coord3D m_location; - Coord3D m_prep; - Real m_orientation; - Int m_runway; - ExitDoorType m_door; - ObjectID m_objectInSpace; - Bool m_reservedForExit; - - ParkingPlaceInfo() - { - m_hangarStart.zero(); - m_hangarStartOrient = 0; - m_location.zero(); - m_prep.zero(); - m_orientation = 0; - m_runway = 0; - m_door = DOOR_NONE_AVAILABLE; - m_objectInSpace = INVALID_ID; - m_reservedForExit = false; - } - }; - - struct RunwayInfo - { - Coord3D m_start; - Coord3D m_end; - ObjectID m_inUseBy; - ObjectID m_nextInLineForTakeoff; - Bool m_wasInLine; - }; - - struct HealingInfo - { - ObjectID m_gettingHealedID; - UnsignedInt m_healStartFrame; - }; - - std::vector m_spaces; - std::vector m_runways; - std::list m_healing; // note, this list can vary in size, and be larger than the parking space count - UnsignedInt m_nextHealFrame; - Bool m_gotInfo; - - void buildInfo(); - void purgeDead(); - void resetWakeFrame(); - - ParkingPlaceInfo* findPPI(ObjectID id); - ParkingPlaceInfo* findEmptyPPI(); - - void applyDamageScalar(Object* obj, Real scalarNew, Real scalarOld = 1.0f); - void removeDamageScalar(Object* obj, Real scalar); - Real getDamageScalar(); - void updateDamageScalars(); - - Coord3D m_heliRallyPoint; - Bool m_heliRallyPointExists; ///< Only move to the rally point if this is true - - Bool m_damageScalarUpgradeApplied; -}; - -#endif // __ParkingPlaceBehavior_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: ParkingPlaceBehavior.h ///////////////////////////////////////////////////////////////////////// +// Author: Steven Johnson, June 2002 +// Desc: +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __ParkingPlaceBehavior_H_ +#define __ParkingPlaceBehavior_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "GameLogic/Module/BehaviorModule.h" +#include "GameLogic/Module/DieModule.h" +#include "GameLogic/Module/UpdateModule.h" + +//------------------------------------------------------------------------------------------------- +class ParkingPlaceBehaviorModuleData : public UpdateModuleData +{ +public: + //UnsignedInt m_framesForFullHeal; + Real m_healAmount; +// Real m_extraHealAmount4Helicopters; + Int m_numRows; + Int m_numCols; + Real m_approachHeight; + Real m_landingDeckHeightOffset; + Bool m_hasRunways; // if true, each col has a runway in front of it + Bool m_parkInHangars; // if true, park at the hangar production spot, not the "real" parking place + Real m_damageScalar; // Damage reduction for parked aircraft + Real m_damageScalarUpgraded; // Damage reduction for parked aircraft + AsciiString m_damageScalarUpgradeTrigger; // Upgrade template for damageScalar upgrade + + KindOfMaskType m_kindof; ///< the kind(s) of units that can land here + KindOfMaskType m_kindofnot; ///< the kind(s) of units that must not land here + + ParkingPlaceBehaviorModuleData() + { + m_damageScalarUpgradeTrigger.clear(); + //m_framesForFullHeal = 0; + m_healAmount = 0; +// m_extraHealAmount4Helicopters = 0; + m_numRows = 0; + m_numCols = 0; + m_approachHeight = 0.0f; + m_landingDeckHeightOffset = 0.0f; + m_hasRunways = false; + m_parkInHangars = false; + m_damageScalar = 1.0f; + m_damageScalarUpgraded = 1.0f; + } + + static void buildFieldParse(MultiIniFieldParse& p) + { + UpdateModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + { "NumRows", INI::parseInt, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_numRows ) }, + { "NumCols", INI::parseInt, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_numCols ) }, + { "ApproachHeight", INI::parseReal, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_approachHeight ) }, + { "LandingDeckHeightOffset", INI::parseReal, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_landingDeckHeightOffset ) }, + { "HasRunways", INI::parseBool, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_hasRunways ) }, + { "ParkInHangars", INI::parseBool, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_parkInHangars ) }, + { "HealAmountPerSecond", INI::parseReal, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_healAmount ) }, +// { "ExtraHealAmount4Helicopters", INI::parseReal, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_extraHealAmount4Helicopters ) }, + { "ParkedUnitsDamageScalar", INI::parseReal, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalar) }, + { "ParkedUnitsDamageScalarUpgraded", INI::parseReal, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalarUpgraded) }, + { "DamageScalarUpgradedTriggeredBy", INI::parseAsciiString, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalarUpgradeTrigger) }, + + { "RequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_kindof) }, + { "ForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_kindofnot) }, + + //{ "TimeForFullHeal", INI::parseDurationUnsignedInt, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_framesForFullHeal ) }, + { 0, 0, 0, 0 } + }; + p.add(dataFieldParse); + } + +private: + +}; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class ParkingPlaceBehavior : public UpdateModule, + public DieModuleInterface, + public ParkingPlaceBehaviorInterface, + public ExitInterface +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( ParkingPlaceBehavior, "ParkingPlaceBehavior" ) + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( ParkingPlaceBehavior, ParkingPlaceBehaviorModuleData ) + +public: + + ParkingPlaceBehavior( Thing *thing, const ModuleData* moduleData ); + // virtual destructor prototype provided by memory pool declaration + + static Int getInterfaceMask() { return UpdateModule::getInterfaceMask() | (MODULEINTERFACE_DIE); } + + // BehaviorModule + virtual DieModuleInterface *getDie( void ) { return this; } + virtual ParkingPlaceBehaviorInterface* getParkingPlaceBehaviorInterface() { return this; } + virtual ExitInterface* getUpdateExitInterface() { return this; } + + // ExitInterface + virtual Bool isExitBusy() const {return FALSE;} ///< Contain style exiters are getting the ability to space out exits, so ask this before reserveDoor as a kind of no-commitment check. + virtual ExitDoorType reserveDoorForExit( const ThingTemplate* objType, Object *specificObject ); + virtual void exitObjectViaDoor( Object *newObj, ExitDoorType exitDoor ); + virtual void unreserveDoorForExit( ExitDoorType exitDoor ); + virtual void exitObjectByBudding( Object *newObj, Object *budHost ) { return; } + + virtual Bool getExitPosition( Coord3D& rallyPoint ) const; + virtual Bool getNaturalRallyPoint( Coord3D& rallyPoint, Bool offset = TRUE ) const; + virtual void setRallyPoint( const Coord3D *pos ); ///< define a "rally point" for units to move towards + virtual const Coord3D *getRallyPoint( void ) const; ///< define a "rally point" for units to move towards + + // UpdateModule + virtual UpdateSleepTime update(); + + // DieModule + virtual void onDie( const DamageInfo *damageInfo ); + + // ParkingPlaceBehaviorInterface + virtual Bool shouldReserveDoorWhenQueued(const ThingTemplate* thing) const; + virtual Bool hasAvailableSpaceFor(const ThingTemplate* thing) const; + virtual Bool hasReservedSpace(ObjectID id) const; + virtual Int getSpaceIndex( ObjectID id ) const; + virtual Bool reserveSpace(ObjectID id, Real parkingOffset, PPInfo* info); + virtual void releaseSpace(ObjectID id); + virtual Bool reserveRunway(ObjectID id, Bool forLanding); + virtual void releaseRunway(ObjectID id); + virtual void calcPPInfo( ObjectID id, PPInfo *info ); + virtual Int getRunwayCount() const { return m_runways.size(); } + virtual ObjectID getRunwayReservation( Int r, RunwayReservationType type ); + virtual void transferRunwayReservationToNextInLineForTakeoff(ObjectID id); + virtual Real getApproachHeight() const { return getParkingPlaceBehaviorModuleData()->m_approachHeight; } + virtual Real getLandingDeckHeightOffset() const { return getParkingPlaceBehaviorModuleData()->m_landingDeckHeightOffset; } + virtual void setHealee(Object* healee, Bool add); + virtual void killAllParkedUnits(); + virtual void defectAllParkedUnits(Team* newTeam, UnsignedInt detectionTime); + virtual Bool calcBestParkingAssignment( ObjectID id, Coord3D *pos, Int *oldIndex = NULL, Int *newIndex = NULL ) { return FALSE; } + virtual const std::vector* getTaxiLocations( ObjectID id ) const { return NULL; } + virtual const std::vector* getCreationLocations( ObjectID id ) const { return NULL; } + +private: + + struct ParkingPlaceInfo + { + Coord3D m_hangarStart; + Real m_hangarStartOrient; + Coord3D m_location; + Coord3D m_prep; + Real m_orientation; + Int m_runway; + ExitDoorType m_door; + ObjectID m_objectInSpace; + Bool m_reservedForExit; + + ParkingPlaceInfo() + { + m_hangarStart.zero(); + m_hangarStartOrient = 0; + m_location.zero(); + m_prep.zero(); + m_orientation = 0; + m_runway = 0; + m_door = DOOR_NONE_AVAILABLE; + m_objectInSpace = INVALID_ID; + m_reservedForExit = false; + } + }; + + struct RunwayInfo + { + Coord3D m_start; + Coord3D m_end; + ObjectID m_inUseBy; + ObjectID m_nextInLineForTakeoff; + Bool m_wasInLine; + }; + + struct HealingInfo + { + ObjectID m_gettingHealedID; + UnsignedInt m_healStartFrame; + }; + + std::vector m_spaces; + std::vector m_runways; + std::list m_healing; // note, this list can vary in size, and be larger than the parking space count + UnsignedInt m_nextHealFrame; + Bool m_gotInfo; + + void buildInfo(); + void purgeDead(); + void resetWakeFrame(); + + ParkingPlaceInfo* findPPI(ObjectID id); + ParkingPlaceInfo* findEmptyPPI(); + + void applyDamageScalar(Object* obj, Real scalarNew, Real scalarOld = 1.0f); + void removeDamageScalar(Object* obj, Real scalar); + Real getDamageScalar(); + void updateDamageScalars(); + + Coord3D m_heliRallyPoint; + Bool m_heliRallyPointExists; ///< Only move to the rally point if this is true + + Bool m_damageScalarUpgradeApplied; +}; + +#endif // __ParkingPlaceBehavior_H_ + diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp index 8e69cffbe68..61af1d740f1 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp @@ -1,814 +1,815 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: MemoryInit.cpp -//----------------------------------------------------------------------------- -// -// Westwood Studios Pacific. -// -// Confidential Information -// Copyright (C) 2001 - All Rights Reserved -// -//----------------------------------------------------------------------------- -// -// Project: RTS3 -// -// File name: MemoryInit.cpp -// -// Created: Steven Johnson, August 2001 -// -// Desc: Memory manager -// -// ---------------------------------------------------------------------------- -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -// SYSTEM INCLUDES - -// USER INCLUDES -#include "Lib/BaseType.h" -#include "Common/GameMemory.h" - -#ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -//----------------------------------------------------------------------------- -void userMemoryManagerGetDmaParms(Int *numSubPools, const PoolInitRec **pParms) -{ - static const PoolInitRec defaultDMA[7] = - { - // name, allocsize, initialcount, overflowcount - { "dmaPool_16", 16, 130000, 10000 }, - { "dmaPool_32", 32, 250000, 10000 }, - { "dmaPool_64", 64, 100000, 10000 }, - { "dmaPool_128", 128, 80000, 10000 }, - { "dmaPool_256", 256, 20000, 5000 }, - { "dmaPool_512", 512, 16000, 5000 }, - { "dmaPool_1024", 1024, 6000, 1024} - }; - - *numSubPools = 7; - *pParms = defaultDMA; -} - -//----------------------------------------------------------------------------- -struct PoolSizeRec -{ - const char* name; - Int initial; - Int overflow; -}; - -//----------------------------------------------------------------------------- -// And please be careful of duplicates. They are not rejected. -// not const -- we might override from INI -static PoolSizeRec sizes[] = -{ - { "PartitionContactListNode", 2048, 512 }, - { "BattleshipUpdate", 32, 32 }, - { "FlyToDestAndDestroyUpdate", 32, 32 }, - { "MusicTrack", 32, 32 }, - { "PositionalSoundPool", 32, 32 }, - { "GameMessage", 2048, 32 }, - { "NameKeyBucketPool", 9000, 1024 }, - { "ObjectSellInfo", 16, 16 }, - { "ProductionPrerequisitePool", 1024, 32 }, - { "RadarObject", 512, 32 }, - { "ResourceGatheringManager", 16, 16 }, - { "SightingInfo", 8192, 2048 },// Looks big, but all 3000 objects used to have 4 just built in. - { "SpecialPowerTemplate", 84, 32 }, - { "StateMachinePool", 32, 32 }, - { "TeamPool", 128, 32 }, // if you increase this, increase player/team relation map pools - { "PlayerRelationMapPool", 128, 32 }, - { "TeamRelationMapPool", 128, 32 }, - { "TeamPrototypePool", 256, 32 }, - { "TerrainType", 256, 32 }, - { "ThingTemplatePool", 2120, 32 }, - { "TunnelTracker", 16, 16 }, - { "Upgrade", 16, 16 }, - { "UpgradeTemplate", 128, 16 }, - { "Anim2D", 32, 32 }, - { "CommandButton", 1024, 256 }, - { "CommandSet", 820, 16 }, - { "DisplayString", 32, 32 }, - { "WebBrowserURL", 16, 16 }, - { "Drawable", 4096, 32 }, - { "Image", 2048, 32 }, - { "ParticlePool", 1400, 1024 }, - { "ParticleSystemTemplatePool", 1100, 32 }, - { "ParticleSystemPool", 1024, 32 }, - { "TerrainRoadType", 100, 32, }, - { "WindowLayoutPool", 32, 32 }, - { "AnimatedParticleSysBoneClientUpdate", 16, 16 }, - { "SwayClientUpdate", 32, 32 }, - { "BeaconClientUpdate", 64, 32 }, - { "AIGroupPool", 64, 32 }, - { "AIDockMachinePool", 256, 32 }, - { "AIGuardMachinePool", 32, 32 }, - { "AIGuardRetaliateMachinePool", 32, 32 }, - { "AITNGuardMachinePool", 32, 32 }, - { "PathNodePool", 8192, 1024 }, - { "PathPool", 256, 16 }, - { "WorkOrder", 32, 32 }, - { "TeamInQueue", 32, 32 }, - { "AIPlayer", 12, 4 }, - { "AISkirmishPlayer", 8, 8 }, - { "AIStateMachine", 600, 32 }, - { "JetAIStateMachine", 64, 32 }, - { "HeliAIStateMachine", 64, 32 }, - { "VtolAIStateMachine", 64, 32 }, - { "AIAttackMoveStateMachine", 2048, 32 }, - { "AIAttackThenIdleStateMachine", 512, 32 }, - { "AttackStateMachine", 512, 32 }, - { "CrateTemplate", 32, 32 }, - { "ExperienceTrackerPool", 2048, 512 }, - { "FiringTrackerPool", 4096, 256 }, - { "ObjectRepulsorHelper", 1024, 256 }, - { "ObjectSMCHelperPool", 2048, 256 }, - { "ObjectWeaponStatusHelperPool", 4096, 256 }, - { "ObjectDefectionHelperPool", 2048, 256 }, - { "StatusDamageHelper", 1500, 256 }, - { "SubdualDamageHelper", 1500, 256 }, - { "TempWeaponBonusHelper", 4096, 256 }, - { "Locomotor", 2048, 32 }, - { "LocomotorTemplate", 192, 32 }, - { "ObjectPool", 1500, 256 }, - { "SimpleObjectIteratorPool", 32, 32 }, - { "SimpleObjectIteratorClumpPool", 4096, 32 }, - { "PartitionDataPool", 2048, 512 }, - { "BuildEntry", 32, 32 }, - { "Weapon", 4096, 32 }, - { "WeaponTemplate", 360, 32 }, - { "AIUpdateInterface", 600, 32 }, - { "ActiveBody", 1024, 32 }, - { "ActiveShroudUpgrade", 32, 32 }, - { "AssistedTargetingUpdate", 32, 32 }, - { "AudioEventInfo", 4096, 64 }, - { "AudioRequest", 256, 8 }, - { "AutoHealBehavior", 1024, 256 }, - { "WeaponBonusUpdate", 16, 16 }, - { "GrantStealthBehavior", 4096, 32 }, - { "NeutronBlastBehavior", 4096, 32 }, - { "CountermeasuresBehavior", 256, 32 }, - { "BaseRegenerateUpdate", 128, 32 }, - { "BoneFXDamage", 64, 32 }, - { "BoneFXUpdate", 64, 32 }, - { "BridgeBehavior", 4, 4 }, - { "BridgeTowerBehavior", 32, 32 }, - { "BridgeScaffoldBehavior", 32, 32 }, - { "CaveContain", 16, 16 }, - { "HealContain", 32, 32 }, - { "CreateCrateDie", 256, 128 }, - { "CreateObjectDie", 1024, 32 }, - { "EjectPilotDie", 1024, 32 }, - { "CrushDie", 1024, 32 }, - { "DamDie", 8, 8 }, - { "DeliverPayloadStateMachine", 32, 32 }, - { "DeliverPayloadAIUpdate", 32, 32 }, - { "DeletionUpdate", 128, 32 }, - { "SmartBombTargetHomingUpdate", 8, 8 }, - { "DynamicAudioEventInfo", 16, 256 }, // Note: some levels have none, some have lots. Since all are allocated at level load time, we can set this low for the levels with none. - { "HackInternetStateMachine", 32, 32 }, - { "HackInternetAIUpdate", 32, 32 }, - { "MissileAIUpdate", 512, 32 }, - { "DumbProjectileBehavior", 64, 32 }, - { "FreeFallProjectileBehavior", 32, 32 }, - { "DestroyDie", 1024, 32 }, - { "UpgradeDie", 128, 32 }, - { "KeepObjectDie", 128, 32 }, - { "DozerAIUpdate", 32, 32 }, - { "DynamicGeometryInfoUpdate", 16, 16 }, - { "DynamicShroudClearingRangeUpdate", 128, 16 }, - { "FXListDie", 1024, 32 }, - { "FireSpreadUpdate", 2048, 128 }, - { "FirestormDynamicGeometryInfoUpdate", 16, 16 }, - { "FireWeaponCollide", 2048, 32 }, - { "FireWeaponUpdate", 32, 32 }, - { "FlammableUpdate", 512, 256 }, - { "FloatUpdate", 512, 128 }, - { "TensileFormationUpdate", 256, 32 }, - { "GarrisonContain", 256, 32 }, - { "HealCrateCollide", 32, 32 }, - { "HeightDieUpdate", 32, 32 }, - { "ScatterShotUpdate", 128, 64 }, - { "FireWeaponWhenDamagedBehavior", 32, 32 }, - { "FireWeaponWhenDeadBehavior", 128, 64 }, - { "GenerateMinefieldBehavior", 32, 32 }, - { "HelicopterSlowDeathBehavior", 64, 32 }, - { "ParkingPlaceBehavior", 32, 32 }, - { "FlightDeckBehavior", 8, 8 }, -#ifdef ALLOW_SURRENDER - { "POWTruckAIUpdate", 32, 32, }, - { "POWTruckBehavior", 32, 32, }, - { "PrisonBehavior", 32, 32 }, - { "PrisonVisual", 32, 32 }, - { "PropagandaCenterBehavior", 16, 16 }, -#endif - { "PropagandaTowerBehavior", 16, 16 }, - { "BunkerBusterBehavior", 16, 16 }, - { "ObjectTracker", 128, 32 }, - { "OCLUpdate", 16, 16 }, - { "BodyParticleSystem", 196, 64 }, - { "HighlanderBody", 2048, 128 }, - { "UndeadBody", 32, 32 }, - { "HordeUpdate", 128, 32 }, - { "ImmortalBody", 128, 256 }, - { "InactiveBody", 2048, 32 }, - { "InstantDeathBehavior", 512, 32 }, - { "LaserUpdate", 32, 32 }, - { "PointDefenseLaserUpdate", 32, 32 }, - { "CleanupHazardUpdate", 32, 32 }, - { "AutoFindHealingUpdate", 256, 32 }, - { "CommandButtonHuntUpdate", 512, 8 }, - { "PilotFindVehicleUpdate", 256, 32 }, - { "DemoTrapUpdate", 32, 32 }, - { "ParticleUplinkCannonUpdate", 16, 16 }, - { "SpectreGunshipUpdate", 8, 8 }, - { "SpectreGunshipDeploymentUpdate", 8, 8 }, - { "BaikonurLaunchPower", 4, 4 }, - { "RadiusDecalUpdate", 16, 16 }, - { "RadiusDecalBehavior", 32, 32 }, - { "BattlePlanUpdate", 32, 32 }, - { "LifetimeUpdate", 32, 32 }, - { "LocomotorSetUpgrade", 512, 128 }, - { "LockWeaponCreate", 64, 128 }, - { "AutoDepositUpdate", 256, 32 }, - { "NeutronMissileUpdate", 512, 32 }, - { "MoneyCrateCollide", 48, 16 }, - { "NeutronMissileSlowDeathBehavior", 8, 8 }, - { "OpenContain", 128, 32 }, - { "OverchargeBehavior", 32, 32 }, - { "OverlordContain", 32, 32 }, - { "HelixContain", 32, 32 }, - { "ParachuteContain", 128, 32 }, - { "PhysicsBehavior", 600, 32 }, - { "PoisonedBehavior", 512, 64 }, - { "ProductionEntry", 32, 32 }, - { "ProductionUpdate", 256, 32 }, - { "ProjectileStreamUpdate", 32, 32 }, - { "ProneUpdate", 128, 32 }, - { "QueueProductionExitUpdate", 32, 32 }, - { "RadarUpdate", 16, 16 }, - { "RadarUpgrade", 16, 16 }, - { "AnimationSteeringUpdate", 1024, 32 }, - { "SupplyWarehouseCripplingBehavior", 16, 16 }, - { "CostModifierUpgrade", 32, 32 }, - { "ProductionTimeModifierUpgrade", 32, 32 }, - { "UnitProductionBonusUpgrade", 64, 32 }, - { "CashBountyPower", 32, 32 }, - { "CleanupAreaPower", 32, 32 }, - { "ObjectCreationUpgrade", 196, 32 }, - { "MinefieldBehavior", 256, 32 }, - { "JetSlowDeathBehavior", 64, 32 }, - { "BattleBusSlowDeathBehavior", 64, 32 }, - { "RebuildHoleBehavior", 64, 32 }, - { "RebuildHoleExposeDie", 64, 32 }, - { "RepairDockUpdate", 32, 32 }, -#ifdef ALLOW_SURRENDER - { "PrisonDockUpdate", 32, 32 }, -#endif - { "RailedTransportDockUpdate", 16, 16 }, - { "RailedTransportAIUpdate", 16, 16 }, - { "RailedTransportContain", 16, 16 }, - { "RailroadBehavior", 16, 16 }, - { "SalvageCrateCollide", 32, 32 }, - { "ShroudCrateCollide", 32, 32 }, - { "SlavedUpdate", 64, 32 }, - { "SlowDeathBehavior", 1400, 256 }, - { "SpyVisionUpdate", 16, 16 }, - { "DefaultProductionExitUpdate", 32, 32 }, - { "SpawnPointProductionExitUpdate", 32, 32 }, - { "SpawnBehavior", 32, 32 }, - { "SpecialPowerCompletionDie", 32, 32 }, - { "SpecialPowerCreate", 32, 32 }, - { "PreorderCreate", 32, 32 }, - { "SpecialAbility", 512, 32 }, - { "SpecialAbilityUpdate", 512, 32 }, - { "MissileLauncherBuildingUpdate", 32, 32 }, - { "SquishCollide", 512, 32 }, - { "StructureBody", 512, 64 }, - { "HiveStructureBody", 64, 32 }, //Stinger sites - { "StructureCollapseUpdate", 32, 32 }, - { "StructureToppleUpdate", 32, 32 }, - { "SupplyCenterCreate", 32, 32 }, - { "SupplyCenterDockUpdate", 32, 32 }, - { "SupplyCenterProductionExitUpdate", 32, 32 }, - { "SupplyTruckStateMachine", 256, 32 }, - { "SupplyTruckAIUpdate", 32, 32 }, - { "SupplyWarehouseCreate", 48, 16 }, - { "SupplyWarehouseDockUpdate", 48, 16 }, - { "EnemyNearUpdate", 1024, 32 }, - { "TechBuildingBehavior", 32, 32 }, - { "ToppleUpdate", 256, 128 }, - { "TransitionDamageFX", 384, 128 }, - { "TransportAIUpdate", 64, 32 }, - { "TransportContain", 128, 32 }, - { "RiderChangeContain", 128, 32 }, - { "InternetHackContain", 16, 16 }, - { "TunnelContain", 8, 8 }, - { "TunnelContainDie", 32, 32 }, - { "TunnelCreate", 32, 32 }, - { "TurretAI", 256, 32 }, - { "TurretStateMachine", 128, 32 }, - { "TurretSwapUpgrade", 512, 128 }, - { "UnitCrateCollide", 32, 32 }, - { "UnpauseSpecialPowerUpgrade", 32, 32 }, - { "VeterancyCrateCollide", 32, 32 }, - { "VeterancyGainCreate", 512, 128 }, - { "ConvertToCarBombCrateCollide", 256, 128 }, - { "ConvertToHijackedVehicleCrateCollide", 256, 128 }, - { "SabotageCommandCenterCrateCollide", 256, 128 }, - { "SabotageFakeBuildingCrateCollide", 256, 128 }, - { "SabotageInternetCenterCrateCollide", 256, 128 }, - { "SabotageMilitaryFactoryCrateCollide", 256, 128 }, - { "SabotagePowerPlantCrateCollide", 256, 128 }, - { "SabotageSuperweaponCrateCollide", 256, 128 }, - { "SabotageSupplyCenterCrateCollide", 256, 128 }, - { "SabotageSupplyDropzoneCrateCollide", 256, 128 }, - { "JetAIUpdate", 64, 32 }, - { "ChinookAIUpdate", 32, 32 }, - { "WanderAIUpdate", 32, 32 }, - { "TeleporterAIUpdate", 64, 32 }, - { "WaveGuideUpdate", 16, 16 }, - { "ArmorDamageScalarUpdate", 256, 32 }, - { "WeaponBonusUpgrade", 512, 128 }, - { "WeaponSetUpgrade", 512, 128 }, - { "ArmorUpgrade", 512, 128 }, - { "WorkerAIUpdate", 128, 128 }, - { "WorkerStateMachine", 128, 128 }, - { "ChinookAIStateMachine", 32, 32 }, - { "DeployStyleAIUpdate", 32, 32 }, - { "AssaultTransportAIUpdate", 64, 32 }, - { "StreamingArchiveFile", 8, 8 }, - - { "DozerActionStateMachine", 256, 32 }, - { "DozerPrimaryStateMachine", 256, 32 }, - { "W3DDisplayString", 1400, 128 }, - { "W3DDefaultDraw", 1024, 128 }, - { "W3DDebrisDraw", 128, 128 }, - { "W3DDependencyModelDraw", 64, 64 }, - { "W3DLaserDraw", 32, 32 }, - { "W3DModelDraw", 2048, 512 }, - { "W3DOverlordTankDraw", 64, 64 }, - { "W3DOverlordTruckDraw", 64, 64 }, - { "W3DOverlordAircraftDraw", 64, 64 }, - { "W3DPoliceCarDraw", 32, 32 }, - { "W3DProjectileStreamDraw", 32, 32 }, - { "W3DRopeDraw", 32, 32 }, - { "W3DScienceModelDraw", 32, 32 }, - { "W3DSupplyDraw", 40, 16 }, - { "W3DTankDraw", 256, 32 }, - { "W3DTreeDraw", 16, 16 }, - { "W3DPropDraw", 16, 16 }, - { "W3DTracerDraw", 64, 32 }, - { "W3DTruckDraw", 128, 32 }, - { "W3DTankTruckDraw", 32, 16 }, - { "W3DTreeTextureClass", 4, 4 }, - { "DefaultSpecialPower", 32, 32 }, - { "OCLSpecialPower", 96, 32 }, - { "FireWeaponPower", 32, 32 }, -#ifdef ALLOW_DEMORALIZE - { "DemoralizeSpecialPower", 16, 16, }, -#endif - { "CashHackSpecialPower", 32, 32 }, - { "CommandSetUpgrade", 32, 32 }, - { "PassengersFireUpgrade", 32, 32 }, - { "GrantUpgradeCreate", 256, 32 }, - { "GrantScienceUpgrade", 256, 32 }, - { "ReplaceObjectUpgrade", 32, 32 }, - { "ModelConditionUpgrade", 32, 32 }, - { "SpyVisionSpecialPower", 256, 32 }, - { "StealthDetectorUpdate", 256, 32 }, - { "StealthUpdate", 512, 128 }, - { "StealthUpgrade", 256, 32 }, - { "StatusBitsUpgrade", 128, 128 }, - { "SubObjectsUpgrade", 128, 128 }, - { "ExperienceScalarUpgrade", 256, 128 }, - { "MaxHealthUpgrade", 128, 128 }, - { "WeaponBonusUpgrade", 128, 64 }, - { "StickyBombUpdate", 64, 32 }, - { "FireOCLAfterWeaponCooldownUpdate", 64, 32 }, - { "HijackerUpdate", 64, 32 }, - { "ChinaMinesUpgrade", 64, 32 }, - { "PowerPlantUpdate", 48, 16 }, - { "PowerPlantUpgrade", 48, 16 }, - { "DefectorSpecialPower", 16, 16 }, - { "CheckpointUpdate", 16, 16 }, - { "MobNexusContain", 128, 32 }, - { "MobMemberSlavedUpdate", 64, 32 }, - { "EMPUpdate", 64, 32 }, - { "LeafletDropBehavior", 64, 32 }, - { "Overridable", 32, 32 }, - - { "W3DGameWindow", 700, 256 }, - { "SuccessState", 32, 32 }, - { "FailureState", 32, 32 }, - { "ContinueState", 32, 32 }, - { "SleepState", 32, 32 }, - - { "AIDockWaitForClearanceState", 256, 32 }, - { "AIDockProcessDockState", 256, 32 }, - { "AIGuardInnerState", 32, 32 }, - { "AIGuardIdleState", 32, 32 }, - { "AIGuardOuterState", 32, 32 }, - { "AIGuardReturnState", 32, 32 }, - { "AIGuardPickUpCrateState", 32, 32 }, - { "AIGuardAttackAggressorState", 32, 32 }, - { "AIGuardRetaliateInnerState", 32, 32 }, - { "AIGuardRetaliateIdleState", 32, 32 }, - { "AIGuardRetaliateOuterState", 32, 32 }, - { "AIGuardRetaliateReturnState", 32, 32 }, - { "AIGuardRetaliatePickUpCrateState", 32, 32 }, - { "AIGuardRetaliateAttackAggressorState", 32, 32 }, - { "AITNGuardInnerState", 32, 32 }, - { "AITNGuardIdleState", 32, 32 }, - { "AITNGuardOuterState", 32, 32 }, - { "AITNGuardReturnState", 32, 32 }, - { "AITNGuardPickUpCrateState", 32, 32 }, - { "AITNGuardAttackAggressorState", 32, 32 }, - { "AIIdleState", 2400, 32 }, - { "AIRappelState", 600, 32 }, - { "AIBusyState", 600, 32 }, - { "AIWaitState", 600, 32 }, - { "AIAttackState", 4096, 32 }, - { "AIAttackSquadState", 600, 32 }, - { "AIDeadState", 600, 32 }, - { "AIDockState", 600, 32 }, - { "AIExitState", 600, 32 }, - { "AIExitInstantlyState", 600, 32 }, - { "AIGuardState", 600, 32 }, - { "AIGuardRetaliateState", 600, 32 }, - { "AITunnelNetworkGuardState", 600, 32 }, - { "AIHuntState", 600, 32 }, - { "AIAttackAreaState", 600, 32 }, - { "AIFaceState", 1200, 32 }, - { "ApproachState", 600, 32 }, - { "DeliveringState", 600, 32 }, - { "ConsiderNewApproachState", 600, 32 }, - { "RecoverFromOffMapState", 600, 32 }, - { "HeadOffMapState", 600, 32 }, - { "CleanUpState", 600, 32 }, - { "HackInternetState", 600, 32 }, - { "PackingState", 600, 32 }, - { "UnpackingState", 600, 32 }, - { "SupplyTruckWantsToPickUpOrDeliverBoxesState", 600, 32 }, - { "RegroupingState", 600, 32 }, - { "DockingState", 600, 32 }, - { "ChinookEvacuateState", 32, 32 }, - { "ChinookHeadOffMapState", 32, 32 }, - { "ChinookTakeoffOrLandingState", 32, 32 }, - { "ChinookCombatDropState", 32, 32 }, - { "DozerActionPickActionPosState", 256, 32 }, - { "DozerActionMoveToActionPosState", 256, 32 }, - { "DozerActionDoActionState", 256, 32 }, - { "DozerPrimaryIdleState", 256, 32 }, - { "DozerActionState", 256, 32 }, - { "DozerPrimaryGoingHomeState", 256, 32 }, - { "JetAwaitingRunwayState", 64, 32 }, - { "JetOrHeliCirclingDeadAirfieldState", 64, 32 }, - { "HeliTakeoffOrLandingState", 64, 32 }, - { "VtolTakeoffOrLandingState", 64, 32 }, - { "JetOrHeliParkOrientState", 64, 32 }, - { "VtolParkOrientState", 64, 32 }, - { "JetOrHeliReloadAmmoState", 64, 32 }, - { "SupplyTruckBusyState", 600, 32 }, - { "SupplyTruckIdleState", 600, 32 }, - { "ActAsDozerState", 600, 32 }, - { "ActAsSupplyTruckState", 600, 32 }, - { "AIDockApproachState", 256, 32 }, - { "AIDockAdvancePositionState", 256, 32 }, - { "AIDockMoveToEntryState", 256, 32 }, - { "AIDockMoveToDockState", 256, 32 }, - { "AIDockMoveToExitState", 256, 32 }, - { "AIDockMoveToRallyState", 256, 32 }, - { "AIMoveToState", 600, 32 }, - { "AIMoveOutOfTheWayState", 600, 32 }, - { "AIMoveAndTightenState", 600, 32 }, - { "AIMoveAwayFromRepulsorsState", 600, 32 }, - { "AIAttackApproachTargetState", 96, 32 }, - { "AIAttackPursueTargetState", 96, 32 }, - { "AIAttackAimAtTargetState", 96, 32 }, - { "AIAttackFireWeaponState", 256, 32 }, - { "AIPickUpCrateState", 4096, 32 }, - { "AIFollowWaypointPathState", 1200, 32 }, - { "AIFollowWaypointPathExactState", 1200, 32 }, - { "AIWanderInPlaceState", 600, 32 }, - { "AIFollowPathState", 1200, 32 }, - { "AIMoveAndEvacuateState", 1200, 32 }, - { "AIMoveAndDeleteState", 600, 32 }, - { "AIEnterState", 600, 32 }, - { "JetOrHeliReturningToDeadAirfieldState", 64, 32 }, - { "JetOrHeliReturnForLandingState", 64, 32 }, - { "TurretAIIdleState", 600, 32 }, - { "TurretAIIdleScanState", 600, 32 }, - { "TurretAIAimTurretState", 600, 32 }, - { "TurretAIRecenterTurretState", 600, 32 }, - { "TurretAIHoldTurretState", 600, 32 }, - { "JetOrHeliTaxiState", 64, 32 }, - { "JetTakeoffOrLandingState", 64, 32 }, - { "JetPauseBeforeTakeoffState", 64, 32 }, - { "AIAttackMoveToState", 600, 32 }, - { "AIAttackFollowWaypointPathState", 1200, 32 }, - { "AIWanderState", 600, 32 }, - { "AIPanicState", 600, 32 }, - { "ChinookMoveToBldgState", 32, 32 }, - { "ChinookRecordCreationState", 32, 32 }, - { "ScienceInfo", 96, 32 }, - { "RankInfo", 32, 32 }, - - { "FireWeaponNugget", 32, 32 }, - { "AttackNugget", 32, 32 }, - { "DeliverPayloadNugget", 48, 32 }, - { "ApplyRandomForceNugget", 32, 32 }, - { "GenericObjectCreationNugget", 632, 32 }, - { "SoundFXNugget", 320, 32 }, - { "TracerFXNugget", 32, 32 }, - { "RayEffectFXNugget", 32, 32 }, - { "LightPulseFXNugget", 68, 32 }, - { "ViewShakeFXNugget", 140, 32 }, - { "TerrainScorchFXNugget", 48, 32 }, - { "ParticleSystemFXNugget", 832, 32 }, - { "FXListAtBonePosFXNugget", 32, 32 }, - { "Squad", 256, 32 }, - { "BuildListInfo", 400, 64 }, - - { "ScriptGroup", 128, 32 }, - { "OrCondition", 1024, 256 }, - { "ScriptAction", 2600, 512 }, - { "Script", 1024, 256 }, - { "Parameter", 8192, 1024 }, - { "Condition", 2048, 256 }, - { "Template", 32, 32 }, - { "ScriptList", 32, 32 }, - { "AttackPriorityInfo", 32, 32 }, - { "SequentialScript", 32, 32 }, - { "Win32LocalFile", 1024, 256 }, - { "StdLocalFile", 1024, 256 }, - { "RAMFile", 32, 32 }, - { "BattlePlanBonuses", 32, 32 }, - { "KindOfPercentProductionChange", 32, 32 }, - { "UserParser", 4096, 256 }, - { "XferBlockData", 32, 32 }, - { "EvaCheckInfo", 52, 16 }, - { "SuperweaponInfo", 32, 32 }, - { "NamedTimerInfo", 32, 32 }, - { "PopupMessageData", 32, 32 }, - { "FloatingTextData", 32, 32 }, - { "MapObject", 5000, 1024 }, - { "Waypoint", 1024, 32 }, - { "PolygonTrigger", 64, 64 }, - { "Bridge", 32, 32 }, - { "Mapping", 384, 64 }, - { "OutputChunk", 32, 32 }, - { "InputChunk", 32, 32 }, - { "AnimateWindow", 32, 32 }, - { "GameFont", 32, 32 }, - { "NetCommandRef", 256, 32 }, - { "GameMessageArgument", 1024, 256 }, - { "GameMessageParserArgumentType", 32, 32 }, - { "GameMessageParser", 32, 32 }, - { "WeaponBonusSet", 96, 32 }, - { "Campaign", 32, 32 }, - { "Mission", 88, 32 }, - { "ModalWindow", 32, 32 }, - { "NetPacket", 32, 32 }, - { "AISideInfo", 32, 32 }, - { "AISideBuildList", 32, 32 }, - { "MetaMapRec", 256, 32 }, - { "TransportStatus", 32, 32 }, - { "Anim2DTemplate", 32, 32 }, - { "ObjectTypes", 32, 32 }, - { "NetCommandList", 512, 32 }, - { "TurretAIData", 256, 32 }, - { "NetCommandMsg", 32, 32 }, - { "NetGameCommandMsg", 64, 32 }, - { "NetAckBothCommandMsg", 32, 32 }, - { "NetAckStage1CommandMsg", 32, 32 }, - { "NetAckStage2CommandMsg", 32, 32 }, - { "NetFrameCommandMsg", 32, 32 }, - { "NetPlayerLeaveCommandMsg", 32, 32 }, - { "NetRunAheadMetricsCommandMsg", 32, 32 }, - { "NetRunAheadCommandMsg", 32, 32 }, - { "NetDestroyPlayerCommandMsg", 32, 32 }, - { "NetDisconnectFrameCommandMsg", 32, 32 }, - { "NetDisconnectScreenOffCommandMsg", 32, 32 }, - { "NetFrameResendRequestCommandMsg", 32, 32 }, - { "NetKeepAliveCommandMsg", 32, 32 }, - { "NetDisconnectKeepAliveCommandMsg", 32, 32 }, - { "NetDisconnectPlayerCommandMsg", 32, 32 }, - { "NetPacketRouterQueryCommandMsg", 32, 32 }, - { "NetPacketRouterAckCommandMsg", 32, 32 }, - { "NetDisconnectChatCommandMsg", 32, 32 }, - { "NetChatCommandMsg", 32, 32 }, - { "NetDisconnectVoteCommandMsg", 32, 32 }, - { "NetProgressCommandMsg", 32, 32 }, - { "NetWrapperCommandMsg", 32, 32 }, - { "NetFileCommandMsg", 32, 32 }, - { "NetFileAnnounceCommandMsg", 32, 32 }, - { "NetFileProgressCommandMsg", 32, 32 }, - { "NetCommandWrapperListNode", 32, 32 }, - { "NetCommandWrapperList", 32, 32 }, - { "Connection", 32, 32 }, - { "User", 32, 32 }, - { "FrameDataManager", 32, 32 }, - { "DrawableIconInfo", 32, 32 }, - { "TintEnvelope", 128, 32 }, - { "DynamicAudioEventRTS", 4000, 256 }, - { "DrawableLocoInfo", 128, 32 }, - { "W3DPrototypeClass", 512, 256 }, - { "EnumeratedIP", 32, 32 }, - { "WaterTransparencySetting", 4, 4 }, - { "WeatherSetting", 4, 4 }, - - // W3D pools! - { "BoxPrototypeClass", 128, 128 }, - { "SpherePrototypeClass", 32, 32 }, - { "SoundRenderObjPrototypeClass", 32, 32 }, - { "RingPrototypeClass", 32, 32 }, - { "PrimitivePrototypeClass", 8192, 32 }, - { "HModelPrototypeClass", 256, 32 }, - { "ParticleEmitterPrototypeClass", 32, 32 }, - { "NullPrototypeClass", 32, 32 }, - { "HLodPrototypeClass", 700, 128 }, - { "HLodDefClass", 700, 128 }, - { "DistLODPrototypeClass", 32, 32 }, - { "DazzlePrototypeClass", 32, 32 }, - { "CollectionPrototypeClass", 32, 32 }, - { "BoxPrototypeClass", 256, 32 }, - { "AggregatePrototypeClass", 32, 32 }, - { "OBBoxRenderObjClass", 512, 128 }, - { "AABoxRenderObjClass", 32, 32 }, - { "VertexMaterialClass", 6000, 2048 }, - { "TextureClass", 1200, 256 }, - { "CloudMapTerrainTextureClass", 4, 4 }, - { "ScorchTextureClass", 4, 4 }, - { "LightMapTerrainTextureClass", 4, 4 }, - { "AlphaEdgeTextureClass", 4, 4 }, - { "AlphaTerrainTextureClass", 4, 4 }, - { "TerrainTextureClass", 4, 4 }, - { "MeshClass", 14000, 2000 }, - { "HTreeClass", 2048, 512 }, - { "HLodClass", 2048, 512 }, - { "MeshModelClass", 8192, 32 }, - { "ShareBufferClass", 32768, 1024 }, - { "AABTreeClass", 300, 128 }, - { "MotionChannelClass", 16384, 32 }, - { "BitChannelClass", 84, 32 }, - { "TimeCodedMotionChannelClass", 116, 32 }, - { "AdaptiveDeltaMotionChannelClass", 32, 32 }, - { "TimeCodedBitChannelClass", 32, 32 }, - { "UVBufferClass", 8192, 32 }, - { "TexBufferClass", 384, 128 }, - { "MatBufferClass", 256, 128 }, - { "MatrixMapperClass", 32, 32 }, - { "ScaleTextureMapperClass", 32, 32 }, - { "LinearOffsetTextureMapperClass", 96, 32 }, - { "GridTextureMapperClass", 32, 32 }, - { "RotateTextureMapperClass", 32, 32 }, - { "SineLinearOffsetTextureMapperClass", 32, 32 }, - { "StepLinearOffsetTextureMapperClass", 32, 32 }, - { "ZigZagLinearOffsetTextureMapperClass", 32, 32 }, - { "ClassicEnvironmentMapperClass", 32, 32 }, - { "EnvironmentMapperClass", 256, 32 }, - { "EdgeMapperClass", 32, 32 }, - { "WSClassicEnvironmentMapperClass", 32, 32 }, - { "WSEnvironmentMapperClass", 32, 32 }, - { "GridClassicEnvironmentMapperClass", 32, 32 }, - { "GridEnvironmentMapperClass", 32, 32 }, - { "ScreenMapperClass", 32, 32 }, - { "RandomTextureMapperClass", 32, 32 }, - { "BumpEnvTextureMapperClass", 32, 32 }, - { "MeshLoadContextClass", 4, 4 }, - { "MaterialInfoClass", 8192, 32 }, - { "MeshMatDescClass", 8192, 32 }, - { "TextureLoadTaskClass", 256, 32 }, - { "SortingNodeStruct", 288, 32 }, - { "ProxyArrayClass", 32, 32 }, - { "Line3DClass", 8, 8 }, - { "Render2DClass", 64, 32 }, - { "SurfaceClass", 128, 32 }, - { "FontCharsClassCharDataStruct", 1024, 32 }, - { "FontCharsBuffer", 16, 4 }, - { "FVFInfoClass", 152, 64 }, - { "TerrainTracksRenderObjClass", 128, 32 }, - { "DynamicIBAccessClass", 32, 32 }, - { "DX8IndexBufferClass", 128, 32 }, - { "SortingIndexBufferClass", 32, 32 }, - { "DX8VertexBufferClass", 128, 32 }, - { "SortingVertexBufferClass", 32, 32 }, - { "DynD3DMATERIAL8", 8192, 32 }, - { "DynamicMatrix3D", 512, 32 }, - { "MeshGeometryClass", 32, 32 }, - { "DynamicMeshModel", 32, 32 }, - { "GapFillerClass", 32, 32 }, - { "FontCharsClass", 64, 32 }, - { "ThumbnailManagerClass", 32, 32}, - { "SmudgeSet", 32, 32}, - { "Smudge", 128, 32}, - { 0, 0, 0 } -}; - -//----------------------------------------------------------------------------- -void userMemoryAdjustPoolSize(const char *poolName, Int& initialAllocationCount, Int& overflowAllocationCount) -{ - if (initialAllocationCount > 0) - return; - - for (const PoolSizeRec* p = sizes; p->name != NULL; ++p) - { - if (strcmp(p->name, poolName) == 0) - { - initialAllocationCount = p->initial; - overflowAllocationCount = p->overflow; - return; - } - } - - DEBUG_CRASH(("Initial size for pool %s not found -- you should add it to MemoryInit.cpp\n",poolName)); -} - -//----------------------------------------------------------------------------- -static Int roundUpMemBound(Int i) -{ - const int MEM_BOUND_ALIGNMENT = 4; - - if (i < MEM_BOUND_ALIGNMENT) - return MEM_BOUND_ALIGNMENT; - else - return (i + (MEM_BOUND_ALIGNMENT-1)) & ~(MEM_BOUND_ALIGNMENT-1); -} - -//----------------------------------------------------------------------------- -void userMemoryManagerInitPools() -{ - // note that we MUST use stdio stuff here, and not the normal game file system - // (with bigfile support, etc), because that relies on memory pools, which - // aren't yet initialized properly! so rely ONLY on straight stdio stuff here. - // (not even AsciiString. thanks.) - - // since we're called prior to main, the cur dir might not be what - // we expect. so do it the hard way. - char buf[_MAX_PATH]; - ::GetModuleFileName(NULL, buf, sizeof(buf)); - char* pEnd = buf + strlen(buf); - while (pEnd != buf) - { - if (*pEnd == '\\') - { - *pEnd = 0; - break; - } - --pEnd; - } - strcat(buf, "\\Data\\INI\\MemoryPools.ini"); - - FILE* fp = fopen(buf, "r"); - if (fp) - { - char poolName[256]; - int initial, overflow; - while (fgets(buf, _MAX_PATH, fp)) - { - if (buf[0] == ';') - continue; - if (sscanf(buf, "%s %d %d", poolName, &initial, &overflow ) == 3) - { - for (PoolSizeRec* p = sizes; p->name != NULL; ++p) - { - if (stricmp(p->name, poolName) == 0) - { - // currently, these must be multiples of 4. so round up. - p->initial = roundUpMemBound(initial); - p->overflow = roundUpMemBound(overflow); - break; // from for-p - } - } - } - } - fclose(fp); - } -} - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: MemoryInit.cpp +//----------------------------------------------------------------------------- +// +// Westwood Studios Pacific. +// +// Confidential Information +// Copyright (C) 2001 - All Rights Reserved +// +//----------------------------------------------------------------------------- +// +// Project: RTS3 +// +// File name: MemoryInit.cpp +// +// Created: Steven Johnson, August 2001 +// +// Desc: Memory manager +// +// ---------------------------------------------------------------------------- +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +// SYSTEM INCLUDES + +// USER INCLUDES +#include "Lib/BaseType.h" +#include "Common/GameMemory.h" + +#ifdef RTS_INTERNAL +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +//----------------------------------------------------------------------------- +void userMemoryManagerGetDmaParms(Int *numSubPools, const PoolInitRec **pParms) +{ + static const PoolInitRec defaultDMA[7] = + { + // name, allocsize, initialcount, overflowcount + { "dmaPool_16", 16, 130000, 10000 }, + { "dmaPool_32", 32, 250000, 10000 }, + { "dmaPool_64", 64, 100000, 10000 }, + { "dmaPool_128", 128, 80000, 10000 }, + { "dmaPool_256", 256, 20000, 5000 }, + { "dmaPool_512", 512, 16000, 5000 }, + { "dmaPool_1024", 1024, 6000, 1024} + }; + + *numSubPools = 7; + *pParms = defaultDMA; +} + +//----------------------------------------------------------------------------- +struct PoolSizeRec +{ + const char* name; + Int initial; + Int overflow; +}; + +//----------------------------------------------------------------------------- +// And please be careful of duplicates. They are not rejected. +// not const -- we might override from INI +static PoolSizeRec sizes[] = +{ + { "PartitionContactListNode", 2048, 512 }, + { "BattleshipUpdate", 32, 32 }, + { "FlyToDestAndDestroyUpdate", 32, 32 }, + { "MusicTrack", 32, 32 }, + { "PositionalSoundPool", 32, 32 }, + { "GameMessage", 2048, 32 }, + { "NameKeyBucketPool", 9000, 1024 }, + { "ObjectSellInfo", 16, 16 }, + { "ProductionPrerequisitePool", 1024, 32 }, + { "RadarObject", 512, 32 }, + { "ResourceGatheringManager", 16, 16 }, + { "SightingInfo", 8192, 2048 },// Looks big, but all 3000 objects used to have 4 just built in. + { "SpecialPowerTemplate", 84, 32 }, + { "StateMachinePool", 32, 32 }, + { "TeamPool", 128, 32 }, // if you increase this, increase player/team relation map pools + { "PlayerRelationMapPool", 128, 32 }, + { "TeamRelationMapPool", 128, 32 }, + { "TeamPrototypePool", 256, 32 }, + { "TerrainType", 256, 32 }, + { "ThingTemplatePool", 2120, 32 }, + { "TunnelTracker", 16, 16 }, + { "Upgrade", 16, 16 }, + { "UpgradeTemplate", 128, 16 }, + { "Anim2D", 32, 32 }, + { "CommandButton", 1024, 256 }, + { "CommandSet", 820, 16 }, + { "DisplayString", 32, 32 }, + { "WebBrowserURL", 16, 16 }, + { "Drawable", 4096, 32 }, + { "Image", 2048, 32 }, + { "ParticlePool", 1400, 1024 }, + { "ParticleSystemTemplatePool", 1100, 32 }, + { "ParticleSystemPool", 1024, 32 }, + { "TerrainRoadType", 100, 32, }, + { "WindowLayoutPool", 32, 32 }, + { "AnimatedParticleSysBoneClientUpdate", 16, 16 }, + { "SwayClientUpdate", 32, 32 }, + { "BeaconClientUpdate", 64, 32 }, + { "AIGroupPool", 64, 32 }, + { "AIDockMachinePool", 256, 32 }, + { "AIGuardMachinePool", 32, 32 }, + { "AIGuardRetaliateMachinePool", 32, 32 }, + { "AITNGuardMachinePool", 32, 32 }, + { "PathNodePool", 8192, 1024 }, + { "PathPool", 256, 16 }, + { "WorkOrder", 32, 32 }, + { "TeamInQueue", 32, 32 }, + { "AIPlayer", 12, 4 }, + { "AISkirmishPlayer", 8, 8 }, + { "AIStateMachine", 600, 32 }, + { "JetAIStateMachine", 64, 32 }, + { "HeliAIStateMachine", 64, 32 }, + { "VtolAIStateMachine", 64, 32 }, + { "AIAttackMoveStateMachine", 2048, 32 }, + { "AIAttackThenIdleStateMachine", 512, 32 }, + { "AttackStateMachine", 512, 32 }, + { "CrateTemplate", 32, 32 }, + { "ExperienceTrackerPool", 2048, 512 }, + { "FiringTrackerPool", 4096, 256 }, + { "ObjectRepulsorHelper", 1024, 256 }, + { "ObjectSMCHelperPool", 2048, 256 }, + { "ObjectWeaponStatusHelperPool", 4096, 256 }, + { "ObjectDefectionHelperPool", 2048, 256 }, + { "StatusDamageHelper", 1500, 256 }, + { "SubdualDamageHelper", 1500, 256 }, + { "TempWeaponBonusHelper", 4096, 256 }, + { "Locomotor", 2048, 32 }, + { "LocomotorTemplate", 192, 32 }, + { "ObjectPool", 1500, 256 }, + { "SimpleObjectIteratorPool", 32, 32 }, + { "SimpleObjectIteratorClumpPool", 4096, 32 }, + { "PartitionDataPool", 2048, 512 }, + { "BuildEntry", 32, 32 }, + { "Weapon", 4096, 32 }, + { "WeaponTemplate", 360, 32 }, + { "AIUpdateInterface", 600, 32 }, + { "ActiveBody", 1024, 32 }, + { "ActiveShroudUpgrade", 32, 32 }, + { "AssistedTargetingUpdate", 32, 32 }, + { "AudioEventInfo", 4096, 64 }, + { "AudioRequest", 256, 8 }, + { "AutoHealBehavior", 1024, 256 }, + { "WeaponBonusUpdate", 16, 16 }, + { "GrantStealthBehavior", 4096, 32 }, + { "NeutronBlastBehavior", 4096, 32 }, + { "CountermeasuresBehavior", 256, 32 }, + { "BaseRegenerateUpdate", 128, 32 }, + { "BoneFXDamage", 64, 32 }, + { "BoneFXUpdate", 64, 32 }, + { "BridgeBehavior", 4, 4 }, + { "BridgeTowerBehavior", 32, 32 }, + { "BridgeScaffoldBehavior", 32, 32 }, + { "CaveContain", 16, 16 }, + { "HealContain", 32, 32 }, + { "CreateCrateDie", 256, 128 }, + { "CreateObjectDie", 1024, 32 }, + { "EjectPilotDie", 1024, 32 }, + { "CrushDie", 1024, 32 }, + { "DamDie", 8, 8 }, + { "DeliverPayloadStateMachine", 32, 32 }, + { "DeliverPayloadAIUpdate", 32, 32 }, + { "DeletionUpdate", 128, 32 }, + { "SmartBombTargetHomingUpdate", 8, 8 }, + { "DynamicAudioEventInfo", 16, 256 }, // Note: some levels have none, some have lots. Since all are allocated at level load time, we can set this low for the levels with none. + { "HackInternetStateMachine", 32, 32 }, + { "HackInternetAIUpdate", 32, 32 }, + { "MissileAIUpdate", 512, 32 }, + { "DumbProjectileBehavior", 64, 32 }, + { "FreeFallProjectileBehavior", 32, 32 }, + { "DestroyDie", 1024, 32 }, + { "UpgradeDie", 128, 32 }, + { "KeepObjectDie", 128, 32 }, + { "DozerAIUpdate", 32, 32 }, + { "DynamicGeometryInfoUpdate", 16, 16 }, + { "DynamicShroudClearingRangeUpdate", 128, 16 }, + { "FXListDie", 1024, 32 }, + { "FireSpreadUpdate", 2048, 128 }, + { "FirestormDynamicGeometryInfoUpdate", 16, 16 }, + { "FireWeaponCollide", 2048, 32 }, + { "FireWeaponUpdate", 32, 32 }, + { "FlammableUpdate", 512, 256 }, + { "FloatUpdate", 512, 128 }, + { "TensileFormationUpdate", 256, 32 }, + { "GarrisonContain", 256, 32 }, + { "HealCrateCollide", 32, 32 }, + { "HeightDieUpdate", 32, 32 }, + { "ScatterShotUpdate", 128, 64 }, + { "FireWeaponWhenDamagedBehavior", 32, 32 }, + { "FireWeaponWhenDeadBehavior", 128, 64 }, + { "DelayedUpgradeBehavior", 128, 64 }, + { "GenerateMinefieldBehavior", 32, 32 }, + { "HelicopterSlowDeathBehavior", 64, 32 }, + { "ParkingPlaceBehavior", 32, 32 }, + { "FlightDeckBehavior", 8, 8 }, +#ifdef ALLOW_SURRENDER + { "POWTruckAIUpdate", 32, 32, }, + { "POWTruckBehavior", 32, 32, }, + { "PrisonBehavior", 32, 32 }, + { "PrisonVisual", 32, 32 }, + { "PropagandaCenterBehavior", 16, 16 }, +#endif + { "PropagandaTowerBehavior", 16, 16 }, + { "BunkerBusterBehavior", 16, 16 }, + { "ObjectTracker", 128, 32 }, + { "OCLUpdate", 16, 16 }, + { "BodyParticleSystem", 196, 64 }, + { "HighlanderBody", 2048, 128 }, + { "UndeadBody", 32, 32 }, + { "HordeUpdate", 128, 32 }, + { "ImmortalBody", 128, 256 }, + { "InactiveBody", 2048, 32 }, + { "InstantDeathBehavior", 512, 32 }, + { "LaserUpdate", 32, 32 }, + { "PointDefenseLaserUpdate", 32, 32 }, + { "CleanupHazardUpdate", 32, 32 }, + { "AutoFindHealingUpdate", 256, 32 }, + { "CommandButtonHuntUpdate", 512, 8 }, + { "PilotFindVehicleUpdate", 256, 32 }, + { "DemoTrapUpdate", 32, 32 }, + { "ParticleUplinkCannonUpdate", 16, 16 }, + { "SpectreGunshipUpdate", 8, 8 }, + { "SpectreGunshipDeploymentUpdate", 8, 8 }, + { "BaikonurLaunchPower", 4, 4 }, + { "RadiusDecalUpdate", 16, 16 }, + { "RadiusDecalBehavior", 32, 32 }, + { "BattlePlanUpdate", 32, 32 }, + { "LifetimeUpdate", 32, 32 }, + { "LocomotorSetUpgrade", 512, 128 }, + { "LockWeaponCreate", 64, 128 }, + { "AutoDepositUpdate", 256, 32 }, + { "NeutronMissileUpdate", 512, 32 }, + { "MoneyCrateCollide", 48, 16 }, + { "NeutronMissileSlowDeathBehavior", 8, 8 }, + { "OpenContain", 128, 32 }, + { "OverchargeBehavior", 32, 32 }, + { "OverlordContain", 32, 32 }, + { "HelixContain", 32, 32 }, + { "ParachuteContain", 128, 32 }, + { "PhysicsBehavior", 600, 32 }, + { "PoisonedBehavior", 512, 64 }, + { "ProductionEntry", 32, 32 }, + { "ProductionUpdate", 256, 32 }, + { "ProjectileStreamUpdate", 32, 32 }, + { "ProneUpdate", 128, 32 }, + { "QueueProductionExitUpdate", 32, 32 }, + { "RadarUpdate", 16, 16 }, + { "RadarUpgrade", 16, 16 }, + { "AnimationSteeringUpdate", 1024, 32 }, + { "SupplyWarehouseCripplingBehavior", 16, 16 }, + { "CostModifierUpgrade", 32, 32 }, + { "ProductionTimeModifierUpgrade", 32, 32 }, + { "UnitProductionBonusUpgrade", 64, 32 }, + { "CashBountyPower", 32, 32 }, + { "CleanupAreaPower", 32, 32 }, + { "ObjectCreationUpgrade", 196, 32 }, + { "MinefieldBehavior", 256, 32 }, + { "JetSlowDeathBehavior", 64, 32 }, + { "BattleBusSlowDeathBehavior", 64, 32 }, + { "RebuildHoleBehavior", 64, 32 }, + { "RebuildHoleExposeDie", 64, 32 }, + { "RepairDockUpdate", 32, 32 }, +#ifdef ALLOW_SURRENDER + { "PrisonDockUpdate", 32, 32 }, +#endif + { "RailedTransportDockUpdate", 16, 16 }, + { "RailedTransportAIUpdate", 16, 16 }, + { "RailedTransportContain", 16, 16 }, + { "RailroadBehavior", 16, 16 }, + { "SalvageCrateCollide", 32, 32 }, + { "ShroudCrateCollide", 32, 32 }, + { "SlavedUpdate", 64, 32 }, + { "SlowDeathBehavior", 1400, 256 }, + { "SpyVisionUpdate", 16, 16 }, + { "DefaultProductionExitUpdate", 32, 32 }, + { "SpawnPointProductionExitUpdate", 32, 32 }, + { "SpawnBehavior", 32, 32 }, + { "SpecialPowerCompletionDie", 32, 32 }, + { "SpecialPowerCreate", 32, 32 }, + { "PreorderCreate", 32, 32 }, + { "SpecialAbility", 512, 32 }, + { "SpecialAbilityUpdate", 512, 32 }, + { "MissileLauncherBuildingUpdate", 32, 32 }, + { "SquishCollide", 512, 32 }, + { "StructureBody", 512, 64 }, + { "HiveStructureBody", 64, 32 }, //Stinger sites + { "StructureCollapseUpdate", 32, 32 }, + { "StructureToppleUpdate", 32, 32 }, + { "SupplyCenterCreate", 32, 32 }, + { "SupplyCenterDockUpdate", 32, 32 }, + { "SupplyCenterProductionExitUpdate", 32, 32 }, + { "SupplyTruckStateMachine", 256, 32 }, + { "SupplyTruckAIUpdate", 32, 32 }, + { "SupplyWarehouseCreate", 48, 16 }, + { "SupplyWarehouseDockUpdate", 48, 16 }, + { "EnemyNearUpdate", 1024, 32 }, + { "TechBuildingBehavior", 32, 32 }, + { "ToppleUpdate", 256, 128 }, + { "TransitionDamageFX", 384, 128 }, + { "TransportAIUpdate", 64, 32 }, + { "TransportContain", 128, 32 }, + { "RiderChangeContain", 128, 32 }, + { "InternetHackContain", 16, 16 }, + { "TunnelContain", 8, 8 }, + { "TunnelContainDie", 32, 32 }, + { "TunnelCreate", 32, 32 }, + { "TurretAI", 256, 32 }, + { "TurretStateMachine", 128, 32 }, + { "TurretSwapUpgrade", 512, 128 }, + { "UnitCrateCollide", 32, 32 }, + { "UnpauseSpecialPowerUpgrade", 32, 32 }, + { "VeterancyCrateCollide", 32, 32 }, + { "VeterancyGainCreate", 512, 128 }, + { "ConvertToCarBombCrateCollide", 256, 128 }, + { "ConvertToHijackedVehicleCrateCollide", 256, 128 }, + { "SabotageCommandCenterCrateCollide", 256, 128 }, + { "SabotageFakeBuildingCrateCollide", 256, 128 }, + { "SabotageInternetCenterCrateCollide", 256, 128 }, + { "SabotageMilitaryFactoryCrateCollide", 256, 128 }, + { "SabotagePowerPlantCrateCollide", 256, 128 }, + { "SabotageSuperweaponCrateCollide", 256, 128 }, + { "SabotageSupplyCenterCrateCollide", 256, 128 }, + { "SabotageSupplyDropzoneCrateCollide", 256, 128 }, + { "JetAIUpdate", 64, 32 }, + { "ChinookAIUpdate", 32, 32 }, + { "WanderAIUpdate", 32, 32 }, + { "TeleporterAIUpdate", 64, 32 }, + { "WaveGuideUpdate", 16, 16 }, + { "ArmorDamageScalarUpdate", 256, 32 }, + { "WeaponBonusUpgrade", 512, 128 }, + { "WeaponSetUpgrade", 512, 128 }, + { "ArmorUpgrade", 512, 128 }, + { "WorkerAIUpdate", 128, 128 }, + { "WorkerStateMachine", 128, 128 }, + { "ChinookAIStateMachine", 32, 32 }, + { "DeployStyleAIUpdate", 32, 32 }, + { "AssaultTransportAIUpdate", 64, 32 }, + { "StreamingArchiveFile", 8, 8 }, + + { "DozerActionStateMachine", 256, 32 }, + { "DozerPrimaryStateMachine", 256, 32 }, + { "W3DDisplayString", 1400, 128 }, + { "W3DDefaultDraw", 1024, 128 }, + { "W3DDebrisDraw", 128, 128 }, + { "W3DDependencyModelDraw", 64, 64 }, + { "W3DLaserDraw", 32, 32 }, + { "W3DModelDraw", 2048, 512 }, + { "W3DOverlordTankDraw", 64, 64 }, + { "W3DOverlordTruckDraw", 64, 64 }, + { "W3DOverlordAircraftDraw", 64, 64 }, + { "W3DPoliceCarDraw", 32, 32 }, + { "W3DProjectileStreamDraw", 32, 32 }, + { "W3DRopeDraw", 32, 32 }, + { "W3DScienceModelDraw", 32, 32 }, + { "W3DSupplyDraw", 40, 16 }, + { "W3DTankDraw", 256, 32 }, + { "W3DTreeDraw", 16, 16 }, + { "W3DPropDraw", 16, 16 }, + { "W3DTracerDraw", 64, 32 }, + { "W3DTruckDraw", 128, 32 }, + { "W3DTankTruckDraw", 32, 16 }, + { "W3DTreeTextureClass", 4, 4 }, + { "DefaultSpecialPower", 32, 32 }, + { "OCLSpecialPower", 96, 32 }, + { "FireWeaponPower", 32, 32 }, +#ifdef ALLOW_DEMORALIZE + { "DemoralizeSpecialPower", 16, 16, }, +#endif + { "CashHackSpecialPower", 32, 32 }, + { "CommandSetUpgrade", 32, 32 }, + { "PassengersFireUpgrade", 32, 32 }, + { "GrantUpgradeCreate", 256, 32 }, + { "GrantScienceUpgrade", 256, 32 }, + { "ReplaceObjectUpgrade", 32, 32 }, + { "ModelConditionUpgrade", 32, 32 }, + { "SpyVisionSpecialPower", 256, 32 }, + { "StealthDetectorUpdate", 256, 32 }, + { "StealthUpdate", 512, 128 }, + { "StealthUpgrade", 256, 32 }, + { "StatusBitsUpgrade", 128, 128 }, + { "SubObjectsUpgrade", 128, 128 }, + { "ExperienceScalarUpgrade", 256, 128 }, + { "MaxHealthUpgrade", 128, 128 }, + { "WeaponBonusUpgrade", 128, 64 }, + { "StickyBombUpdate", 64, 32 }, + { "FireOCLAfterWeaponCooldownUpdate", 64, 32 }, + { "HijackerUpdate", 64, 32 }, + { "ChinaMinesUpgrade", 64, 32 }, + { "PowerPlantUpdate", 48, 16 }, + { "PowerPlantUpgrade", 48, 16 }, + { "DefectorSpecialPower", 16, 16 }, + { "CheckpointUpdate", 16, 16 }, + { "MobNexusContain", 128, 32 }, + { "MobMemberSlavedUpdate", 64, 32 }, + { "EMPUpdate", 64, 32 }, + { "LeafletDropBehavior", 64, 32 }, + { "Overridable", 32, 32 }, + + { "W3DGameWindow", 700, 256 }, + { "SuccessState", 32, 32 }, + { "FailureState", 32, 32 }, + { "ContinueState", 32, 32 }, + { "SleepState", 32, 32 }, + + { "AIDockWaitForClearanceState", 256, 32 }, + { "AIDockProcessDockState", 256, 32 }, + { "AIGuardInnerState", 32, 32 }, + { "AIGuardIdleState", 32, 32 }, + { "AIGuardOuterState", 32, 32 }, + { "AIGuardReturnState", 32, 32 }, + { "AIGuardPickUpCrateState", 32, 32 }, + { "AIGuardAttackAggressorState", 32, 32 }, + { "AIGuardRetaliateInnerState", 32, 32 }, + { "AIGuardRetaliateIdleState", 32, 32 }, + { "AIGuardRetaliateOuterState", 32, 32 }, + { "AIGuardRetaliateReturnState", 32, 32 }, + { "AIGuardRetaliatePickUpCrateState", 32, 32 }, + { "AIGuardRetaliateAttackAggressorState", 32, 32 }, + { "AITNGuardInnerState", 32, 32 }, + { "AITNGuardIdleState", 32, 32 }, + { "AITNGuardOuterState", 32, 32 }, + { "AITNGuardReturnState", 32, 32 }, + { "AITNGuardPickUpCrateState", 32, 32 }, + { "AITNGuardAttackAggressorState", 32, 32 }, + { "AIIdleState", 2400, 32 }, + { "AIRappelState", 600, 32 }, + { "AIBusyState", 600, 32 }, + { "AIWaitState", 600, 32 }, + { "AIAttackState", 4096, 32 }, + { "AIAttackSquadState", 600, 32 }, + { "AIDeadState", 600, 32 }, + { "AIDockState", 600, 32 }, + { "AIExitState", 600, 32 }, + { "AIExitInstantlyState", 600, 32 }, + { "AIGuardState", 600, 32 }, + { "AIGuardRetaliateState", 600, 32 }, + { "AITunnelNetworkGuardState", 600, 32 }, + { "AIHuntState", 600, 32 }, + { "AIAttackAreaState", 600, 32 }, + { "AIFaceState", 1200, 32 }, + { "ApproachState", 600, 32 }, + { "DeliveringState", 600, 32 }, + { "ConsiderNewApproachState", 600, 32 }, + { "RecoverFromOffMapState", 600, 32 }, + { "HeadOffMapState", 600, 32 }, + { "CleanUpState", 600, 32 }, + { "HackInternetState", 600, 32 }, + { "PackingState", 600, 32 }, + { "UnpackingState", 600, 32 }, + { "SupplyTruckWantsToPickUpOrDeliverBoxesState", 600, 32 }, + { "RegroupingState", 600, 32 }, + { "DockingState", 600, 32 }, + { "ChinookEvacuateState", 32, 32 }, + { "ChinookHeadOffMapState", 32, 32 }, + { "ChinookTakeoffOrLandingState", 32, 32 }, + { "ChinookCombatDropState", 32, 32 }, + { "DozerActionPickActionPosState", 256, 32 }, + { "DozerActionMoveToActionPosState", 256, 32 }, + { "DozerActionDoActionState", 256, 32 }, + { "DozerPrimaryIdleState", 256, 32 }, + { "DozerActionState", 256, 32 }, + { "DozerPrimaryGoingHomeState", 256, 32 }, + { "JetAwaitingRunwayState", 64, 32 }, + { "JetOrHeliCirclingDeadAirfieldState", 64, 32 }, + { "HeliTakeoffOrLandingState", 64, 32 }, + { "VtolTakeoffOrLandingState", 64, 32 }, + { "JetOrHeliParkOrientState", 64, 32 }, + { "VtolParkOrientState", 64, 32 }, + { "JetOrHeliReloadAmmoState", 64, 32 }, + { "SupplyTruckBusyState", 600, 32 }, + { "SupplyTruckIdleState", 600, 32 }, + { "ActAsDozerState", 600, 32 }, + { "ActAsSupplyTruckState", 600, 32 }, + { "AIDockApproachState", 256, 32 }, + { "AIDockAdvancePositionState", 256, 32 }, + { "AIDockMoveToEntryState", 256, 32 }, + { "AIDockMoveToDockState", 256, 32 }, + { "AIDockMoveToExitState", 256, 32 }, + { "AIDockMoveToRallyState", 256, 32 }, + { "AIMoveToState", 600, 32 }, + { "AIMoveOutOfTheWayState", 600, 32 }, + { "AIMoveAndTightenState", 600, 32 }, + { "AIMoveAwayFromRepulsorsState", 600, 32 }, + { "AIAttackApproachTargetState", 96, 32 }, + { "AIAttackPursueTargetState", 96, 32 }, + { "AIAttackAimAtTargetState", 96, 32 }, + { "AIAttackFireWeaponState", 256, 32 }, + { "AIPickUpCrateState", 4096, 32 }, + { "AIFollowWaypointPathState", 1200, 32 }, + { "AIFollowWaypointPathExactState", 1200, 32 }, + { "AIWanderInPlaceState", 600, 32 }, + { "AIFollowPathState", 1200, 32 }, + { "AIMoveAndEvacuateState", 1200, 32 }, + { "AIMoveAndDeleteState", 600, 32 }, + { "AIEnterState", 600, 32 }, + { "JetOrHeliReturningToDeadAirfieldState", 64, 32 }, + { "JetOrHeliReturnForLandingState", 64, 32 }, + { "TurretAIIdleState", 600, 32 }, + { "TurretAIIdleScanState", 600, 32 }, + { "TurretAIAimTurretState", 600, 32 }, + { "TurretAIRecenterTurretState", 600, 32 }, + { "TurretAIHoldTurretState", 600, 32 }, + { "JetOrHeliTaxiState", 64, 32 }, + { "JetTakeoffOrLandingState", 64, 32 }, + { "JetPauseBeforeTakeoffState", 64, 32 }, + { "AIAttackMoveToState", 600, 32 }, + { "AIAttackFollowWaypointPathState", 1200, 32 }, + { "AIWanderState", 600, 32 }, + { "AIPanicState", 600, 32 }, + { "ChinookMoveToBldgState", 32, 32 }, + { "ChinookRecordCreationState", 32, 32 }, + { "ScienceInfo", 96, 32 }, + { "RankInfo", 32, 32 }, + + { "FireWeaponNugget", 32, 32 }, + { "AttackNugget", 32, 32 }, + { "DeliverPayloadNugget", 48, 32 }, + { "ApplyRandomForceNugget", 32, 32 }, + { "GenericObjectCreationNugget", 632, 32 }, + { "SoundFXNugget", 320, 32 }, + { "TracerFXNugget", 32, 32 }, + { "RayEffectFXNugget", 32, 32 }, + { "LightPulseFXNugget", 68, 32 }, + { "ViewShakeFXNugget", 140, 32 }, + { "TerrainScorchFXNugget", 48, 32 }, + { "ParticleSystemFXNugget", 832, 32 }, + { "FXListAtBonePosFXNugget", 32, 32 }, + { "Squad", 256, 32 }, + { "BuildListInfo", 400, 64 }, + + { "ScriptGroup", 128, 32 }, + { "OrCondition", 1024, 256 }, + { "ScriptAction", 2600, 512 }, + { "Script", 1024, 256 }, + { "Parameter", 8192, 1024 }, + { "Condition", 2048, 256 }, + { "Template", 32, 32 }, + { "ScriptList", 32, 32 }, + { "AttackPriorityInfo", 32, 32 }, + { "SequentialScript", 32, 32 }, + { "Win32LocalFile", 1024, 256 }, + { "StdLocalFile", 1024, 256 }, + { "RAMFile", 32, 32 }, + { "BattlePlanBonuses", 32, 32 }, + { "KindOfPercentProductionChange", 32, 32 }, + { "UserParser", 4096, 256 }, + { "XferBlockData", 32, 32 }, + { "EvaCheckInfo", 52, 16 }, + { "SuperweaponInfo", 32, 32 }, + { "NamedTimerInfo", 32, 32 }, + { "PopupMessageData", 32, 32 }, + { "FloatingTextData", 32, 32 }, + { "MapObject", 5000, 1024 }, + { "Waypoint", 1024, 32 }, + { "PolygonTrigger", 64, 64 }, + { "Bridge", 32, 32 }, + { "Mapping", 384, 64 }, + { "OutputChunk", 32, 32 }, + { "InputChunk", 32, 32 }, + { "AnimateWindow", 32, 32 }, + { "GameFont", 32, 32 }, + { "NetCommandRef", 256, 32 }, + { "GameMessageArgument", 1024, 256 }, + { "GameMessageParserArgumentType", 32, 32 }, + { "GameMessageParser", 32, 32 }, + { "WeaponBonusSet", 96, 32 }, + { "Campaign", 32, 32 }, + { "Mission", 88, 32 }, + { "ModalWindow", 32, 32 }, + { "NetPacket", 32, 32 }, + { "AISideInfo", 32, 32 }, + { "AISideBuildList", 32, 32 }, + { "MetaMapRec", 256, 32 }, + { "TransportStatus", 32, 32 }, + { "Anim2DTemplate", 32, 32 }, + { "ObjectTypes", 32, 32 }, + { "NetCommandList", 512, 32 }, + { "TurretAIData", 256, 32 }, + { "NetCommandMsg", 32, 32 }, + { "NetGameCommandMsg", 64, 32 }, + { "NetAckBothCommandMsg", 32, 32 }, + { "NetAckStage1CommandMsg", 32, 32 }, + { "NetAckStage2CommandMsg", 32, 32 }, + { "NetFrameCommandMsg", 32, 32 }, + { "NetPlayerLeaveCommandMsg", 32, 32 }, + { "NetRunAheadMetricsCommandMsg", 32, 32 }, + { "NetRunAheadCommandMsg", 32, 32 }, + { "NetDestroyPlayerCommandMsg", 32, 32 }, + { "NetDisconnectFrameCommandMsg", 32, 32 }, + { "NetDisconnectScreenOffCommandMsg", 32, 32 }, + { "NetFrameResendRequestCommandMsg", 32, 32 }, + { "NetKeepAliveCommandMsg", 32, 32 }, + { "NetDisconnectKeepAliveCommandMsg", 32, 32 }, + { "NetDisconnectPlayerCommandMsg", 32, 32 }, + { "NetPacketRouterQueryCommandMsg", 32, 32 }, + { "NetPacketRouterAckCommandMsg", 32, 32 }, + { "NetDisconnectChatCommandMsg", 32, 32 }, + { "NetChatCommandMsg", 32, 32 }, + { "NetDisconnectVoteCommandMsg", 32, 32 }, + { "NetProgressCommandMsg", 32, 32 }, + { "NetWrapperCommandMsg", 32, 32 }, + { "NetFileCommandMsg", 32, 32 }, + { "NetFileAnnounceCommandMsg", 32, 32 }, + { "NetFileProgressCommandMsg", 32, 32 }, + { "NetCommandWrapperListNode", 32, 32 }, + { "NetCommandWrapperList", 32, 32 }, + { "Connection", 32, 32 }, + { "User", 32, 32 }, + { "FrameDataManager", 32, 32 }, + { "DrawableIconInfo", 32, 32 }, + { "TintEnvelope", 128, 32 }, + { "DynamicAudioEventRTS", 4000, 256 }, + { "DrawableLocoInfo", 128, 32 }, + { "W3DPrototypeClass", 512, 256 }, + { "EnumeratedIP", 32, 32 }, + { "WaterTransparencySetting", 4, 4 }, + { "WeatherSetting", 4, 4 }, + + // W3D pools! + { "BoxPrototypeClass", 128, 128 }, + { "SpherePrototypeClass", 32, 32 }, + { "SoundRenderObjPrototypeClass", 32, 32 }, + { "RingPrototypeClass", 32, 32 }, + { "PrimitivePrototypeClass", 8192, 32 }, + { "HModelPrototypeClass", 256, 32 }, + { "ParticleEmitterPrototypeClass", 32, 32 }, + { "NullPrototypeClass", 32, 32 }, + { "HLodPrototypeClass", 700, 128 }, + { "HLodDefClass", 700, 128 }, + { "DistLODPrototypeClass", 32, 32 }, + { "DazzlePrototypeClass", 32, 32 }, + { "CollectionPrototypeClass", 32, 32 }, + { "BoxPrototypeClass", 256, 32 }, + { "AggregatePrototypeClass", 32, 32 }, + { "OBBoxRenderObjClass", 512, 128 }, + { "AABoxRenderObjClass", 32, 32 }, + { "VertexMaterialClass", 6000, 2048 }, + { "TextureClass", 1200, 256 }, + { "CloudMapTerrainTextureClass", 4, 4 }, + { "ScorchTextureClass", 4, 4 }, + { "LightMapTerrainTextureClass", 4, 4 }, + { "AlphaEdgeTextureClass", 4, 4 }, + { "AlphaTerrainTextureClass", 4, 4 }, + { "TerrainTextureClass", 4, 4 }, + { "MeshClass", 14000, 2000 }, + { "HTreeClass", 2048, 512 }, + { "HLodClass", 2048, 512 }, + { "MeshModelClass", 8192, 32 }, + { "ShareBufferClass", 32768, 1024 }, + { "AABTreeClass", 300, 128 }, + { "MotionChannelClass", 16384, 32 }, + { "BitChannelClass", 84, 32 }, + { "TimeCodedMotionChannelClass", 116, 32 }, + { "AdaptiveDeltaMotionChannelClass", 32, 32 }, + { "TimeCodedBitChannelClass", 32, 32 }, + { "UVBufferClass", 8192, 32 }, + { "TexBufferClass", 384, 128 }, + { "MatBufferClass", 256, 128 }, + { "MatrixMapperClass", 32, 32 }, + { "ScaleTextureMapperClass", 32, 32 }, + { "LinearOffsetTextureMapperClass", 96, 32 }, + { "GridTextureMapperClass", 32, 32 }, + { "RotateTextureMapperClass", 32, 32 }, + { "SineLinearOffsetTextureMapperClass", 32, 32 }, + { "StepLinearOffsetTextureMapperClass", 32, 32 }, + { "ZigZagLinearOffsetTextureMapperClass", 32, 32 }, + { "ClassicEnvironmentMapperClass", 32, 32 }, + { "EnvironmentMapperClass", 256, 32 }, + { "EdgeMapperClass", 32, 32 }, + { "WSClassicEnvironmentMapperClass", 32, 32 }, + { "WSEnvironmentMapperClass", 32, 32 }, + { "GridClassicEnvironmentMapperClass", 32, 32 }, + { "GridEnvironmentMapperClass", 32, 32 }, + { "ScreenMapperClass", 32, 32 }, + { "RandomTextureMapperClass", 32, 32 }, + { "BumpEnvTextureMapperClass", 32, 32 }, + { "MeshLoadContextClass", 4, 4 }, + { "MaterialInfoClass", 8192, 32 }, + { "MeshMatDescClass", 8192, 32 }, + { "TextureLoadTaskClass", 256, 32 }, + { "SortingNodeStruct", 288, 32 }, + { "ProxyArrayClass", 32, 32 }, + { "Line3DClass", 8, 8 }, + { "Render2DClass", 64, 32 }, + { "SurfaceClass", 128, 32 }, + { "FontCharsClassCharDataStruct", 1024, 32 }, + { "FontCharsBuffer", 16, 4 }, + { "FVFInfoClass", 152, 64 }, + { "TerrainTracksRenderObjClass", 128, 32 }, + { "DynamicIBAccessClass", 32, 32 }, + { "DX8IndexBufferClass", 128, 32 }, + { "SortingIndexBufferClass", 32, 32 }, + { "DX8VertexBufferClass", 128, 32 }, + { "SortingVertexBufferClass", 32, 32 }, + { "DynD3DMATERIAL8", 8192, 32 }, + { "DynamicMatrix3D", 512, 32 }, + { "MeshGeometryClass", 32, 32 }, + { "DynamicMeshModel", 32, 32 }, + { "GapFillerClass", 32, 32 }, + { "FontCharsClass", 64, 32 }, + { "ThumbnailManagerClass", 32, 32}, + { "SmudgeSet", 32, 32}, + { "Smudge", 128, 32}, + { 0, 0, 0 } +}; + +//----------------------------------------------------------------------------- +void userMemoryAdjustPoolSize(const char *poolName, Int& initialAllocationCount, Int& overflowAllocationCount) +{ + if (initialAllocationCount > 0) + return; + + for (const PoolSizeRec* p = sizes; p->name != NULL; ++p) + { + if (strcmp(p->name, poolName) == 0) + { + initialAllocationCount = p->initial; + overflowAllocationCount = p->overflow; + return; + } + } + + DEBUG_CRASH(("Initial size for pool %s not found -- you should add it to MemoryInit.cpp\n",poolName)); +} + +//----------------------------------------------------------------------------- +static Int roundUpMemBound(Int i) +{ + const int MEM_BOUND_ALIGNMENT = 4; + + if (i < MEM_BOUND_ALIGNMENT) + return MEM_BOUND_ALIGNMENT; + else + return (i + (MEM_BOUND_ALIGNMENT-1)) & ~(MEM_BOUND_ALIGNMENT-1); +} + +//----------------------------------------------------------------------------- +void userMemoryManagerInitPools() +{ + // note that we MUST use stdio stuff here, and not the normal game file system + // (with bigfile support, etc), because that relies on memory pools, which + // aren't yet initialized properly! so rely ONLY on straight stdio stuff here. + // (not even AsciiString. thanks.) + + // since we're called prior to main, the cur dir might not be what + // we expect. so do it the hard way. + char buf[_MAX_PATH]; + ::GetModuleFileName(NULL, buf, sizeof(buf)); + char* pEnd = buf + strlen(buf); + while (pEnd != buf) + { + if (*pEnd == '\\') + { + *pEnd = 0; + break; + } + --pEnd; + } + strcat(buf, "\\Data\\INI\\MemoryPools.ini"); + + FILE* fp = fopen(buf, "r"); + if (fp) + { + char poolName[256]; + int initial, overflow; + while (fgets(buf, _MAX_PATH, fp)) + { + if (buf[0] == ';') + continue; + if (sscanf(buf, "%s %d %d", poolName, &initial, &overflow ) == 3) + { + for (PoolSizeRec* p = sizes; p->name != NULL; ++p) + { + if (stricmp(p->name, poolName) == 0) + { + // currently, these must be multiples of 4. so round up. + p->initial = roundUpMemBound(initial); + p->overflow = roundUpMemBound(overflow); + break; // from for-p + } + } + } + } + fclose(fp); + } +} + diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index 9629f767c20..26e059f1edf 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -1,745 +1,747 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: ModuleFactory.cpp //////////////////////////////////////////////////////////////////////// -// Author: Colin Day, September 2001 -// Desc: TheModuleFactory is where we actually instance modules for objects -// and drawbles. Those modules are things such as an UpdateModule -// or DamageModule or DrawModule etc. -// -// TheModuleFactory will contain a list of ModuleTemplates, when we -// request a new module, we will look for that template in our -// list and create it -// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -#include "Common/Module.h" -#include "Common/ModuleFactory.h" -#include "Common/NameKeyGenerator.h" - -// behavior includes -#include "GameLogic/Module/AutoHealBehavior.h" -#include "GameLogic/Module/GrantStealthBehavior.h" -#include "GameLogic/Module/NeutronBlastBehavior.h" -#include "GameLogic/Module/BehaviorModule.h" -#include "GameLogic/Module/BridgeBehavior.h" -#include "GameLogic/Module/BridgeScaffoldBehavior.h" -#include "GameLogic/Module/BridgeTowerBehavior.h" -#include "GameLogic/Module/CountermeasuresBehavior.h" -#include "GameLogic/Module/DumbProjectileBehavior.h" -#include "GameLogic/Module/FreeFallProjectileBehavior.h" -#include "GameLogic/Module/InstantDeathBehavior.h" -#include "GameLogic/Module/SlowDeathBehavior.h" -#include "GameLogic/Module/HelicopterSlowDeathUpdate.h" -#include "GameLogic/Module/NeutronMissileSlowDeathUpdate.h" -#include "GameLogic/Module/CaveContain.h" -#include "GameLogic/Module/OpenContain.h" -#include "GameLogic/Module/OverchargeBehavior.h" -#include "GameLogic/Module/HealContain.h" -#include "GameLogic/Module/GarrisonContain.h" -#include "GameLogic/Module/InternetHackContain.h" -#include "GameLogic/Module/RailedTransportContain.h" -#include "GameLogic/Module/RiderChangeContain.h" -#include "GameLogic/Module/TransportContain.h" -#include "GameLogic/Module/MobNexusContain.h" -#include "GameLogic/Module/TunnelContain.h" -#include "GameLogic/Module/OverlordContain.h" -#include "GameLogic/Module/HelixContain.h" -#include "GameLogic/Module/ParachuteContain.h" -#ifdef ALLOW_SURRENDER -#include "GameLogic/Module/POWTruckBehavior.h" -#include "GameLogic/Module/PrisonBehavior.h" -#include "GameLogic/Module/PropagandaCenterBehavior.h" -#endif -#include "GameLogic/Module/PropagandaTowerBehavior.h" -#include "GameLogic/Module/BunkerBusterBehavior.h" -#include "GameLogic/Module/FireWeaponWhenDamagedBehavior.h" -#include "GameLogic/Module/FireWeaponWhenDeadBehavior.h" -#include "GameLogic/Module/GenerateMinefieldBehavior.h" -#include "GameLogic/Module/ParkingPlaceBehavior.h" -#include "GameLogic/Module/FlightDeckBehavior.h" -#include "GameLogic/Module/PoisonedBehavior.h" -#include "GameLogic/Module/RebuildHoleBehavior.h" -#include "GameLogic/Module/SupplyWarehouseCripplingBehavior.h" -#include "GameLogic/Module/TechBuildingBehavior.h" -#include "GameLogic/Module/MinefieldBehavior.h" -#include "GameLogic/Module/BattleBusSlowDeathBehavior.h" -#include "GameLogic/Module/JetSlowDeathBehavior.h" - -// die includes -#include "GameLogic/Module/CreateCrateDie.h" -#include "GameLogic/Module/CreateObjectDie.h" -#include "GameLogic/Module/CrushDie.h" -#include "GameLogic/Module/DamDie.h" -#include "GameLogic/Module/DestroyDie.h" -#include "GameLogic/Module/EjectPilotDie.h" -#include "GameLogic/Module/FXListDie.h" -#include "GameLogic/Module/RebuildHoleExposeDie.h" -#include "GameLogic/Module/SpecialPowerCompletionDie.h" -#include "GameLogic/Module/UpgradeDie.h" -#include "GameLogic/Module/KeepObjectDie.h" - -// logic update includes -#include "GameLogic/Module/AIUpdate.h" -#include "GameLogic/Module/AnimationSteeringUpdate.h" -#include "GameLogic/Module/AssistedTargetingUpdate.h" -#include "GameLogic/Module/BaseRegenerateUpdate.h" -#include "GameLogic/Module/BoneFXUpdate.h" -#include "GameLogic/Module/ChinookAIUpdate.h" -#include "GameLogic/Module/DefaultProductionExitUpdate.h" -#include "GameLogic/Module/DeletionUpdate.h" -#include "GameLogic/Module/SmartBombTargetHomingUpdate.h" -#include "GameLogic/Module/DeliverPayloadAIUpdate.h" -#include "GameLogic/Module/DozerAIUpdate.h" -#include "GameLogic/Module/DynamicGeometryInfoUpdate.h" -#include "GameLogic/Module/DynamicShroudClearingRangeUpdate.h" -#include "GameLogic/Module/EnemyNearUpdate.h" -#include "GameLogic/Module/FireSpreadUpdate.h" -#include "GameLogic/Module/FirestormDynamicGeometryInfoUpdate.h" -#include "GameLogic/Module/FireWeaponUpdate.h" -#include "GameLogic/Module/FlammableUpdate.h" -#include "GameLogic/Module/FloatUpdate.h" -#include "GameLogic/Module/TensileFormationUpdate.h" -#include "GameLogic/Module/HackInternetAIUpdate.h" -#include "GameLogic/Module/DeployStyleAIUpdate.h" -#include "GameLogic/Module/AssaultTransportAIUpdate.h" -#include "GameLogic/Module/HeightDieUpdate.h" -#include "GameLogic/Module/HordeUpdate.h" -#include "GameLogic/Module/ScatterShotUpdate.h" -#include "GameLogic/Module/JetAIUpdate.h" -#include "GameLogic/Module/LaserUpdate.h" -#include "GameLogic/Module/PointDefenseLaserUpdate.h" -#include "GameLogic/Module/CleanupHazardUpdate.h" -#include "GameLogic/Module/AutoFindHealingUpdate.h" -#include "GameLogic/Module/CommandButtonHuntUpdate.h" -#include "GameLogic/Module/PilotFindVehicleUpdate.h" -#include "GameLogic/Module/DemoTrapUpdate.h" -#include "GameLogic/Module/ParticleUplinkCannonUpdate.h" -#include "GameLogic/Module/SpectreGunshipUpdate.h" -#include "GameLogic/Module/SpectreGunshipDeploymentUpdate.h" -#include "GameLogic/Module/BaikonurLaunchPower.h" -#include "GameLogic/Module/BattlePlanUpdate.h" -#include "GameLogic/Module/LifetimeUpdate.h" -#include "GameLogic/Module/RadiusDecalUpdate.h" -#include "GameLogic/Module/RadiusDecalBehavior.h" -#include "GameLogic/Module/AutoDepositUpdate.h" -#include "GameLogic/Module/MissileAIUpdate.h" -#include "GameLogic/Module/NeutronMissileUpdate.h" -#include "GameLogic/Module/OCLUpdate.h" -#include "GameLogic/Module/PhysicsUpdate.h" -#ifdef ALLOW_SURRENDER -#include "GameLogic/Module/POWTruckAIUpdate.h" -#endif -#include "GameLogic/Module/ProductionUpdate.h" -#include "GameLogic/Module/ProjectileStreamUpdate.h" -#include "GameLogic/Module/ProneUpdate.h" -#include "GameLogic/Module/QueueProductionExitUpdate.h" -#include "GameLogic/Module/RadarUpdate.h" -#include "GameLogic/Module/RepairDockUpdate.h" -#ifdef ALLOW_SURRENDER -#include "GameLogic/Module/PrisonDockUpdate.h" -#endif -#include "GameLogic/Module/RailedTransportDockUpdate.h" -#include "GameLogic/Module/RailedTransportAIUpdate.h" -#include "GameLogic/Module/RailroadGuideAIUpdate.h" -#include "GameLogic/Module/SlavedUpdate.h" -#include "GameLogic/Module/MobMemberSlavedUpdate.h" -#include "GameLogic/Module/SpecialAbilityUpdate.h" -#include "GameLogic/Module/MissileLauncherBuildingUpdate.h" -#include "GameLogic/Module/StealthDetectorUpdate.h" -#include "GameLogic/Module/StealthUpdate.h" -#include "GameLogic/Module/SpawnPointProductionExitUpdate.h" -#include "GameLogic/Module/SpawnBehavior.h" -#include "GameLogic/Module/SpyVisionUpdate.h" -#include "GameLogic/Module/StickyBombUpdate.h" -#include "GameLogic/Module/FireOCLAfterWeaponCooldownUpdate.h" -#include "GameLogic/Module/HijackerUpdate.h" -#include "GameLogic/Module/StructureCollapseUpdate.h" -#include "GameLogic/Module/StructureToppleUpdate.h" -#include "GameLogic/Module/SupplyCenterDockUpdate.h" -#include "GameLogic/Module/SupplyCenterProductionExitUpdate.h" -#include "GameLogic/Module/SupplyTruckAIUpdate.h" -#include "GameLogic/Module/SupplyWarehouseDockUpdate.h" -#include "GameLogic/Module/ToppleUpdate.h" -#include "GameLogic/Module/TransportAIUpdate.h" -#include "GameLogic/Module/WanderAIUpdate.h" -#include "GameLogic/Module/TeleporterAIUpdate.h" -#include "GameLogic/Module/WaveGuideUpdate.h" -#include "GameLogic/Module/WeaponBonusUpdate.h" -#include "GameLogic/Module/ArmorDamageScalarUpdate.h" -#include "GameLogic/Module/WorkerAIUpdate.h" -#include "GameLogic/Module/PowerPlantUpdate.h" -#include "GameLogic/Module/CheckpointUpdate.h" -#include "GameLogic/Module/EMPUpdate.h" - -// upgrade includes -#include "GameLogic/Module/ActiveShroudUpgrade.h" -#include "GameLogic/Module/ArmorUpgrade.h" -#include "GameLogic/Module/CommandSetUpgrade.h" -#include "GameLogic/Module/GrantScienceUpgrade.h" -#include "GameLogic/Module/PassengersFireUpgrade.h" -#include "GameLogic/Module/LocomotorSetUpgrade.h" -#include "GameLogic/Module/ObjectCreationUpgrade.h" -#include "GameLogic/Module/RadarUpgrade.h" -#include "GameLogic/Module/PowerPlantUpgrade.h" -#include "GameLogic/Module/ReplaceObjectUpgrade.h" -#include "GameLogic/Module/ModelConditionUpgrade.h" -#include "GameLogic/Module/StatusBitsUpgrade.h" -#include "GameLogic/Module/SubObjectsUpgrade.h" -#include "GameLogic/Module/StealthUpgrade.h" -#include "GameLogic/Module/UnpauseSpecialPowerUpgrade.h" -#include "GameLogic/Module/WeaponBonusUpgrade.h" -#include "GameLogic/Module/WeaponSetUpgrade.h" -#include "GameLogic/Module/WeaponBonusUpgrade.h" -#include "GameLogic/Module/CostModifierUpgrade.h" -#include "GameLogic/Module/ProductionTimeModifierUpgrade.h" -#include "GameLogic/Module/UnitProductionBonusUpgrade.h" -#include "GameLogic/Module/ExperienceScalarUpgrade.h" -#include "GameLogic/Module/MaxHealthUpgrade.h" - -// create includes -#include "GameLogic/Module/LockWeaponCreate.h" -#include "GameLogic/Module/SupplyCenterCreate.h" -#include "GameLogic/Module/SupplyWarehouseCreate.h" -#include "GameLogic/Module/GrantUpgradeCreate.h" -#include "GameLogic/Module/PreorderCreate.h" -#include "GameLogic/Module/SpecialPowerCreate.h" -#include "GameLogic/Module/VeterancyGainCreate.h" - -// damage includes -#include "GameLogic/Module/BoneFXDamage.h" -#include "GameLogic/Module/TransitionDamageFX.h" - -// collide includes -#include "GameLogic/Module/FireWeaponCollide.h" -#include "GameLogic/Module/SquishCollide.h" - -#include "GameLogic/Module/ConvertToCarBombCrateCollide.h" -#include "GameLogic/Module/ConvertToHijackedVehicleCrateCollide.h" -#include "GameLogic/Module/HealCrateCollide.h" -#include "GameLogic/Module/MoneyCrateCollide.h" -#include "GameLogic/Module/SabotageCommandCenterCrateCollide.h" -#include "GameLogic/Module/SabotageFakeBuildingCrateCollide.h" -#include "GameLogic/Module/SabotageInternetCenterCrateCollide.h" -#include "GameLogic/Module/SabotageMilitaryFactoryCrateCollide.h" -#include "GameLogic/Module/SabotagePowerPlantCrateCollide.h" -#include "GameLogic/Module/SabotageSuperweaponCrateCollide.h" -#include "GameLogic/Module/SabotageSupplyCenterCrateCollide.h" -#include "GameLogic/Module/SabotageSupplyDropzoneCrateCollide.h" -#include "GameLogic/Module/SalvageCrateCollide.h" -#include "GameLogic/Module/ShroudCrateCollide.h" -#include "GameLogic/Module/UnitCrateCollide.h" -#include "GameLogic/Module/VeterancyCrateCollide.h" - -// body includes -#include "GameLogic/Module/InactiveBody.h" -#include "GameLogic/Module/ActiveBody.h" -#include "GameLogic/Module/HighlanderBody.h" -#include "GameLogic/Module/ImmortalBody.h" -#include "GameLogic/Module/StructureBody.h" -#include "GameLogic/Module/HiveStructureBody.h" -#include "GameLogic/Module/UndeadBody.h" - -// contain includes -// (none) - -// special power modules -#include "GameLogic/Module/CashHackSpecialPower.h" -#include "GameLogic/Module/DefectorSpecialPower.h" -#ifdef ALLOW_DEMORALIZE -#include "GameLogic/Module/DemoralizeSpecialPower.h" -#endif -#include "GameLogic/Module/OCLSpecialPower.h" -#include "GameLogic/Module/SpecialAbility.h" -#include "GameLogic/Module/SpyVisionSpecialPower.h" -#include "GameLogic/Module/CashBountyPower.h" -#include "GameLogic/Module/CleanupAreaPower.h" -#include "GameLogic/Module/FireWeaponPower.h" - -// destroy includes -// (none) - -// client update includes -#include "GameClient/Module/AnimatedParticleSysBoneClientUpdate.h" -#include "GameClient/Module/SwayClientUpdate.h" -#include "GameClient/Module/BeaconClientUpdate.h" - -// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// -ModuleFactory *TheModuleFactory = NULL; ///< the module factory singleton - -// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// - -#ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -ModuleFactory::ModuleFactory( void ) -{ - m_moduleTemplateMap.clear(); - m_moduleDataList.clear(); - -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -ModuleFactory::~ModuleFactory( void ) -{ - m_moduleTemplateMap.clear(); - - for (ModuleDataList::iterator i = m_moduleDataList.begin(); i != m_moduleDataList.end(); ++i) - { - const ModuleData* data = *i; - delete data; - } - m_moduleDataList.clear(); - -} - -//------------------------------------------------------------------------------------------------- -/** Initialize the module factory. Any class that needs to be attached - * to objects or drawables as modules needs to add a template - * for that class here */ -//------------------------------------------------------------------------------------------------- -void ModuleFactory::init( void ) -{ - - // behavior modules - addModule( AutoHealBehavior ); - addModule( GrantStealthBehavior ); - addModule( NeutronBlastBehavior ); - addModule( BridgeBehavior ); - addModule( BridgeScaffoldBehavior ); - addModule( BridgeTowerBehavior ); - addModule( CountermeasuresBehavior ); - addModule( DumbProjectileBehavior ); - addModule( FreeFallProjectileBehavior ); - addModule( PhysicsBehavior ); - addModule( InstantDeathBehavior ); - addModule( SlowDeathBehavior ); - addModule( HelicopterSlowDeathBehavior ); - addModule( NeutronMissileSlowDeathBehavior ); - addModule( CaveContain ); - addModule( OpenContain ); - addModule( OverchargeBehavior ); - addModule( HealContain ); - addModule( GarrisonContain ); - addModule( InternetHackContain ); - addModule( TransportContain ); - addModule( RiderChangeContain ); - addModule( RailedTransportContain ); - addModule( MobNexusContain ); - addModule( TunnelContain ); - addModule( OverlordContain ); - addModule( HelixContain ); - addModule( ParachuteContain ); -#ifdef ALLOW_SURRENDER - addModule( POWTruckBehavior ); - addModule( PrisonBehavior ); - addModule( PropagandaCenterBehavior ); -#endif - addModule( PropagandaTowerBehavior ); - addModule( BunkerBusterBehavior ); - addModule( FireWeaponWhenDamagedBehavior ); - addModule( FireWeaponWhenDeadBehavior ); - addModule( GenerateMinefieldBehavior ); - addModule( ParkingPlaceBehavior ); - addModule( FlightDeckBehavior ); - addModule( PoisonedBehavior ); - addModule( RebuildHoleBehavior ); - addModule( SupplyWarehouseCripplingBehavior ); - addModule( TechBuildingBehavior ); - addModule( MinefieldBehavior ); - addModule( BattleBusSlowDeathBehavior ); - addModule( JetSlowDeathBehavior ); - addModule( RailroadBehavior ); - addModule( SpawnBehavior ); - - // die modules - addModule( DestroyDie ); - addModule( FXListDie ); - addModule( CrushDie ); - addModule( DamDie ); - addModule( CreateCrateDie ); - addModule( CreateObjectDie ); - addModule( EjectPilotDie ); - addModule( SpecialPowerCompletionDie ); - addModule( RebuildHoleExposeDie ); - addModule( UpgradeDie ); - addModule( KeepObjectDie ); - - // update modules - addModule( AssistedTargetingUpdate ); - addModule( AutoFindHealingUpdate ); - addModule( BaseRegenerateUpdate ); - addModule( StealthDetectorUpdate ); - addModule( StealthUpdate ); - addModule( DeletionUpdate ); - addModule( SmartBombTargetHomingUpdate ); - addModule( DynamicShroudClearingRangeUpdate ); - addModule( DeployStyleAIUpdate ); - addModule( AssaultTransportAIUpdate ); - addModule( HordeUpdate ); - addModule( ToppleUpdate ); - addModule( EnemyNearUpdate ); - addModule( LifetimeUpdate ); - addModule( RadiusDecalUpdate ); - addModule( RadiusDecalBehavior ); - addModule( EMPUpdate ); - addModule( LeafletDropBehavior ); - addModule( AutoDepositUpdate ); - addModule( WeaponBonusUpdate ); - addModule( ArmorDamageScalarUpdate ); - addModule( MissileAIUpdate ); - addModule( NeutronMissileUpdate ); - addModule( FireSpreadUpdate ); - addModule( FireWeaponUpdate ); - addModule( FlammableUpdate ); - addModule( FloatUpdate ); - addModule( TensileFormationUpdate ); - addModule( HeightDieUpdate ); - addModule( ScatterShotUpdate ); - addModule( ChinookAIUpdate ); - addModule( JetAIUpdate ); - addModule( AIUpdateInterface ); - addModule( SupplyTruckAIUpdate ); - addModule( DeliverPayloadAIUpdate ); - addModule( HackInternetAIUpdate ); - addModule( DynamicGeometryInfoUpdate ); - addModule( FirestormDynamicGeometryInfoUpdate ); - addModule( LaserUpdate ); - addModule( PointDefenseLaserUpdate ); - addModule( CleanupHazardUpdate ); - addModule( CommandButtonHuntUpdate ); - addModule( PilotFindVehicleUpdate ); - addModule( DemoTrapUpdate ); - addModule( ParticleUplinkCannonUpdate ); - addModule( SpectreGunshipUpdate ); - addModule( SpectreGunshipDeploymentUpdate ); - addModule( BaikonurLaunchPower ); - addModule( BattlePlanUpdate ); - addModule( ProjectileStreamUpdate ); - addModule( QueueProductionExitUpdate ); - addModule( RepairDockUpdate ); -#ifdef ALLOW_SURRENDER - addModule( PrisonDockUpdate ); -#endif - addModule( RailedTransportDockUpdate ); - addModule( DefaultProductionExitUpdate ); - addModule( SpawnPointProductionExitUpdate ); - addModule( SpyVisionUpdate ); - addModule( SlavedUpdate ); - addModule( MobMemberSlavedUpdate ); - addModule( OCLUpdate ); - addModule( SpecialAbilityUpdate ); - addModule( MissileLauncherBuildingUpdate ); - addModule( SupplyCenterProductionExitUpdate ); - addModule( SupplyCenterDockUpdate ); - addModule( SupplyWarehouseDockUpdate ); - addModule( DozerAIUpdate ); -#ifdef ALLOW_SURRENDER - addModule( POWTruckAIUpdate ); -#endif - addModule( RailedTransportAIUpdate ); - addModule( ProductionUpdate ); - addModule( ProneUpdate ); - addModule( StickyBombUpdate ); - addModule( FireOCLAfterWeaponCooldownUpdate ); - addModule( HijackerUpdate ); - addModule( StructureToppleUpdate ); - addModule( StructureCollapseUpdate ); - addModule( BoneFXUpdate ); - addModule( RadarUpdate ); - addModule( AnimationSteeringUpdate ); - addModule( TransportAIUpdate ); - addModule( WanderAIUpdate ); - addModule( TeleporterAIUpdate ); - addModule( WaveGuideUpdate ); - addModule( WorkerAIUpdate ); - addModule( PowerPlantUpdate ); - addModule( CheckpointUpdate ); - - // upgrade modules - addModule( CostModifierUpgrade ); - addModule( ProductionTimeModifierUpgrade ); - addModule( UnitProductionBonusUpgrade ); - addModule( ActiveShroudUpgrade ); - addModule( ArmorUpgrade ); - addModule( CommandSetUpgrade ); - addModule( GrantScienceUpgrade ); - addModule( PassengersFireUpgrade ); - addModule( StatusBitsUpgrade ); - addModule( SubObjectsUpgrade ); - addModule( StealthUpgrade ); - addModule( RadarUpgrade ); - addModule( PowerPlantUpgrade ); - addModule( LocomotorSetUpgrade ); - addModule( ObjectCreationUpgrade ); - addModule( ReplaceObjectUpgrade ); - addModule( ModelConditionUpgrade ); - addModule( UnpauseSpecialPowerUpgrade ); - addModule( WeaponBonusUpgrade ); - addModule( WeaponSetUpgrade ); - addModule( WeaponBonusUpgrade ); - addModule( ExperienceScalarUpgrade ); - addModule( MaxHealthUpgrade ); - - // create modules - addModule( LockWeaponCreate ); - addModule( PreorderCreate ); - addModule( SupplyCenterCreate ); - addModule( SupplyWarehouseCreate ); - addModule( SpecialPowerCreate ); - addModule( GrantUpgradeCreate ); - addModule( VeterancyGainCreate ); - - // damage modules - addModule( BoneFXDamage ); - addModule( TransitionDamageFX ); - - // collide modules - addModule( FireWeaponCollide ); - addModule( SquishCollide ); - - addModule( HealCrateCollide ); - addModule( MoneyCrateCollide ); - addModule( ShroudCrateCollide ); - addModule( UnitCrateCollide ); - addModule( VeterancyCrateCollide ); - addModule( ConvertToCarBombCrateCollide ); - addModule( ConvertToHijackedVehicleCrateCollide ); - addModule( SabotageCommandCenterCrateCollide ); - addModule( SabotageFakeBuildingCrateCollide ); - addModule( SabotageInternetCenterCrateCollide ); - addModule( SabotageMilitaryFactoryCrateCollide ); - addModule( SabotagePowerPlantCrateCollide ); - addModule( SabotageSuperweaponCrateCollide ); - addModule( SabotageSupplyCenterCrateCollide ); - addModule( SabotageSupplyDropzoneCrateCollide ); - addModule( SalvageCrateCollide ); - - // body modules - addModule( InactiveBody ); - addModule( ActiveBody ); - addModule( HighlanderBody ); - addModule( ImmortalBody ); - addModule( StructureBody ); - addModule( HiveStructureBody ); - addModule( UndeadBody ); - - // contain modules - // (none) - - // special power modules - addModule( CashHackSpecialPower ); - addModule( DefectorSpecialPower ); -#ifdef ALLOW_DEMORALIZE - addModule( DemoralizeSpecialPower ); -#endif - addModule( OCLSpecialPower ); - addModule( FireWeaponPower ); - addModule( SpecialAbility ); - addModule( SpyVisionSpecialPower ); - addModule( CashBountyPower ); - addModule( CleanupAreaPower ); - - // destroy modules - // (none) - - // client update modules - addModule( AnimatedParticleSysBoneClientUpdate ); - addModule( SwayClientUpdate ); - addModule( BeaconClientUpdate ); - -} // end init - -//------------------------------------------------------------------------------------------------- -Int ModuleFactory::findModuleInterfaceMask(const AsciiString& name, ModuleType type) -{ - if (name.isEmpty()) - return 0; - - const ModuleTemplate* moduleTemplate = findModuleTemplate(name, type); - if (moduleTemplate) - { - return moduleTemplate->m_whichInterfaces; - } - - return 0; -} - -//------------------------------------------------------------------------------------------------- -ModuleData* ModuleFactory::newModuleDataFromINI(INI* ini, const AsciiString& name, ModuleType type, - const AsciiString& moduleTag) -{ - if (name.isEmpty()) - return NULL; - - const ModuleTemplate* moduleTemplate = findModuleTemplate(name, type); - if (moduleTemplate) - { - ModuleData* md = (*moduleTemplate->m_createDataProc)(ini); - md->setModuleTagNameKey( NAMEKEY( moduleTag ) ); - m_moduleDataList.push_back(md); - return md; - } - - return NULL; -} - -// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------------- -/*static*/ NameKeyType ModuleFactory::makeDecoratedNameKey(const AsciiString& name, ModuleType type) -{ - char tmp[256]; - tmp[0] = '0' + (int)type; - strcpy(&tmp[1], name.str()); - return TheNameKeyGenerator->nameToKey(tmp); -} - -//------------------------------------------------------------------------------------------------- -const ModuleFactory::ModuleTemplate* ModuleFactory::findModuleTemplate(const AsciiString& name, ModuleType type) -{ - NameKeyType namekey = makeDecoratedNameKey(name, type); - - ModuleTemplateMap::const_iterator it = m_moduleTemplateMap.find(namekey); - if (it == m_moduleTemplateMap.end()) - { - DEBUG_CRASH(( "Module name '%s' not found\n", name.str() )); - return NULL; - } - else - { - return &(*it).second; - } -} - -//------------------------------------------------------------------------------------------------- -/** Allocate a new acton class istance given the name */ -//------------------------------------------------------------------------------------------------- -Module *ModuleFactory::newModule( Thing *thing, const AsciiString& name, const ModuleData* moduleData, ModuleType type ) -{ - // sanity - if( name.isEmpty() ) - { - DEBUG_CRASH(("attempting to create module with empty name\n")); - return NULL; - } - const ModuleTemplate* mt = findModuleTemplate(name, type); - if (mt) - { - Module* mod = (*mt->m_createProc)( thing, moduleData ); - -#ifdef DEBUG_CRASHING - if (type == MODULETYPE_BEHAVIOR) - { - BehaviorModule* bm = (BehaviorModule*)mod; - - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_BODY)) != 0) == (bm->getBody() != NULL), - ("getInterfaceMask bad for MODULE_BODY (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_COLLIDE)) != 0) == (bm->getCollide() != NULL), - ("getInterfaceMask bad for MODULE_COLLIDE (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_CONTAIN)) != 0) == (bm->getContain() != NULL), - ("getInterfaceMask bad for MODULE_CONTAIN (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_CREATE)) != 0) == (bm->getCreate() != NULL), - ("getInterfaceMask bad for MODULE_CREATE (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_DAMAGE)) != 0) == (bm->getDamage() != NULL), - ("getInterfaceMask bad for MODULE_DAMAGE (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_DESTROY)) != 0) == (bm->getDestroy() != NULL), - ("getInterfaceMask bad for MODULE_DESTROY (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_DIE)) != 0) == (bm->getDie() != NULL), - ("getInterfaceMask bad for MODULE_DIE (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_SPECIAL_POWER)) != 0) == (bm->getSpecialPower() != NULL), - ("getInterfaceMask bad for MODULE_SPECIAL_POWER (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_UPDATE)) != 0) == (bm->getUpdate() != NULL), - ("getInterfaceMask bad for MODULE_UPDATE (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_UPGRADE)) != 0) == (bm->getUpgrade() != NULL), - ("getInterfaceMask bad for MODULE_UPGRADE (%s)\n",name.str())); - } -#endif - - return mod; - } - - return NULL; - -} // end newModule - -//------------------------------------------------------------------------------------------------- -/** Add a module template to our list of templates */ -//------------------------------------------------------------------------------------------------- -void ModuleFactory::addModuleInternal( NewModuleProc proc, NewModuleDataProc dataproc, ModuleType type, const AsciiString& name, Int whichIntf ) -{ - NameKeyType namekey = makeDecoratedNameKey(name, type); - ModuleTemplate& mtm = m_moduleTemplateMap[namekey]; // this creates it if it does not exist already - mtm.m_createProc = proc; - mtm.m_createDataProc = dataproc; - mtm.m_whichInterfaces = whichIntf; -} - -//------------------------------------------------------------------------------------------------- -void ModuleFactory::crc( Xfer *xfer ) -{ - for (ModuleDataList::iterator mdIt = m_moduleDataList.begin(); mdIt != m_moduleDataList.end(); ++mdIt) - { - ((ModuleData *)(*mdIt))->crc(xfer); - } -} - -//------------------------------------------------------------------------------------------------- -void ModuleFactory::xfer( Xfer *xfer ) -{ - - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - for (ModuleDataList::iterator mdIt = m_moduleDataList.begin(); mdIt != m_moduleDataList.end(); ++mdIt) - { - ((ModuleData *)(*mdIt))->xfer(xfer); - } -} - -//------------------------------------------------------------------------------------------------- -void ModuleFactory::loadPostProcess( void ) -{ -} +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: ModuleFactory.cpp //////////////////////////////////////////////////////////////////////// +// Author: Colin Day, September 2001 +// Desc: TheModuleFactory is where we actually instance modules for objects +// and drawbles. Those modules are things such as an UpdateModule +// or DamageModule or DrawModule etc. +// +// TheModuleFactory will contain a list of ModuleTemplates, when we +// request a new module, we will look for that template in our +// list and create it +// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#include "Common/Module.h" +#include "Common/ModuleFactory.h" +#include "Common/NameKeyGenerator.h" + +// behavior includes +#include "GameLogic/Module/AutoHealBehavior.h" +#include "GameLogic/Module/GrantStealthBehavior.h" +#include "GameLogic/Module/NeutronBlastBehavior.h" +#include "GameLogic/Module/BehaviorModule.h" +#include "GameLogic/Module/BridgeBehavior.h" +#include "GameLogic/Module/BridgeScaffoldBehavior.h" +#include "GameLogic/Module/BridgeTowerBehavior.h" +#include "GameLogic/Module/CountermeasuresBehavior.h" +#include "GameLogic/Module/DumbProjectileBehavior.h" +#include "GameLogic/Module/FreeFallProjectileBehavior.h" +#include "GameLogic/Module/InstantDeathBehavior.h" +#include "GameLogic/Module/SlowDeathBehavior.h" +#include "GameLogic/Module/HelicopterSlowDeathUpdate.h" +#include "GameLogic/Module/NeutronMissileSlowDeathUpdate.h" +#include "GameLogic/Module/CaveContain.h" +#include "GameLogic/Module/OpenContain.h" +#include "GameLogic/Module/OverchargeBehavior.h" +#include "GameLogic/Module/HealContain.h" +#include "GameLogic/Module/GarrisonContain.h" +#include "GameLogic/Module/InternetHackContain.h" +#include "GameLogic/Module/RailedTransportContain.h" +#include "GameLogic/Module/RiderChangeContain.h" +#include "GameLogic/Module/TransportContain.h" +#include "GameLogic/Module/MobNexusContain.h" +#include "GameLogic/Module/TunnelContain.h" +#include "GameLogic/Module/OverlordContain.h" +#include "GameLogic/Module/HelixContain.h" +#include "GameLogic/Module/ParachuteContain.h" +#ifdef ALLOW_SURRENDER +#include "GameLogic/Module/POWTruckBehavior.h" +#include "GameLogic/Module/PrisonBehavior.h" +#include "GameLogic/Module/PropagandaCenterBehavior.h" +#endif +#include "GameLogic/Module/PropagandaTowerBehavior.h" +#include "GameLogic/Module/BunkerBusterBehavior.h" +#include "GameLogic/Module/FireWeaponWhenDamagedBehavior.h" +#include "GameLogic/Module/FireWeaponWhenDeadBehavior.h" +#include "GameLogic/Module/DelayedUpgradeBehavior.h" +#include "GameLogic/Module/GenerateMinefieldBehavior.h" +#include "GameLogic/Module/ParkingPlaceBehavior.h" +#include "GameLogic/Module/FlightDeckBehavior.h" +#include "GameLogic/Module/PoisonedBehavior.h" +#include "GameLogic/Module/RebuildHoleBehavior.h" +#include "GameLogic/Module/SupplyWarehouseCripplingBehavior.h" +#include "GameLogic/Module/TechBuildingBehavior.h" +#include "GameLogic/Module/MinefieldBehavior.h" +#include "GameLogic/Module/BattleBusSlowDeathBehavior.h" +#include "GameLogic/Module/JetSlowDeathBehavior.h" + +// die includes +#include "GameLogic/Module/CreateCrateDie.h" +#include "GameLogic/Module/CreateObjectDie.h" +#include "GameLogic/Module/CrushDie.h" +#include "GameLogic/Module/DamDie.h" +#include "GameLogic/Module/DestroyDie.h" +#include "GameLogic/Module/EjectPilotDie.h" +#include "GameLogic/Module/FXListDie.h" +#include "GameLogic/Module/RebuildHoleExposeDie.h" +#include "GameLogic/Module/SpecialPowerCompletionDie.h" +#include "GameLogic/Module/UpgradeDie.h" +#include "GameLogic/Module/KeepObjectDie.h" + +// logic update includes +#include "GameLogic/Module/AIUpdate.h" +#include "GameLogic/Module/AnimationSteeringUpdate.h" +#include "GameLogic/Module/AssistedTargetingUpdate.h" +#include "GameLogic/Module/BaseRegenerateUpdate.h" +#include "GameLogic/Module/BoneFXUpdate.h" +#include "GameLogic/Module/ChinookAIUpdate.h" +#include "GameLogic/Module/DefaultProductionExitUpdate.h" +#include "GameLogic/Module/DeletionUpdate.h" +#include "GameLogic/Module/SmartBombTargetHomingUpdate.h" +#include "GameLogic/Module/DeliverPayloadAIUpdate.h" +#include "GameLogic/Module/DozerAIUpdate.h" +#include "GameLogic/Module/DynamicGeometryInfoUpdate.h" +#include "GameLogic/Module/DynamicShroudClearingRangeUpdate.h" +#include "GameLogic/Module/EnemyNearUpdate.h" +#include "GameLogic/Module/FireSpreadUpdate.h" +#include "GameLogic/Module/FirestormDynamicGeometryInfoUpdate.h" +#include "GameLogic/Module/FireWeaponUpdate.h" +#include "GameLogic/Module/FlammableUpdate.h" +#include "GameLogic/Module/FloatUpdate.h" +#include "GameLogic/Module/TensileFormationUpdate.h" +#include "GameLogic/Module/HackInternetAIUpdate.h" +#include "GameLogic/Module/DeployStyleAIUpdate.h" +#include "GameLogic/Module/AssaultTransportAIUpdate.h" +#include "GameLogic/Module/HeightDieUpdate.h" +#include "GameLogic/Module/HordeUpdate.h" +#include "GameLogic/Module/ScatterShotUpdate.h" +#include "GameLogic/Module/JetAIUpdate.h" +#include "GameLogic/Module/LaserUpdate.h" +#include "GameLogic/Module/PointDefenseLaserUpdate.h" +#include "GameLogic/Module/CleanupHazardUpdate.h" +#include "GameLogic/Module/AutoFindHealingUpdate.h" +#include "GameLogic/Module/CommandButtonHuntUpdate.h" +#include "GameLogic/Module/PilotFindVehicleUpdate.h" +#include "GameLogic/Module/DemoTrapUpdate.h" +#include "GameLogic/Module/ParticleUplinkCannonUpdate.h" +#include "GameLogic/Module/SpectreGunshipUpdate.h" +#include "GameLogic/Module/SpectreGunshipDeploymentUpdate.h" +#include "GameLogic/Module/BaikonurLaunchPower.h" +#include "GameLogic/Module/BattlePlanUpdate.h" +#include "GameLogic/Module/LifetimeUpdate.h" +#include "GameLogic/Module/RadiusDecalUpdate.h" +#include "GameLogic/Module/RadiusDecalBehavior.h" +#include "GameLogic/Module/AutoDepositUpdate.h" +#include "GameLogic/Module/MissileAIUpdate.h" +#include "GameLogic/Module/NeutronMissileUpdate.h" +#include "GameLogic/Module/OCLUpdate.h" +#include "GameLogic/Module/PhysicsUpdate.h" +#ifdef ALLOW_SURRENDER +#include "GameLogic/Module/POWTruckAIUpdate.h" +#endif +#include "GameLogic/Module/ProductionUpdate.h" +#include "GameLogic/Module/ProjectileStreamUpdate.h" +#include "GameLogic/Module/ProneUpdate.h" +#include "GameLogic/Module/QueueProductionExitUpdate.h" +#include "GameLogic/Module/RadarUpdate.h" +#include "GameLogic/Module/RepairDockUpdate.h" +#ifdef ALLOW_SURRENDER +#include "GameLogic/Module/PrisonDockUpdate.h" +#endif +#include "GameLogic/Module/RailedTransportDockUpdate.h" +#include "GameLogic/Module/RailedTransportAIUpdate.h" +#include "GameLogic/Module/RailroadGuideAIUpdate.h" +#include "GameLogic/Module/SlavedUpdate.h" +#include "GameLogic/Module/MobMemberSlavedUpdate.h" +#include "GameLogic/Module/SpecialAbilityUpdate.h" +#include "GameLogic/Module/MissileLauncherBuildingUpdate.h" +#include "GameLogic/Module/StealthDetectorUpdate.h" +#include "GameLogic/Module/StealthUpdate.h" +#include "GameLogic/Module/SpawnPointProductionExitUpdate.h" +#include "GameLogic/Module/SpawnBehavior.h" +#include "GameLogic/Module/SpyVisionUpdate.h" +#include "GameLogic/Module/StickyBombUpdate.h" +#include "GameLogic/Module/FireOCLAfterWeaponCooldownUpdate.h" +#include "GameLogic/Module/HijackerUpdate.h" +#include "GameLogic/Module/StructureCollapseUpdate.h" +#include "GameLogic/Module/StructureToppleUpdate.h" +#include "GameLogic/Module/SupplyCenterDockUpdate.h" +#include "GameLogic/Module/SupplyCenterProductionExitUpdate.h" +#include "GameLogic/Module/SupplyTruckAIUpdate.h" +#include "GameLogic/Module/SupplyWarehouseDockUpdate.h" +#include "GameLogic/Module/ToppleUpdate.h" +#include "GameLogic/Module/TransportAIUpdate.h" +#include "GameLogic/Module/WanderAIUpdate.h" +#include "GameLogic/Module/TeleporterAIUpdate.h" +#include "GameLogic/Module/WaveGuideUpdate.h" +#include "GameLogic/Module/WeaponBonusUpdate.h" +#include "GameLogic/Module/ArmorDamageScalarUpdate.h" +#include "GameLogic/Module/WorkerAIUpdate.h" +#include "GameLogic/Module/PowerPlantUpdate.h" +#include "GameLogic/Module/CheckpointUpdate.h" +#include "GameLogic/Module/EMPUpdate.h" + +// upgrade includes +#include "GameLogic/Module/ActiveShroudUpgrade.h" +#include "GameLogic/Module/ArmorUpgrade.h" +#include "GameLogic/Module/CommandSetUpgrade.h" +#include "GameLogic/Module/GrantScienceUpgrade.h" +#include "GameLogic/Module/PassengersFireUpgrade.h" +#include "GameLogic/Module/LocomotorSetUpgrade.h" +#include "GameLogic/Module/ObjectCreationUpgrade.h" +#include "GameLogic/Module/RadarUpgrade.h" +#include "GameLogic/Module/PowerPlantUpgrade.h" +#include "GameLogic/Module/ReplaceObjectUpgrade.h" +#include "GameLogic/Module/ModelConditionUpgrade.h" +#include "GameLogic/Module/StatusBitsUpgrade.h" +#include "GameLogic/Module/SubObjectsUpgrade.h" +#include "GameLogic/Module/StealthUpgrade.h" +#include "GameLogic/Module/UnpauseSpecialPowerUpgrade.h" +#include "GameLogic/Module/WeaponBonusUpgrade.h" +#include "GameLogic/Module/WeaponSetUpgrade.h" +#include "GameLogic/Module/WeaponBonusUpgrade.h" +#include "GameLogic/Module/CostModifierUpgrade.h" +#include "GameLogic/Module/ProductionTimeModifierUpgrade.h" +#include "GameLogic/Module/UnitProductionBonusUpgrade.h" +#include "GameLogic/Module/ExperienceScalarUpgrade.h" +#include "GameLogic/Module/MaxHealthUpgrade.h" + +// create includes +#include "GameLogic/Module/LockWeaponCreate.h" +#include "GameLogic/Module/SupplyCenterCreate.h" +#include "GameLogic/Module/SupplyWarehouseCreate.h" +#include "GameLogic/Module/GrantUpgradeCreate.h" +#include "GameLogic/Module/PreorderCreate.h" +#include "GameLogic/Module/SpecialPowerCreate.h" +#include "GameLogic/Module/VeterancyGainCreate.h" + +// damage includes +#include "GameLogic/Module/BoneFXDamage.h" +#include "GameLogic/Module/TransitionDamageFX.h" + +// collide includes +#include "GameLogic/Module/FireWeaponCollide.h" +#include "GameLogic/Module/SquishCollide.h" + +#include "GameLogic/Module/ConvertToCarBombCrateCollide.h" +#include "GameLogic/Module/ConvertToHijackedVehicleCrateCollide.h" +#include "GameLogic/Module/HealCrateCollide.h" +#include "GameLogic/Module/MoneyCrateCollide.h" +#include "GameLogic/Module/SabotageCommandCenterCrateCollide.h" +#include "GameLogic/Module/SabotageFakeBuildingCrateCollide.h" +#include "GameLogic/Module/SabotageInternetCenterCrateCollide.h" +#include "GameLogic/Module/SabotageMilitaryFactoryCrateCollide.h" +#include "GameLogic/Module/SabotagePowerPlantCrateCollide.h" +#include "GameLogic/Module/SabotageSuperweaponCrateCollide.h" +#include "GameLogic/Module/SabotageSupplyCenterCrateCollide.h" +#include "GameLogic/Module/SabotageSupplyDropzoneCrateCollide.h" +#include "GameLogic/Module/SalvageCrateCollide.h" +#include "GameLogic/Module/ShroudCrateCollide.h" +#include "GameLogic/Module/UnitCrateCollide.h" +#include "GameLogic/Module/VeterancyCrateCollide.h" + +// body includes +#include "GameLogic/Module/InactiveBody.h" +#include "GameLogic/Module/ActiveBody.h" +#include "GameLogic/Module/HighlanderBody.h" +#include "GameLogic/Module/ImmortalBody.h" +#include "GameLogic/Module/StructureBody.h" +#include "GameLogic/Module/HiveStructureBody.h" +#include "GameLogic/Module/UndeadBody.h" + +// contain includes +// (none) + +// special power modules +#include "GameLogic/Module/CashHackSpecialPower.h" +#include "GameLogic/Module/DefectorSpecialPower.h" +#ifdef ALLOW_DEMORALIZE +#include "GameLogic/Module/DemoralizeSpecialPower.h" +#endif +#include "GameLogic/Module/OCLSpecialPower.h" +#include "GameLogic/Module/SpecialAbility.h" +#include "GameLogic/Module/SpyVisionSpecialPower.h" +#include "GameLogic/Module/CashBountyPower.h" +#include "GameLogic/Module/CleanupAreaPower.h" +#include "GameLogic/Module/FireWeaponPower.h" + +// destroy includes +// (none) + +// client update includes +#include "GameClient/Module/AnimatedParticleSysBoneClientUpdate.h" +#include "GameClient/Module/SwayClientUpdate.h" +#include "GameClient/Module/BeaconClientUpdate.h" + +// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// +ModuleFactory *TheModuleFactory = NULL; ///< the module factory singleton + +// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// + +#ifdef RTS_INTERNAL +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ModuleFactory::ModuleFactory( void ) +{ + m_moduleTemplateMap.clear(); + m_moduleDataList.clear(); + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ModuleFactory::~ModuleFactory( void ) +{ + m_moduleTemplateMap.clear(); + + for (ModuleDataList::iterator i = m_moduleDataList.begin(); i != m_moduleDataList.end(); ++i) + { + const ModuleData* data = *i; + delete data; + } + m_moduleDataList.clear(); + +} + +//------------------------------------------------------------------------------------------------- +/** Initialize the module factory. Any class that needs to be attached + * to objects or drawables as modules needs to add a template + * for that class here */ +//------------------------------------------------------------------------------------------------- +void ModuleFactory::init( void ) +{ + + // behavior modules + addModule( AutoHealBehavior ); + addModule( GrantStealthBehavior ); + addModule( NeutronBlastBehavior ); + addModule( BridgeBehavior ); + addModule( BridgeScaffoldBehavior ); + addModule( BridgeTowerBehavior ); + addModule( CountermeasuresBehavior ); + addModule( DumbProjectileBehavior ); + addModule( FreeFallProjectileBehavior ); + addModule( PhysicsBehavior ); + addModule( InstantDeathBehavior ); + addModule( SlowDeathBehavior ); + addModule( HelicopterSlowDeathBehavior ); + addModule( NeutronMissileSlowDeathBehavior ); + addModule( CaveContain ); + addModule( OpenContain ); + addModule( OverchargeBehavior ); + addModule( HealContain ); + addModule( GarrisonContain ); + addModule( InternetHackContain ); + addModule( TransportContain ); + addModule( RiderChangeContain ); + addModule( RailedTransportContain ); + addModule( MobNexusContain ); + addModule( TunnelContain ); + addModule( OverlordContain ); + addModule( HelixContain ); + addModule( ParachuteContain ); +#ifdef ALLOW_SURRENDER + addModule( POWTruckBehavior ); + addModule( PrisonBehavior ); + addModule( PropagandaCenterBehavior ); +#endif + addModule( PropagandaTowerBehavior ); + addModule( BunkerBusterBehavior ); + addModule( FireWeaponWhenDamagedBehavior ); + addModule( FireWeaponWhenDeadBehavior ); + addModule( DelayedUpgradeBehavior ); + addModule( GenerateMinefieldBehavior ); + addModule( ParkingPlaceBehavior ); + addModule( FlightDeckBehavior ); + addModule( PoisonedBehavior ); + addModule( RebuildHoleBehavior ); + addModule( SupplyWarehouseCripplingBehavior ); + addModule( TechBuildingBehavior ); + addModule( MinefieldBehavior ); + addModule( BattleBusSlowDeathBehavior ); + addModule( JetSlowDeathBehavior ); + addModule( RailroadBehavior ); + addModule( SpawnBehavior ); + + // die modules + addModule( DestroyDie ); + addModule( FXListDie ); + addModule( CrushDie ); + addModule( DamDie ); + addModule( CreateCrateDie ); + addModule( CreateObjectDie ); + addModule( EjectPilotDie ); + addModule( SpecialPowerCompletionDie ); + addModule( RebuildHoleExposeDie ); + addModule( UpgradeDie ); + addModule( KeepObjectDie ); + + // update modules + addModule( AssistedTargetingUpdate ); + addModule( AutoFindHealingUpdate ); + addModule( BaseRegenerateUpdate ); + addModule( StealthDetectorUpdate ); + addModule( StealthUpdate ); + addModule( DeletionUpdate ); + addModule( SmartBombTargetHomingUpdate ); + addModule( DynamicShroudClearingRangeUpdate ); + addModule( DeployStyleAIUpdate ); + addModule( AssaultTransportAIUpdate ); + addModule( HordeUpdate ); + addModule( ToppleUpdate ); + addModule( EnemyNearUpdate ); + addModule( LifetimeUpdate ); + addModule( RadiusDecalUpdate ); + addModule( RadiusDecalBehavior ); + addModule( EMPUpdate ); + addModule( LeafletDropBehavior ); + addModule( AutoDepositUpdate ); + addModule( WeaponBonusUpdate ); + addModule( ArmorDamageScalarUpdate ); + addModule( MissileAIUpdate ); + addModule( NeutronMissileUpdate ); + addModule( FireSpreadUpdate ); + addModule( FireWeaponUpdate ); + addModule( FlammableUpdate ); + addModule( FloatUpdate ); + addModule( TensileFormationUpdate ); + addModule( HeightDieUpdate ); + addModule( ScatterShotUpdate ); + addModule( ChinookAIUpdate ); + addModule( JetAIUpdate ); + addModule( AIUpdateInterface ); + addModule( SupplyTruckAIUpdate ); + addModule( DeliverPayloadAIUpdate ); + addModule( HackInternetAIUpdate ); + addModule( DynamicGeometryInfoUpdate ); + addModule( FirestormDynamicGeometryInfoUpdate ); + addModule( LaserUpdate ); + addModule( PointDefenseLaserUpdate ); + addModule( CleanupHazardUpdate ); + addModule( CommandButtonHuntUpdate ); + addModule( PilotFindVehicleUpdate ); + addModule( DemoTrapUpdate ); + addModule( ParticleUplinkCannonUpdate ); + addModule( SpectreGunshipUpdate ); + addModule( SpectreGunshipDeploymentUpdate ); + addModule( BaikonurLaunchPower ); + addModule( BattlePlanUpdate ); + addModule( ProjectileStreamUpdate ); + addModule( QueueProductionExitUpdate ); + addModule( RepairDockUpdate ); +#ifdef ALLOW_SURRENDER + addModule( PrisonDockUpdate ); +#endif + addModule( RailedTransportDockUpdate ); + addModule( DefaultProductionExitUpdate ); + addModule( SpawnPointProductionExitUpdate ); + addModule( SpyVisionUpdate ); + addModule( SlavedUpdate ); + addModule( MobMemberSlavedUpdate ); + addModule( OCLUpdate ); + addModule( SpecialAbilityUpdate ); + addModule( MissileLauncherBuildingUpdate ); + addModule( SupplyCenterProductionExitUpdate ); + addModule( SupplyCenterDockUpdate ); + addModule( SupplyWarehouseDockUpdate ); + addModule( DozerAIUpdate ); +#ifdef ALLOW_SURRENDER + addModule( POWTruckAIUpdate ); +#endif + addModule( RailedTransportAIUpdate ); + addModule( ProductionUpdate ); + addModule( ProneUpdate ); + addModule( StickyBombUpdate ); + addModule( FireOCLAfterWeaponCooldownUpdate ); + addModule( HijackerUpdate ); + addModule( StructureToppleUpdate ); + addModule( StructureCollapseUpdate ); + addModule( BoneFXUpdate ); + addModule( RadarUpdate ); + addModule( AnimationSteeringUpdate ); + addModule( TransportAIUpdate ); + addModule( WanderAIUpdate ); + addModule( TeleporterAIUpdate ); + addModule( WaveGuideUpdate ); + addModule( WorkerAIUpdate ); + addModule( PowerPlantUpdate ); + addModule( CheckpointUpdate ); + + // upgrade modules + addModule( CostModifierUpgrade ); + addModule( ProductionTimeModifierUpgrade ); + addModule( UnitProductionBonusUpgrade ); + addModule( ActiveShroudUpgrade ); + addModule( ArmorUpgrade ); + addModule( CommandSetUpgrade ); + addModule( GrantScienceUpgrade ); + addModule( PassengersFireUpgrade ); + addModule( StatusBitsUpgrade ); + addModule( SubObjectsUpgrade ); + addModule( StealthUpgrade ); + addModule( RadarUpgrade ); + addModule( PowerPlantUpgrade ); + addModule( LocomotorSetUpgrade ); + addModule( ObjectCreationUpgrade ); + addModule( ReplaceObjectUpgrade ); + addModule( ModelConditionUpgrade ); + addModule( UnpauseSpecialPowerUpgrade ); + addModule( WeaponBonusUpgrade ); + addModule( WeaponSetUpgrade ); + addModule( WeaponBonusUpgrade ); + addModule( ExperienceScalarUpgrade ); + addModule( MaxHealthUpgrade ); + + // create modules + addModule( LockWeaponCreate ); + addModule( PreorderCreate ); + addModule( SupplyCenterCreate ); + addModule( SupplyWarehouseCreate ); + addModule( SpecialPowerCreate ); + addModule( GrantUpgradeCreate ); + addModule( VeterancyGainCreate ); + + // damage modules + addModule( BoneFXDamage ); + addModule( TransitionDamageFX ); + + // collide modules + addModule( FireWeaponCollide ); + addModule( SquishCollide ); + + addModule( HealCrateCollide ); + addModule( MoneyCrateCollide ); + addModule( ShroudCrateCollide ); + addModule( UnitCrateCollide ); + addModule( VeterancyCrateCollide ); + addModule( ConvertToCarBombCrateCollide ); + addModule( ConvertToHijackedVehicleCrateCollide ); + addModule( SabotageCommandCenterCrateCollide ); + addModule( SabotageFakeBuildingCrateCollide ); + addModule( SabotageInternetCenterCrateCollide ); + addModule( SabotageMilitaryFactoryCrateCollide ); + addModule( SabotagePowerPlantCrateCollide ); + addModule( SabotageSuperweaponCrateCollide ); + addModule( SabotageSupplyCenterCrateCollide ); + addModule( SabotageSupplyDropzoneCrateCollide ); + addModule( SalvageCrateCollide ); + + // body modules + addModule( InactiveBody ); + addModule( ActiveBody ); + addModule( HighlanderBody ); + addModule( ImmortalBody ); + addModule( StructureBody ); + addModule( HiveStructureBody ); + addModule( UndeadBody ); + + // contain modules + // (none) + + // special power modules + addModule( CashHackSpecialPower ); + addModule( DefectorSpecialPower ); +#ifdef ALLOW_DEMORALIZE + addModule( DemoralizeSpecialPower ); +#endif + addModule( OCLSpecialPower ); + addModule( FireWeaponPower ); + addModule( SpecialAbility ); + addModule( SpyVisionSpecialPower ); + addModule( CashBountyPower ); + addModule( CleanupAreaPower ); + + // destroy modules + // (none) + + // client update modules + addModule( AnimatedParticleSysBoneClientUpdate ); + addModule( SwayClientUpdate ); + addModule( BeaconClientUpdate ); + +} // end init + +//------------------------------------------------------------------------------------------------- +Int ModuleFactory::findModuleInterfaceMask(const AsciiString& name, ModuleType type) +{ + if (name.isEmpty()) + return 0; + + const ModuleTemplate* moduleTemplate = findModuleTemplate(name, type); + if (moduleTemplate) + { + return moduleTemplate->m_whichInterfaces; + } + + return 0; +} + +//------------------------------------------------------------------------------------------------- +ModuleData* ModuleFactory::newModuleDataFromINI(INI* ini, const AsciiString& name, ModuleType type, + const AsciiString& moduleTag) +{ + if (name.isEmpty()) + return NULL; + + const ModuleTemplate* moduleTemplate = findModuleTemplate(name, type); + if (moduleTemplate) + { + ModuleData* md = (*moduleTemplate->m_createDataProc)(ini); + md->setModuleTagNameKey( NAMEKEY( moduleTag ) ); + m_moduleDataList.push_back(md); + return md; + } + + return NULL; +} + +// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +/*static*/ NameKeyType ModuleFactory::makeDecoratedNameKey(const AsciiString& name, ModuleType type) +{ + char tmp[256]; + tmp[0] = '0' + (int)type; + strcpy(&tmp[1], name.str()); + return TheNameKeyGenerator->nameToKey(tmp); +} + +//------------------------------------------------------------------------------------------------- +const ModuleFactory::ModuleTemplate* ModuleFactory::findModuleTemplate(const AsciiString& name, ModuleType type) +{ + NameKeyType namekey = makeDecoratedNameKey(name, type); + + ModuleTemplateMap::const_iterator it = m_moduleTemplateMap.find(namekey); + if (it == m_moduleTemplateMap.end()) + { + DEBUG_CRASH(( "Module name '%s' not found\n", name.str() )); + return NULL; + } + else + { + return &(*it).second; + } +} + +//------------------------------------------------------------------------------------------------- +/** Allocate a new acton class istance given the name */ +//------------------------------------------------------------------------------------------------- +Module *ModuleFactory::newModule( Thing *thing, const AsciiString& name, const ModuleData* moduleData, ModuleType type ) +{ + // sanity + if( name.isEmpty() ) + { + DEBUG_CRASH(("attempting to create module with empty name\n")); + return NULL; + } + const ModuleTemplate* mt = findModuleTemplate(name, type); + if (mt) + { + Module* mod = (*mt->m_createProc)( thing, moduleData ); + +#ifdef DEBUG_CRASHING + if (type == MODULETYPE_BEHAVIOR) + { + BehaviorModule* bm = (BehaviorModule*)mod; + + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_BODY)) != 0) == (bm->getBody() != NULL), + ("getInterfaceMask bad for MODULE_BODY (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_COLLIDE)) != 0) == (bm->getCollide() != NULL), + ("getInterfaceMask bad for MODULE_COLLIDE (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_CONTAIN)) != 0) == (bm->getContain() != NULL), + ("getInterfaceMask bad for MODULE_CONTAIN (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_CREATE)) != 0) == (bm->getCreate() != NULL), + ("getInterfaceMask bad for MODULE_CREATE (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_DAMAGE)) != 0) == (bm->getDamage() != NULL), + ("getInterfaceMask bad for MODULE_DAMAGE (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_DESTROY)) != 0) == (bm->getDestroy() != NULL), + ("getInterfaceMask bad for MODULE_DESTROY (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_DIE)) != 0) == (bm->getDie() != NULL), + ("getInterfaceMask bad for MODULE_DIE (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_SPECIAL_POWER)) != 0) == (bm->getSpecialPower() != NULL), + ("getInterfaceMask bad for MODULE_SPECIAL_POWER (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_UPDATE)) != 0) == (bm->getUpdate() != NULL), + ("getInterfaceMask bad for MODULE_UPDATE (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_UPGRADE)) != 0) == (bm->getUpgrade() != NULL), + ("getInterfaceMask bad for MODULE_UPGRADE (%s)\n",name.str())); + } +#endif + + return mod; + } + + return NULL; + +} // end newModule + +//------------------------------------------------------------------------------------------------- +/** Add a module template to our list of templates */ +//------------------------------------------------------------------------------------------------- +void ModuleFactory::addModuleInternal( NewModuleProc proc, NewModuleDataProc dataproc, ModuleType type, const AsciiString& name, Int whichIntf ) +{ + NameKeyType namekey = makeDecoratedNameKey(name, type); + ModuleTemplate& mtm = m_moduleTemplateMap[namekey]; // this creates it if it does not exist already + mtm.m_createProc = proc; + mtm.m_createDataProc = dataproc; + mtm.m_whichInterfaces = whichIntf; +} + +//------------------------------------------------------------------------------------------------- +void ModuleFactory::crc( Xfer *xfer ) +{ + for (ModuleDataList::iterator mdIt = m_moduleDataList.begin(); mdIt != m_moduleDataList.end(); ++mdIt) + { + ((ModuleData *)(*mdIt))->crc(xfer); + } +} + +//------------------------------------------------------------------------------------------------- +void ModuleFactory::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + for (ModuleDataList::iterator mdIt = m_moduleDataList.begin(); mdIt != m_moduleDataList.end(); ++mdIt) + { + ((ModuleData *)(*mdIt))->xfer(xfer); + } +} + +//------------------------------------------------------------------------------------------------- +void ModuleFactory::loadPostProcess( void ) +{ +} diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DelayedUpgradeBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DelayedUpgradeBehavior.cpp new file mode 100644 index 00000000000..f71e31f8290 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DelayedUpgradeBehavior.cpp @@ -0,0 +1,248 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: DelayedUpgradeBehavior.cpp /////////////////////////////////////////////////////////////////////// +// Author: +// Desc: +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + + +//#include "Common/Thing.h" +//#include "Common/ThingTemplate.h" +#include "Common/INI.h" +//#include "Common/RandomValue.h" +#include "Common/Xfer.h" +#include "Common/Player.h" +//#include "GameClient/Drawable.h" +//#include "GameClient/FXList.h" +//#include "GameClient/InGameUI.h" +#include "GameLogic/GameLogic.h" +//#include "GameLogic/Module/BodyModule.h" +#include "GameLogic/Module/DelayedUpgradeBehavior.h" +#include "GameLogic/Module/AIUpdate.h" +#include "GameLogic/Object.h" +//#include "GameLogic/ObjectCreationList.h" +#include "GameLogic/Weapon.h" +//#include "GameClient/Drawable.h" + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +DelayedUpgradeBehavior::DelayedUpgradeBehavior(Thing* thing, const ModuleData* moduleData) : UpdateModule(thing, moduleData) +{ + DEBUG_LOG(("DelayedUpgradeBehavior::INIT\n")); + m_triggerCompleted = FALSE; + m_triggerFrame = 0; + //m_shotsLeft = 0; + + if (getDelayedUpgradeBehaviorModuleData()->m_initiallyActive) + { + giveSelfUpgrade(); + } + else { + setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +DelayedUpgradeBehavior::~DelayedUpgradeBehavior(void) +{ +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void DelayedUpgradeBehavior::upgradeImplementation(void) +{ + DEBUG_LOG(("DelayedUpgradeBehavior::upgradeImplementation() 1\n")); + + const DelayedUpgradeBehaviorModuleData* d = getDelayedUpgradeBehaviorModuleData(); + + UnsignedInt delay = d->m_triggerDelay; + // Trigger after time: + if (delay > 0) { + m_triggerFrame = TheGameLogic->getFrame() + delay; + } + + //if (d->m_triggerNumShots > 0) { + // m_shotsLeft = d->m_triggerNumShots; + // setWakeFrame(getObject(), UPDATE_SLEEP_NONE); + // return; + //} + + if (delay > 0) { + + DEBUG_LOG(("DelayedUpgradeBehavior::upgradeImplementation(): trigger_frame = %d\n", m_triggerFrame)); + + setWakeFrame(getObject(), UPDATE_SLEEP(d->m_triggerDelay)); + return; + } + + DEBUG_LOG(("DelayedUpgradeBehavior::upgradeImplementation(): We have no trigger!!!\n")); + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +UpdateSleepTime DelayedUpgradeBehavior::update(void) +{ + if (m_triggerCompleted) { + DEBUG_LOG(("DelayedUpgradeBehavior::Update(): Already triggered. We should not be awake!!!\n")); + return UPDATE_SLEEP_FOREVER; + } + + if (!isUpgradeActive()) { + DEBUG_LOG(("DelayedUpgradeBehavior::Update(): Upgrade not applied. We should not be awake!!!\n")); + return UPDATE_SLEEP_FOREVER; + } + + const DelayedUpgradeBehaviorModuleData* d = getDelayedUpgradeBehaviorModuleData(); + + if (d->m_triggerDelay > 0) { + UnsignedInt now = TheGameLogic->getFrame(); + if (now >= m_triggerFrame) { + DEBUG_LOG(("DelayedUpgradeBehavior::update(): Trigger Frame reached.\n")); + triggerUpgrade(); + return UPDATE_SLEEP_FOREVER; + } + } + + //if (d->m_triggerNumShots > 0) { + + // //checkShots(); + // if (m_shotsLeft >= 0) { + // triggerUpgrade(); + // return UPDATE_SLEEP_FOREVER; + // } + //} + + return UPDATE_SLEEP_NONE; +} + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void DelayedUpgradeBehavior::triggerUpgrade(void) +{ + + const DelayedUpgradeBehaviorModuleData* d = getDelayedUpgradeBehaviorModuleData(); + const UpgradeTemplate* upgradeTemplate = TheUpgradeCenter->findUpgrade(d->m_upgradeToTrigger); + if (!upgradeTemplate) + { + DEBUG_ASSERTCRASH(0, ("DelayedUpgradeBehavior for %s can't find upgrade template %s.", getObject()->getName(), d->m_upgradeToTrigger)); + return; + } + + m_triggerCompleted = TRUE; + + Player* player = getObject()->getControllingPlayer(); + if (upgradeTemplate->getUpgradeType() == UPGRADE_TYPE_PLAYER) + { + // get the player + player->addUpgrade(upgradeTemplate, UPGRADE_STATUS_COMPLETE); + } + else + { + getObject()->giveUpgrade(upgradeTemplate); + } + + player->getAcademyStats()->recordUpgrade(upgradeTemplate, TRUE); + + DEBUG_LOG(("DelayedUpgradeBehavior::triggerUpgrade() Done.\n")); + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool DelayedUpgradeBehavior::resetUpgrade(UpgradeMaskType keyMask) +{ + DEBUG_LOG(("DelayedUpgradeBehavior::resetUpgrade().\n")); + if (UpgradeMux::resetUpgrade(keyMask)) { + m_triggerCompleted = FALSE; + m_triggerFrame = 0; + // m_shotsLeft = 0; + return TRUE; + } + else { + return FALSE; + } +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void DelayedUpgradeBehavior::crc(Xfer* xfer) +{ + + // extend base class + BehaviorModule::crc(xfer); + + // extend upgrade mux + UpgradeMux::upgradeMuxCRC(xfer); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ + // ------------------------------------------------------------------------------------------------ +void DelayedUpgradeBehavior::xfer(Xfer* xfer) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion(&version, currentVersion); + + // extend base class + BehaviorModule::xfer(xfer); + + // extend upgrade mux + UpgradeMux::upgradeMuxXfer(xfer); + + // trigger frame + xfer->xferUnsignedInt(&m_triggerFrame); + + // trigger completed + xfer->xferBool(&m_triggerCompleted); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void DelayedUpgradeBehavior::loadPostProcess(void) +{ + + // extend base class + BehaviorModule::loadPostProcess(); + + // extend upgrade mux + UpgradeMux::upgradeMuxLoadPostProcess(); + +} // end loadPostProcess diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp index 297d5fc1dd6..b935559a4ed 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp @@ -1,2830 +1,2844 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: Locomotor.cpp /////////////////////////////////////////////////////////////////////////////// -// Author: Steven Johnson, Feb 2002 -// Desc: Locomotor descriptions -/////////////////////////////////////////////////////////////////////////////////////////////////// - - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -#define DEFINE_SURFACECATEGORY_NAMES -#define DEFINE_LOCO_Z_NAMES -#define DEFINE_LOCO_APPEARANCE_NAMES - -#include "Common/INI.h" -#include "GameLogic/GameLogic.h" -#include "GameLogic/PartitionManager.h" -#include "GameLogic/Locomotor.h" -#include "GameLogic/Object.h" -#include "GameLogic/AI.h" -#include "GameLogic/AIPathfind.h" -#include "GameLogic/Module/PhysicsUpdate.h" -#include "GameLogic/Module/BodyModule.h" -#include "GameLogic/Module/AIUpdate.h" - -#ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -static const Real DONUT_TIME_DELAY_SECONDS=2.5f; -static const Real DONUT_DISTANCE=4.0*PATHFIND_CELL_SIZE_F; - - -#define MAX_BRAKING_FACTOR 5.0f -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -LocomotorStore *TheLocomotorStore = NULL; ///< the Locomotor store definition - -const Real BIGNUM = 99999.0f; - -static const char *TheLocomotorPriorityNames[] = -{ - "MOVES_BACK", - "MOVES_MIDDLE", - "MOVES_FRONT", - - NULL -}; - - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------------- -static Real calcSlowDownDist(Real curSpeed, Real desiredSpeed, Real maxBraking) -{ - Real delta = curSpeed - desiredSpeed; - if (delta <= 0) - return 0.0f; - - Real dist = (sqr(delta) / fabs(maxBraking)) * 0.5f; - - // use a little fudge so that things can stop "on a dime" more easily... - const Real FUDGE = 1.05f; - return dist * FUDGE; -} - -//----------------------------------------------------------------------------- -inline Bool isNearlyZero(Real a) -{ - const Real TINY_EPSILON = 0.001f; - return fabs(a) < TINY_EPSILON; -} - -//----------------------------------------------------------------------------- -inline Bool isNearly(Real a, Real val) -{ - const Real TINY_EPSILON = 0.001f; - return fabs(a - val) < TINY_EPSILON; -} - -//----------------------------------------------------------------------------- -// return the angle delta (in 3-space) we turned. -static Real tryToRotateVector3D( - Real maxAngle, // if negative, it's a percent (0...1) of the dist to rotate 'em - const Vector3& inCurDir, - const Vector3& inGoalDir, - Vector3& actualDir -) -{ - if (isNearlyZero(maxAngle)) - { - actualDir = inCurDir; - return 0.0f; - } - - Vector3 curDir = inCurDir; - curDir.Normalize(); - - Vector3 goalDir = inGoalDir; - goalDir.Normalize(); - - // dot of two unit vectors is cos of angle between them. - Real cosine = Vector3::Dot_Product(curDir, goalDir); - // bound it in case of numerical error - Real angleBetween = (Real)ACos(clamp(-1.0f, cosine, 1.0f)); - - if (maxAngle < 0) - { - maxAngle = -maxAngle * angleBetween; - if (isNearlyZero(maxAngle)) - { - actualDir = inCurDir; - return 0.0f; - } - } - - if (fabs(angleBetween) <= maxAngle) - { - // close enough - actualDir = goalDir; - } - else - { - // nah, try as much as we can in the right dir. - // we need to rotate around the axis perpendicular to these two vecs. - // but: cross of two vectors is the perpendicular axis! -#ifdef ALLOW_TEMPORARIES - Vector3 objCrossGoal = Vector3::Cross_Product(curDir, goalDir); - objCrossGoal.Normalize(); -#else - Vector3 objCrossGoal; - Vector3::Normalized_Cross_Product(curDir, goalDir, &objCrossGoal); -#endif - - angleBetween = maxAngle; - Matrix3D rotMtx(objCrossGoal, angleBetween); - actualDir = rotMtx.Rotate_Vector(curDir); - } - - return angleBetween; -} - -//------------------------------------------------------------------------------------------------- -static Real tryToOrientInThisDirection3D(Object* obj, Real maxTurnRate, const Vector3& desiredDir) -{ - Vector3 actualDir; - Real relAngle = tryToRotateVector3D(maxTurnRate, obj->getTransformMatrix()->Get_X_Vector(), desiredDir, actualDir); - if (relAngle != 0.0f) - { - Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z); - - Matrix3D newXform; - newXform.buildTransformMatrix( objPos, actualDir ); - - obj->setTransformMatrix( &newXform ); - } - return relAngle; -} - -//------------------------------------------------------------------------------------------------- -inline Real tryToOrientInThisDirection3D(Object* obj, Real maxTurnRate, const Coord3D* dir) -{ - return tryToOrientInThisDirection3D(obj, maxTurnRate, Vector3(dir->x, dir->y, dir->z)); -} - -//----------------------------------------------------------------------------- -static void calcDirectionToApplyThrust( - const Object* obj, - const PhysicsBehavior* physics, - const Coord3D& ingoalPos, - Real maxAccel, - Vector3& goalDir -) -{ - /* - our meta-goal here is to calculate the direction we should apply our motive force - in order to minimize the angle between (our velocity) and (direction towards goalpos). - - this is complicated by the fact that we generally have an intrinsic velocity already, - that must be accounted for, and by the fact that we can only apply force in our - forward-x-direction (with a thrust-angle-range), and (due to limited range) might not - be able to apply the force in the optimal direction! - */ - - // convert to Vector3, to use all its handy stuff - Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z); - Vector3 goalPos(ingoalPos.x, ingoalPos.y, ingoalPos.z); - - Vector3 vecToGoal = goalPos - objPos; - if (isNearlyZero(vecToGoal.Length2())) - { - // goal pos is essentially same as current pos, so just stay the same & return - goalDir = obj->getTransformMatrix()->Get_X_Vector(); - return; - } - - /* - get our cur vel into a useful Vector3 form - */ - Vector3 curVel(physics->getVelocity()->x, physics->getVelocity()->y, physics->getVelocity()->z); - - // add gravity to our vel so that we account for it in our calcs - curVel.Z += TheGlobalData->m_gravity; - - Bool foundSolution = false; - Real distToGoalSqr = vecToGoal.Length2(); - Real distToGoal = sqrt(distToGoalSqr); - Real curVelMagSqr = curVel.Length2(); - Real curVelMag = sqrt(curVelMagSqr); - Real maxAccelSqr = sqr(maxAccel); - - Real denom = curVelMagSqr - maxAccelSqr; - if (!isNearlyZero(denom)) - { - // solve the (greatly simplified) quadratic... - Real t = (distToGoal * (curVelMag + maxAccel)) / denom; - Real t2 = (distToGoal * (curVelMag - maxAccel)) / denom; - if (t >= 0 || t2 >= 0) - { - // choose the smallest positive t. - if (t < 0 || (t2 >= 0 && t2 < t)) - t = t2; - - // plug it in. - if (!isNearlyZero(t)) - { - goalDir.X = (vecToGoal.X / t) - curVel.X; - goalDir.Y = (vecToGoal.Y / t) - curVel.Y; - goalDir.Z = (vecToGoal.Z / t) - curVel.Z; - goalDir.Normalize(); - foundSolution = true; - } - } - } - if (!foundSolution) - { - // Doh... no (useful) solution. revert to dumb. - goalDir = vecToGoal; - goalDir.Normalize(); - } - -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------------- -LocomotorTemplate::LocomotorTemplate() -{ - // these values mean "make the same as undamaged if not explicitly specified" - m_maxSpeedDamaged = -1.0f; - m_maxTurnRateDamaged = -1.0f; - m_accelerationDamaged = -1.0f; - m_liftDamaged = -1.0f; - - m_surfaces = 0; - m_maxSpeed = 0.0f; - m_maxTurnRate = 0.0f; - m_acceleration = 0.0f; - m_lift = 0.0f; - m_braking = BIGNUM; - m_minSpeed = 0.0f; - m_minTurnSpeed = BIGNUM; - m_behaviorZ = Z_NO_Z_MOTIVE_FORCE; - m_appearance = LOCO_OTHER; - m_movePriority = LOCO_MOVES_MIDDLE; - m_preferredHeight = 0; - m_preferredHeightDamping = 1.0f; - m_circlingRadius = 0; - - m_maxThrustAngle = 0; - m_speedLimitZ = 999999.0f; - m_extra2DFriction = 0.0f; - - m_accelPitchLimit = 0; - m_decelPitchLimit = 0; - m_bounceKick = 0; - -// m_pitchStiffness = 0; -// m_rollStiffness = 0; -// m_pitchDamping = 0; -// m_rollDamping = 0; -// it's highly unlikely you want zero for the defaults for stiffness and damping... (srj) -// for stiffness: stiffness of the "springs" in the suspension 0 = no stiffness, 1 = totally stiff (huh huh, he said "stiff") -// for damping: 0=perfect spring, bounces forever. 1=glued to terrain. - m_pitchStiffness = 0.1f; - m_rollStiffness = 0.1f; - m_pitchDamping = 0.9f; - m_rollDamping = 0.9f; - m_forwardVelCoef = 0; - m_pitchByZVelCoef = 0; - m_thrustRoll = 0.0f; - m_wobbleRate = 0.0f; - m_minWobble = 0.0f; - m_maxWobble = 0.0f; - m_lateralVelCoef = 0; - m_forwardAccelCoef = 0; - m_lateralAccelCoef = 0; - m_uniformAxialDamping = 1.0f; - m_turnPivotOffset = 0; - m_apply2DFrictionWhenAirborne = false; - m_downhillOnly = false; - m_allowMotiveForceWhileAirborne = false; - m_locomotorWorksWhenDead = false; - m_airborneTargetingHeight = INT_MAX; - m_stickToGround = false; - m_canMoveBackward = false; - m_hasSuspension = false; - m_wheelTurnAngle = 0; - m_maximumWheelExtension = 0; - m_maximumWheelCompression = 0; - m_closeEnoughDist = 1.0f; - m_isCloseEnoughDist3D = FALSE; - m_ultraAccurateSlideIntoPlaceFactor = 0.0f; - - m_wanderWidthFactor = 0.0f; - m_wanderLengthFactor = 1.0f; - m_wanderAboutPointRadius = 0.0f; - - m_rudderCorrectionDegree = 0.0f; - m_rudderCorrectionRate = 0.0f; - m_elevatorCorrectionDegree = 0.0f; - m_elevatorCorrectionRate = 0.0f; - -} - -//------------------------------------------------------------------------------------------------- -LocomotorTemplate::~LocomotorTemplate() -{ - -} - -//------------------------------------------------------------------------------------------------- -void LocomotorTemplate::validate() -{ - // this is ok; parachutes need it! - //DEBUG_ASSERTCRASH(m_lift == 0.0f || m_lift > fabs(TheGlobalData->m_gravity), ("Lift is too low to counteract gravity!")); - //DEBUG_ASSERTCRASH(m_liftDamaged == 0.0f || m_liftDamaged > fabs(TheGlobalData->m_gravity), ("LiftDamaged is too low to counteract gravity!")); - //DEBUG_ASSERTCRASH(m_preferredHeight == 0.0f || (m_behaviorZ == Z_SURFACE_RELATIVE_HEIGHT || m_behaviorZ == Z_ABSOLUTE_HEIGHT || m_appearance == LOCO_THRUST), - // ("You must use Z_SURFACE_RELATIVE_HEIGHT or Z_ABSOLUTE_HEIGHT (or THRUST) to use preferredHeight")); - - // for 'damaged' stuff that was omitted, set 'em to be the same as 'undamaged'... - if (m_maxSpeedDamaged < 0.0f) - m_maxSpeedDamaged = m_maxSpeed; - - if (m_maxTurnRateDamaged < 0.0f) - m_maxTurnRateDamaged = m_maxTurnRate; - - if (m_accelerationDamaged < 0.0f) - m_accelerationDamaged = m_acceleration; - - if (m_liftDamaged < 0.0f) - m_liftDamaged = m_lift; - - if (m_appearance == LOCO_WINGS) - { - if (m_minSpeed <= 0.0f) - { - DEBUG_CRASH(("WINGS should always have positive minSpeeds (otherwise, they hover)")); - m_minSpeed = 0.01f; - } - if (m_minTurnSpeed <= 0.0f) - { - DEBUG_CRASH(("WINGS should always have positive minTurnSpeed")); - m_minTurnSpeed = 0.01f; - } - } - - if (m_appearance == LOCO_THRUST) - { - if (m_behaviorZ != Z_NO_Z_MOTIVE_FORCE || - m_lift != 0.0f || - m_liftDamaged != 0.0f) - { - DEBUG_CRASH(("THRUST locos may not use ZAxisBehavior or lift!\n")); - throw INI_INVALID_DATA; - } - if (m_maxSpeed <= 0.0f) - { - // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing - DEBUG_LOG(("THRUST locos may not have zero m_maxSpeed; healing...\n")); - m_maxSpeed = 0.01f; - } - if (m_maxSpeedDamaged <= 0.0f) - { - // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing - DEBUG_LOG(("THRUST locos may not have zero m_maxSpeedDamaged; healing...\n")); - m_maxSpeedDamaged = 0.01f; - } - if (m_minSpeed <= 0.0f) - { - // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing - DEBUG_LOG(("THRUST locos may not have zero m_minSpeed; healing...\n")); - m_minSpeed = 0.01f; - } - } -} - -//------------------------------------------------------------------------------------------------- -static void parseFrictionPerSec( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - Real fricPerSec = INI::scanReal(ini->getNextToken()); - Real fricPerFrame = fricPerSec * SECONDS_PER_LOGICFRAME_REAL; - *(Real *)store = fricPerFrame; -} - -//------------------------------------------------------------------------------------------------- -const FieldParse* LocomotorTemplate::getFieldParse() const -{ - static const FieldParse TheFieldParse[] = - { - { "Surfaces", INI::parseBitString32, TheLocomotorSurfaceTypeNames, offsetof(LocomotorTemplate, m_surfaces) }, - { "Speed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_maxSpeed) }, - { "SpeedDamaged", INI::parseVelocityReal, NULL, offsetof( LocomotorTemplate, m_maxSpeedDamaged ) }, - { "TurnRate", INI::parseAngularVelocityReal, NULL, offsetof(LocomotorTemplate, m_maxTurnRate) }, - { "TurnRateDamaged", INI::parseAngularVelocityReal, NULL, offsetof( LocomotorTemplate, m_maxTurnRateDamaged ) }, - { "Acceleration", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_acceleration) }, - { "AccelerationDamaged", INI::parseAccelerationReal, NULL, offsetof( LocomotorTemplate, m_accelerationDamaged ) }, - { "Lift", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_lift) }, - { "LiftDamaged", INI::parseAccelerationReal, NULL, offsetof( LocomotorTemplate, m_liftDamaged ) }, - { "Braking", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_braking) }, - { "MinSpeed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_minSpeed) }, - { "MinTurnSpeed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_minTurnSpeed) }, - { "PreferredHeight", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_preferredHeight) }, - { "PreferredHeightDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_preferredHeightDamping) }, - { "CirclingRadius", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_circlingRadius) }, - { "Extra2DFriction", parseFrictionPerSec, NULL, offsetof(LocomotorTemplate, m_extra2DFriction) }, - { "SpeedLimitZ", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_speedLimitZ) }, - { "MaxThrustAngle", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_maxThrustAngle) }, // yes, angle, not angular-vel - { "ZAxisBehavior", INI::parseIndexList, TheLocomotorBehaviorZNames, offsetof(LocomotorTemplate, m_behaviorZ) }, - { "Appearance", INI::parseIndexList, TheLocomotorAppearanceNames, offsetof(LocomotorTemplate, m_appearance) }, \ - { "GroupMovementPriority", INI::parseIndexList, TheLocomotorPriorityNames, offsetof(LocomotorTemplate, m_movePriority) }, \ - - { "AccelerationPitchLimit", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_accelPitchLimit) }, - { "DecelerationPitchLimit", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_decelPitchLimit) }, - { "BounceAmount", INI::parseAngularVelocityReal, NULL, offsetof(LocomotorTemplate, m_bounceKick) }, - { "PitchStiffness", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchStiffness) }, - { "RollStiffness", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rollStiffness) }, - { "PitchDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchDamping) }, - { "RollDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rollDamping) }, - { "ThrustRoll", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_thrustRoll) }, - { "ThrustWobbleRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wobbleRate) }, - { "ThrustMinWobble", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_minWobble) }, - { "ThrustMaxWobble", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maxWobble) }, - { "PitchInDirectionOfZVelFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchByZVelCoef) }, - { "ForwardVelocityPitchFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_forwardVelCoef) }, - { "LateralVelocityRollFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_lateralVelCoef) }, - { "ForwardAccelerationPitchFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_forwardAccelCoef) }, - { "LateralAccelerationRollFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_lateralAccelCoef) }, - { "UniformAxialDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_uniformAxialDamping) }, - { "TurnPivotOffset", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_turnPivotOffset) }, - { "Apply2DFrictionWhenAirborne", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_apply2DFrictionWhenAirborne) }, - { "DownhillOnly", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_downhillOnly) }, - { "AllowAirborneMotiveForce", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_allowMotiveForceWhileAirborne) }, - { "LocomotorWorksWhenDead", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_locomotorWorksWhenDead) }, - { "AirborneTargetingHeight", INI::parseInt, NULL, offsetof( LocomotorTemplate, m_airborneTargetingHeight ) }, - { "StickToGround", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_stickToGround) }, - { "CanMoveBackwards", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_canMoveBackward) }, - { "HasSuspension", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_hasSuspension) }, - { "FrontWheelTurnAngle", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_wheelTurnAngle) }, - { "MaximumWheelExtension", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maximumWheelExtension) }, - { "MaximumWheelCompression", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maximumWheelCompression) }, - { "CloseEnoughDist", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_closeEnoughDist) }, - { "CloseEnoughDist3D", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_isCloseEnoughDist3D) }, - { "SlideIntoPlaceTime", INI::parseDurationReal, NULL, offsetof(LocomotorTemplate, m_ultraAccurateSlideIntoPlaceFactor) }, - - { "WanderWidthFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderWidthFactor) }, - { "WanderLengthFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderLengthFactor) }, - { "WanderAboutPointRadius", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderAboutPointRadius) }, - - { "RudderCorrectionDegree", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rudderCorrectionDegree) }, - { "RudderCorrectionRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rudderCorrectionRate) }, - { "ElevatorCorrectionDegree", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_elevatorCorrectionDegree) }, - { "ElevatorCorrectionRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_elevatorCorrectionRate) }, - { NULL, NULL, NULL, 0 } // keep this last - - }; - return TheFieldParse; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -LocomotorStore::LocomotorStore() -{ -} - -//------------------------------------------------------------------------------------------------- -LocomotorStore::~LocomotorStore() -{ - // delete all the templates, then clear out the table. - LocomotorTemplateMap::iterator it; - for (it = m_locomotorTemplates.begin(); it != m_locomotorTemplates.end(); ++it) { - it->second->deleteInstance(); - } - - m_locomotorTemplates.clear(); -} - -//------------------------------------------------------------------------------------------------- -LocomotorTemplate* LocomotorStore::findLocomotorTemplate(NameKeyType namekey) -{ - if (namekey == NAMEKEY_INVALID) - return NULL; - - LocomotorTemplateMap::iterator it = m_locomotorTemplates.find(namekey); - if (it == m_locomotorTemplates.end()) - return NULL; - else - return (*it).second; -} - -//------------------------------------------------------------------------------------------------- -const LocomotorTemplate* LocomotorStore::findLocomotorTemplate(NameKeyType namekey) const -{ - if (namekey == NAMEKEY_INVALID) - return NULL; - - LocomotorTemplateMap::const_iterator it = m_locomotorTemplates.find(namekey); - if (it == m_locomotorTemplates.end()) - { - return NULL; - } - else - { - return (*it).second; - } -} - -//------------------------------------------------------------------------------------------------- -void LocomotorStore::update() -{ -} - -//------------------------------------------------------------------------------------------------- -void LocomotorStore::reset() -{ - // cleanup overrides. - LocomotorTemplateMap::iterator it; - for (it = m_locomotorTemplates.begin(); it != m_locomotorTemplates.end(); ) { - Overridable *locoTemp = it->second->deleteOverrides(); - if (!locoTemp) - { - m_locomotorTemplates.erase(it); - } - else - { - ++it; - } - } -} - -//------------------------------------------------------------------------------------------------- -LocomotorTemplate *LocomotorStore::newOverride( LocomotorTemplate *locoTemplate ) -{ - if (locoTemplate == NULL) - return NULL; - - // allocate new template - LocomotorTemplate *newTemplate = newInstance(LocomotorTemplate); - - // copy data from final override to 'newTemplate' as a set of initial default values - *newTemplate = *locoTemplate; - locoTemplate->setNextOverride(newTemplate); - - newTemplate->markAsOverride(); - - // return the newly created override for us to set values with etc - return newTemplate; - -} // end newOverride - -//------------------------------------------------------------------------------------------------- -/*static*/ void LocomotorStore::parseLocomotorTemplateDefinition(INI* ini) -{ - if (!TheLocomotorStore) - throw INI_INVALID_DATA; - - Bool isOverride = false; - // read the Locomotor name - const char* token = ini->getNextToken(); - NameKeyType namekey = NAMEKEY(token); - - LocomotorTemplate *loco = TheLocomotorStore->findLocomotorTemplate(namekey); - if (loco) { - if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES) { - loco = TheLocomotorStore->newOverride((LocomotorTemplate*) loco->friend_getFinalOverride()); - } - isOverride = true; - } else { - loco = newInstance(LocomotorTemplate); - if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES) { - loco->markAsOverride(); - } - } - - loco->friend_setName(token); - ini->initFromINI(loco, loco->getFieldParse()); - loco->validate(); - - // if this is an override, then we want the pointer on the existing named locomotor to point us - // to the override, so don't add it to the map. - if (!isOverride) - TheLocomotorStore->m_locomotorTemplates[namekey] = loco; -} - -//------------------------------------------------------------------------------------------------- -/*static*/ void INI::parseLocomotorTemplateDefinition( INI* ini ) -{ - LocomotorStore::parseLocomotorTemplateDefinition(ini); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- -Locomotor::Locomotor(const LocomotorTemplate* tmpl) -{ - m_template = tmpl; - m_brakingFactor = 1.0f; - m_maxLift = BIGNUM; - m_maxSpeed = BIGNUM; - m_maxAccel = BIGNUM; - m_maxBraking = BIGNUM; - m_maxTurnRate = BIGNUM; - m_flags = 0; - m_closeEnoughDist = m_template->m_closeEnoughDist; - setFlag(IS_CLOSE_ENOUGH_DIST_3D, m_template->m_isCloseEnoughDist3D); -#ifdef CIRCLE_FOR_LANDING - m_circleThresh = 0.0f; -#endif - m_preferredHeight = m_template->m_preferredHeight; - m_preferredHeightDamping = m_template->m_preferredHeightDamping; - - m_angleOffset = GameLogicRandomValueReal(-PI/6, PI/6); - m_offsetIncrement = (PI/40) * (GameLogicRandomValueReal(0.8f, 1.2f)/m_template->m_wanderLengthFactor); - setFlag(OFFSET_INCREASING, GameLogicRandomValue(0,1)); - m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; -} - -//------------------------------------------------------------------------------------------------- -Locomotor::Locomotor(const Locomotor& that) -{ - //Added By Sadullah Nader - //Initializations - m_angleOffset = 0.0f; - m_maintainPos.zero(); - - // - - m_template = that.m_template; - m_brakingFactor = that.m_brakingFactor; - m_maxLift = that.m_maxLift; - m_maxSpeed = that.m_maxSpeed; - m_maxAccel = that.m_maxAccel; - m_maxBraking = that.m_maxBraking; - m_maxTurnRate = that.m_maxTurnRate; - m_flags = that.m_flags; - m_closeEnoughDist = that.m_closeEnoughDist; -#ifdef CIRCLE_FOR_LANDING - m_circleThresh = that.m_circleThresh; -#endif - m_preferredHeight = that.m_preferredHeight; - m_preferredHeightDamping = that.m_preferredHeightDamping; - m_angleOffset = that.m_angleOffset; - m_offsetIncrement = that.m_offsetIncrement; -} - -//------------------------------------------------------------------------------------------------- -Locomotor& Locomotor::operator=(const Locomotor& that) -{ - if (this != &that) - { - m_template = that.m_template; - m_brakingFactor = that.m_brakingFactor; - m_maxLift = that.m_maxLift; - m_maxSpeed = that.m_maxSpeed; - m_maxAccel = that.m_maxAccel; - m_maxBraking = that.m_maxBraking; - m_maxTurnRate = that.m_maxTurnRate; - m_flags = that.m_flags; - m_closeEnoughDist = that.m_closeEnoughDist; -#ifdef CIRCLE_FOR_LANDING - m_circleThresh = that.m_circleThresh; -#endif - m_preferredHeight = that.m_preferredHeight; - m_preferredHeightDamping = that.m_preferredHeightDamping; - } - return *this; -} - -//------------------------------------------------------------------------------------------------- -Locomotor::~Locomotor() -{ -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void Locomotor::crc( Xfer *xfer ) -{ - -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void Locomotor::xfer( Xfer *xfer ) -{ - // version - const XferVersion currentVersion = 2; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - if (version>=2) { - xfer->xferUnsignedInt(&m_donutTimer); - } - - xfer->xferCoord3D(&m_maintainPos); - xfer->xferReal(&m_brakingFactor); - xfer->xferReal(&m_maxLift); - xfer->xferReal(&m_maxSpeed); - xfer->xferReal(&m_maxAccel); - xfer->xferReal(&m_maxBraking); - xfer->xferReal(&m_maxTurnRate); - xfer->xferReal(&m_closeEnoughDist); -#ifdef CIRCLE_FOR_LANDING - DEBUG_CRASH(("not supported, must fix me")); -#endif - xfer->xferUnsignedInt(&m_flags); - xfer->xferReal(&m_preferredHeight); - xfer->xferReal(&m_preferredHeightDamping); - xfer->xferReal(&m_angleOffset); - xfer->xferReal(&m_offsetIncrement); - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void Locomotor::loadPostProcess( void ) -{ - -} // end loadPostProcess - -//------------------------------------------------------------------------------------------------- -void Locomotor::startMove(void) -{ - // Reset the donut timer. - m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::getMaxSpeedForCondition(BodyDamageType condition) const -{ - Real speed; - - if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) - speed = m_template->m_maxSpeed; - else - speed = m_template->m_maxSpeedDamaged; - - if (speed > m_maxSpeed) - speed = m_maxSpeed; - - return speed; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::getMaxTurnRate(BodyDamageType condition) const -{ - Real turn; - - if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) - turn = m_template->m_maxTurnRate; - else - turn = m_template->m_maxTurnRateDamaged; - - if (turn > m_maxTurnRate) - turn = m_maxTurnRate; - - const Real TURN_FACTOR = 2; - if (getFlag(ULTRA_ACCURATE)) - turn *= TURN_FACTOR; // monster turning ability - - return turn; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::getMaxAcceleration(BodyDamageType condition) const -{ - Real accel; - - if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) - accel = m_template->m_acceleration; - else - accel = m_template->m_accelerationDamaged; - - if (accel > m_maxAccel) - accel = m_maxAccel; - - return accel; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::getBraking() const -{ - Real braking = m_template->m_braking; - - if (braking > m_maxBraking) - braking = m_maxBraking; - - return braking; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::getMaxLift(BodyDamageType condition) const -{ - Real lift; - - if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) - lift = m_template->m_lift; - else - lift = m_template->m_liftDamaged; - - if (lift > m_maxLift) - lift = m_maxLift; - - return lift; -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::locoUpdate_moveTowardsAngle(Object* obj, Real goalAngle) -{ - setFlag(MAINTAIN_POS_IS_VALID, false); - - if (obj == NULL || m_template == NULL) - return; - - PhysicsBehavior *physics = obj->getPhysics(); - if (physics == NULL) - { - DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); - return; - } - - // Skip moveTowardsAngle if physics say you're stunned - if(physics->getIsStunned()) - { - return; - } - -#ifdef DEBUG_OBJECT_ID_EXISTS -// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_moveTowardsAngle %f (%f deg), spd %f (%f)\n",goalAngle,goalAngle*180/PI,physics->getSpeed(),physics->getForwardSpeed2D())); -#endif - - Real minSpeed = getMinSpeed(); - if (minSpeed > 0) - { - // can't stay in one place; move in the desired direction at min speed. - Coord3D desiredPos = *obj->getPosition(); - desiredPos.x += Cos(goalAngle) * minSpeed * 2; - desiredPos.y += Sin(goalAngle) * minSpeed * 2; - // pass a huge num for "dist to goal", so that we don't think we're nearing - // our destination and thus slow down... - const Real onPathDistToGoal = 99999.0f; - Bool blocked = false; - locoUpdate_moveTowardsPosition(obj, desiredPos, onPathDistToGoal, minSpeed, &blocked); - - // don't need to call handleBehaviorZ() here, since locoUpdate_moveTowardsPosition() will do so - return; - } - else - { - DEBUG_ASSERTCRASH(m_template->m_appearance != LOCO_THRUST, ("THRUST should always have minspeeds!\n")); - Coord3D desiredPos = *obj->getPosition(); - desiredPos.x += Cos(goalAngle) * 1000.0f; - desiredPos.y += Sin(goalAngle) * 1000.0f; - PhysicsTurningType rotating = rotateTowardsPosition(obj, desiredPos); - physics->setTurning(rotating); - handleBehaviorZ(obj, physics, *obj->getPosition()); - } - -} - -//------------------------------------------------------------------------------------------------- -PhysicsTurningType Locomotor::rotateTowardsPosition(Object* obj, const Coord3D& goalPos, Real *relAngle) -{ - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real turnRate = getMaxTurnRate(bdt); - - PhysicsTurningType rotating = rotateObjAroundLocoPivot(obj, goalPos, turnRate, relAngle); - return rotating; -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::setPhysicsOptions(Object* obj) -{ - PhysicsBehavior *physics = obj->getPhysics(); - if (physics == NULL) - { - DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); - return; - } - - // crank up the friction in ultra-accurate mode to increase movement precision. - const Real EXTRA_FRIC = 0.5f; - Real extraExtraFriction = getFlag(ULTRA_ACCURATE) ? EXTRA_FRIC : 0.0f; - physics->setExtraFriction(m_template->m_extra2DFriction + extraExtraFriction); - physics->setAllowAirborneFriction(getApply2DFrictionWhenAirborne()); // you'd think we wouldn't want friction in the air, but it's needed for realistic behavior. - physics->setStickToGround(getStickToGround()); // walking guys aren't allowed to catch huge (or even small) air. -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalPos, - Real onPathDistToGoal, Real desiredSpeed, Bool *blocked) -{ - setFlag(MAINTAIN_POS_IS_VALID, false); - - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxSpeed = getMaxSpeedForCondition(bdt); - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - Real distToStopAtMaxSpeed = (maxSpeed/getBraking()) * (maxSpeed)/2.0f; - if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > distToStopAtMaxSpeed) - { - setFlag(IS_BRAKING, false); - m_brakingFactor = 1.0f; - } - PhysicsBehavior *physics = obj->getPhysics(); - if (physics == NULL) - { - DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); - return; - } - - // Skip moveTowardsPosition if physics say you're stunned - if(physics->getIsStunned()) - { - return; - } - -#ifdef DEBUG_OBJECT_ID_EXISTS -// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_moveTowardsPosition %f %f %f (dtg %f, spd %f), speed %f (%f)\n",goalPos.x,goalPos.y,goalPos.z,onPathDistToGoal,desiredSpeed,physics->getSpeed(),physics->getForwardSpeed2D())); -#endif - - // - // do not allow for invalid positions that the pathfinder cannot handle ... for airborne - // objects we don't need the pathfinder so we'll ignore this - // - if( BitIsSet( m_template->m_surfaces, LOCOMOTORSURFACE_AIR ) == false && - !TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, obj->getPosition()) && - !getFlag(ALLOW_INVALID_POSITION)) - { - // Somehow, we have gotten to an invalid location. - if (fixInvalidPosition(obj, physics)) - { - // the we adjusted us toward a legal position, so just return. - return; - } - } - - // If the actual distance is farther, then use the actual distance so we get there. - Real dx = goalPos.x - obj->getPosition()->x; - Real dy = goalPos.y - obj->getPosition()->y; - Real dz = goalPos.z - obj->getPosition()->z; - Real dist = sqrt(dx*dx+dy*dy); - if (dist>onPathDistToGoal) - { - if (!obj->isKindOf(KINDOF_PROJECTILE) && dist>2*onPathDistToGoal) - { - setFlag(IS_BRAKING, true); - } - onPathDistToGoal = dist; - } - - Coord3D nullAccel; - - Bool treatAsAirborne = false; - Coord3D pos = *obj->getPosition(); - Real heightAboveSurface = pos.z - TheTerrainLogic->getLayerHeight(pos.x, pos.y, obj->getLayer()); - - if( obj->getStatusBits().test( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) ) - { - heightAboveSurface -= obj->getCarrierDeckHeight(); - } - - if (heightAboveSurface > -(3*3)*TheGlobalData->m_gravity) - { - // If we get high enough to stay up for 3 frames, then we left the ground. - treatAsAirborne = true; - } - // We apply a zero acceleration to all units, as the call to - // applyMotiveForce flags an object as being "driven" by a locomotor, rather - // than being pushed around by objects bumping it. - nullAccel.x = nullAccel.y = nullAccel.z = 0; - physics->applyMotiveForce(&nullAccel); - - if (*blocked) - { - if (desiredSpeed > physics->getVelocityMagnitude()) - { - *blocked = false; - } - if (treatAsAirborne && BitIsSet( m_template->m_surfaces, LOCOMOTORSURFACE_AIR ) ) - { - // Airborne flying objects don't collide for now. jba. - *blocked = false; - } - } - - if (*blocked) - { - physics->scrubVelocity2D(desiredSpeed); // stop if we are about to run into the blocking object. - Real turnRate = getMaxTurnRate(obj->getBodyModule()->getDamageState()); - if (m_template->m_wanderWidthFactor == 0.0f) - { - *blocked = (TURN_NONE != rotateObjAroundLocoPivot(obj, goalPos, turnRate)); - } - - // it is very important to be sure to call this in all situations, even if not moving in 2d space. - handleBehaviorZ(obj, physics, goalPos); - return; - } - - if ( -// srj sez: I don't know why we didn't want HOVERs to allow to "brake". -// we actually really want them to, because it allows much more precise destination positioning. -// m_template->m_appearance == LOCO_HOVER || - m_template->m_appearance == LOCO_WINGS) - { - setFlag(IS_BRAKING, false); - } - - Bool wasBraking = obj->getStatusBits().test( OBJECT_STATUS_BRAKING ); - - physics->setTurning(TURN_NONE); - if (getAllowMotiveForceWhileAirborne() || !treatAsAirborne) - { - switch (m_template->m_appearance) - { - case LOCO_LEGS_TWO: - moveTowardsPositionLegs(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_CLIMBER: - moveTowardsPositionClimb(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_WHEELS_FOUR: - case LOCO_MOTORCYCLE: - moveTowardsPositionWheels( obj, physics, goalPos, onPathDistToGoal, desiredSpeed ); - break; - case LOCO_TREADS: - moveTowardsPositionTreads(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_HOVER: - moveTowardsPositionHover(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_WINGS: - moveTowardsPositionWings(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_THRUST: - moveTowardsPositionThrust(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_OTHER: - default: - moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - } - } - - handleBehaviorZ(obj, physics, goalPos); - // Objects that are braking don't follow the normal physics, so they end up at their destination exactly. - obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ), getFlag(IS_BRAKING) ); - - if (wasBraking) - { - #define MIN_VEL (PATHFIND_CELL_SIZE_F/(LOGICFRAMES_PER_SECOND)) - - Coord3D pos = *obj->getPosition(); - if (obj->isKindOf(KINDOF_PROJECTILE)) - { - // Projectiles never stop braking once they start. jba. - obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ) ); - // Projectiles cheat in 3 dimensions. - dist = sqrt(dx*dx+dy*dy+dz*dz); - Real vel = physics->getVelocityMagnitude(); - if (vel < MIN_VEL) - vel = MIN_VEL; - if (vel > dist) - vel = dist; // do not overcompensate! - // Normalize. - if (dist > 0.001f) - { - dist = 1.0f / dist; - dx *= dist; - dy *= dist; - dz *= dist; - - // DEBUG_LOG((">>> Locomotor Braking - d(xyz) = %f / %f / %f\n", dx * vel, dy * vel, dz * vel)); - - pos.x += dx * vel; - pos.y += dy * vel; - pos.z += dz * vel; - } - } - else - { - // not projectiles only cheat in x & y. - // Normalize. - if (dist > 0.001f) - { - Real vel = fabs(physics->getForwardSpeed2D()); - if (vel < MIN_VEL) - vel = MIN_VEL; - if (vel > dist) - vel = dist; // do not overcompensate! - dist = 1.0f / dist; - dx *= dist; - dy *= dist; - pos.x += dx * vel; - pos.y += dy * vel; - } - } - obj->setPosition(&pos); - } - -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxSpeed = getMaxSpeedForCondition(bdt); - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - Real maxAcceleration = getMaxAcceleration(bdt); - - // Locomotion for treaded vehicles, ie tanks. - - // - // Orient toward goal position - // -// Real angle = obj->getOrientation(); -// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); -// Real desiredAngle = angle + relAngle; - Real relAngle ; - PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos, &relAngle); - physics->setTurning(rotating); - - // - // Modulate speed according to turning. The more we have to turn, the slower we go - // - const Real QUAETERPI = PI / 4.0f; - Real angleCoeff = (Real)fabs( relAngle ) / QUAETERPI; - if (angleCoeff > 1.0f) - angleCoeff = 1.0; - - - Real dx = obj->getPosition()->x - goalPos.x; - Real dy = obj->getPosition()->y - goalPos.y; - - - Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; - - -// if (speed < m_minTurnSpeed) -// speed = m_minTurnSpeed; - - Real actualSpeed = physics->getForwardSpeed2D(); - Real slowDownTime = actualSpeed / getBraking(); - Real slowDownDist = (actualSpeed/1.50f) * slowDownTime; - - if (sqr(dx)+sqr(dy) 0.05) { - goalSpeed = actualSpeed*0.6f; - } - - if (onPathDistToGoal < slowDownDist && !getFlag(IS_BRAKING) && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - { - setFlag(IS_BRAKING, true); - m_brakingFactor = 1.1f; - } - - if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > 2.0*slowDownDist) - { - setFlag(IS_BRAKING, false); - } - - if (getFlag(IS_BRAKING)) - { - m_brakingFactor = slowDownDist/onPathDistToGoal; - m_brakingFactor *= m_brakingFactor; - if (m_brakingFactor>MAX_BRAKING_FACTOR) { - m_brakingFactor = MAX_BRAKING_FACTOR; - } - if (slowDownDist>onPathDistToGoal) { - goalSpeed = actualSpeed-getBraking(); - if (goalSpeed<0.0f) goalSpeed= 0.0f; - } else if (slowDownDist>onPathDistToGoal*0.75f) { - goalSpeed = actualSpeed-getBraking()/2.0f; - if (goalSpeed<0.0f) goalSpeed = 0.0f; - } else { - goalSpeed = actualSpeed; - } - } - - - //DEBUG_LOG(("Actual speed %f, Braking factor %f, slowDownDist %f, Pathdist %f, goalSpeed %f\n", - // actualSpeed, m_brakingFactor, slowDownDist, onPathDistToGoal, goalSpeed)); - - // - // Maintain goal speed - // - Real speedDelta = goalSpeed - actualSpeed; - if (speedDelta != 0.0f) - { - Real mass = physics->getMass(); - Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -m_brakingFactor*getBraking(); - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - const Coord3D *dir = obj->getUnitDirectionVector2D(); - - Coord3D force; - force.x = accelForce * dir->x; - force.y = accelForce * dir->y; - force.z = 0.0f; - - // apply forces to object - physics->applyMotiveForce( &force ); - } -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxSpeed = getMaxSpeedForCondition(bdt); - Real maxTurnRate = getMaxTurnRate(bdt); - Real maxAcceleration = getMaxAcceleration(bdt); - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - // Locomotion for wheeled vehicles, ie trucks. - // - // See if we are turning. If so, use the min turn speed. - // - Real turnSpeed = m_template->m_minTurnSpeed; - Real angle = obj->getOrientation(); -// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); -// Real desiredAngle = angle + relAngle; - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); - Real relAngle = stdAngleDiff(desiredAngle, angle); - - Bool moveBackwards = false; - - // Wheeled vehicles can only turn while moving, so make sure the turn speed is reasonable. - if (turnSpeed < maxSpeed/4.0f) - { - turnSpeed = maxSpeed/4.0f; - } - - - Real actualSpeed = physics->getForwardSpeed2D(); - Bool do3pointTurn = false; -#if 1 - if (actualSpeed==0.0f) { - setFlag(MOVING_BACKWARDS, false); - if (m_template->m_canMoveBackward && fabs(relAngle) > PI/2) { - setFlag(MOVING_BACKWARDS, true ); - setFlag(DOING_THREE_POINT_TURN, onPathDistToGoal>5*obj->getGeometryInfo().getMajorRadius()); - } - - } - if (getFlag(MOVING_BACKWARDS)) { - if (fabs(relAngle) < PI/2) { - moveBackwards = false; - setFlag(MOVING_BACKWARDS, false); - } else { - moveBackwards = true; - setFlag(DOING_THREE_POINT_TURN, onPathDistToGoal>5*obj->getGeometryInfo().getMajorRadius()); - do3pointTurn = getFlag(DOING_THREE_POINT_TURN); - if (!do3pointTurn) { - desiredAngle = stdAngleDiff(desiredAngle, PI); - relAngle = stdAngleDiff(desiredAngle, angle); - } - } - } -#endif - - const Real SMALL_TURN = PI / 20.0f; - if ((Real)fabs( relAngle ) > SMALL_TURN) - { - if (desiredSpeed>turnSpeed) - { - desiredSpeed = turnSpeed; - } - } - - Real goalSpeed = desiredSpeed; - if (moveBackwards) { - actualSpeed = -actualSpeed; - } - - - - Real slowDownTime = actualSpeed / getBraking() + 1.0f; - Real slowDownDist = (actualSpeed/1.5f) * slowDownTime + actualSpeed; - Real effectiveSlowDownDist = slowDownDist; - if (effectiveSlowDownDist < 1*PATHFIND_CELL_SIZE) { - effectiveSlowDownDist = 1*PATHFIND_CELL_SIZE; - } - - - const Real FIFTEEN_DEGREES = PI / 12.0f; - const Real PROJECT_FRAMES = LOGICFRAMES_PER_SECOND/2; // Project out 1/2 second. - if (fabs( relAngle ) > FIFTEEN_DEGREES) - { - // If we're turning more than 10 degrees, check & see if we're moving into "impassable territory" - Real distance = PROJECT_FRAMES * (goalSpeed+actualSpeed)/2.0f; - Real targetAngle = obj->getOrientation(); - Real turnFactor = ((goalSpeed+actualSpeed)/2.0f)/turnSpeed; - if (turnFactor > 1.0f) - turnFactor = 1.0f; - Real turnAmount = PROJECT_FRAMES*turnFactor*maxTurnRate/4.0f; - if (relAngle < 0) - { - targetAngle -= turnAmount; - } - else - { - targetAngle += turnAmount; - } - Coord3D offset; - offset.x = Cos(targetAngle)*distance; - offset.y = Sin(targetAngle)*distance; - offset.z = 0; - - const Coord3D* pos = obj->getPosition(); - - Coord3D nextPos; - nextPos.x = pos->x+offset.x; - nextPos.y = pos->y+offset.y; - nextPos.z = pos->z; - - pos = obj->getPosition(); - - Coord3D halfPos; - halfPos.x = pos->x+offset.x/2; - halfPos.y = pos->y+offset.y/2; - halfPos.z = pos->z; - - if (!TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &halfPos) || - !TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &nextPos)) - { - PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos); - physics->setTurning(rotating); - - // apply a zero force to object so that it acts "driven" - Coord3D force; - force.zero(); - physics->applyMotiveForce( &force ); - return; - } - - } - - if (onPathDistToGoal < effectiveSlowDownDist && !getFlag(IS_BRAKING) && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - { - setFlag(IS_BRAKING, true); - m_brakingFactor = 1.1f; - } - - - if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > 2.0*slowDownDist) - { - setFlag(IS_BRAKING, false); - } - - if (onPathDistToGoal > DONUT_DISTANCE) { - m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; - } else { - if (m_donutTimer < TheGameLogic->getFrame()) { - setFlag(IS_BRAKING, true); - } - } - - if (getFlag(IS_BRAKING)) - { - m_brakingFactor = slowDownDist/onPathDistToGoal; - m_brakingFactor *= m_brakingFactor; - if (m_brakingFactor>MAX_BRAKING_FACTOR) { - m_brakingFactor = MAX_BRAKING_FACTOR; - } - m_brakingFactor = 1.0f; - if (slowDownDist>onPathDistToGoal) { - goalSpeed = actualSpeed-getBraking(); - if (goalSpeed<0.0f) goalSpeed= 0.0f; - } else if (slowDownDist>onPathDistToGoal*0.75f) { - goalSpeed = actualSpeed-getBraking()/2.0f; - if (goalSpeed<0.0f) goalSpeed = 0.0f; - } else { - goalSpeed = actualSpeed; - } - } - - - //DEBUG_LOG(("Actual speed %f, Braking factor %f, slowDownDist %f, Pathdist %f, goalSpeed %f\n", - // actualSpeed, m_brakingFactor, slowDownDist, onPathDistToGoal, goalSpeed)); - - - // Wheeled can only turn while moving. - Real turnFactor = actualSpeed/turnSpeed; - if (turnFactor<0) { - turnFactor = -turnFactor; // in case we're sliding backwards in a 3 pt turn. - } - if (turnFactor > 1.0f) - turnFactor = 1.0f; - Real turnAmount = turnFactor*maxTurnRate; - - PhysicsTurningType rotating; - if (moveBackwards && !do3pointTurn) { - Coord3D backwardPos = *obj->getPosition(); - backwardPos.x += -(goalPos.x - obj->getPosition()->x); - backwardPos.y += -(goalPos.y - obj->getPosition()->y); - rotating = rotateObjAroundLocoPivot(obj, backwardPos, turnAmount); - } else { - rotating = rotateObjAroundLocoPivot(obj, goalPos, turnAmount); - } - - physics->setTurning(rotating); - - // - // Maintain goal speed - // - Real speedDelta = goalSpeed - actualSpeed; - if (moveBackwards) { - speedDelta = -goalSpeed+actualSpeed; - } - if (speedDelta != 0.0f) - { - Real mass = physics->getMass(); - Real acceleration; - if (moveBackwards) { - acceleration = (speedDelta < 0.0f) ? -maxAcceleration : m_brakingFactor*getBraking(); - } else { - acceleration = (speedDelta > 0.0f) ? maxAcceleration : -m_brakingFactor*getBraking(); - } - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - //DEBUG_LOG(("Braking %d, actualSpeed %f, goalSpeed %f, delta %f, accel %f\n", getFlag(IS_BRAKING), - //actualSpeed, goalSpeed, speedDelta, accelForce)); - - const Coord3D *dir = obj->getUnitDirectionVector2D(); - - Coord3D force; - force.x = accelForce * dir->x; - force.y = accelForce * dir->y; - force.z = 0.0f; - - // apply forces to object - physics->applyMotiveForce( &force ); - } - -} -//------------------------------------------------------------------------------------------------- -Bool Locomotor::fixInvalidPosition(Object* obj, PhysicsBehavior *physics) -{ - if (obj->isKindOf(KINDOF_DOZER)) { - // don't fix him. - return false; - } -#define no_IGNORE_INVALID -#ifdef IGNORE_INVALID - // Right now we ignore invalid positions, so when units clip the edge of a building or cliff - // they don't get stuck. jba. 12SEPT02 - return false; -#else - Int dx = 0; - Int dy = 0; - Int i, j; - for (j=-1; j<2; j++) { - for (i=-1; i<2; i++) { - Coord3D thePos = *obj->getPosition(); - thePos.x += i*PATHFIND_CELL_SIZE_F; - thePos.y += j*PATHFIND_CELL_SIZE_F; - if (!TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &thePos)) { - if (i<0) dx += 1; - if (i>0) dx -= 1; - if (j<0) dy += 1; - if (j>0) dy -= 1; - } - } - } - if (dx || dy) { - - Coord3D correction; - correction.x = dx*physics->getMass()/5; - correction.y = dy*physics->getMass()/5; - correction.z = 0; - - Coord3D correctionNormalized = correction; - correctionNormalized.normalize(); - - Coord3D velocity; - // Kill current velocity in the direction of the correction. - velocity = *physics->getVelocity(); - Real dot = (velocity.x*correctionNormalized.x) + (velocity.y*correctionNormalized.y); - if (dot>.25f) { - // It was already leaving. - return false; - } - - - // Kill current accel - //physics->clearAcceleration(); - - if (dot<0) { - dot = sqrt(-dot); - correctionNormalized.x *= dot*physics->getMass(); - correctionNormalized.y *= dot*physics->getMass(); - physics->applyMotiveForce(&correctionNormalized); - } - - // apply correction. - physics->applyMotiveForce(&correction); - return true; - } - return false; -#endif -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::calcMinTurnRadius(BodyDamageType condition, Real* timeToTravelThatDist) const -{ - Real minSpeed = getMinSpeed(); // in dist/frame - Real maxTurnRate = getMaxTurnRate(condition); // in rads/frame - - /* - our minimum circumference will be like so: - - Real minTurnCircum = maxSpeed * (2*PI / maxTurnRate); - - so therefore our minimum turn radius is: - - Real minTurnRadius = minTurnCircum / 2*PI; - - so we just eliminate the middleman: - */ - // if we can't turn, return a huge-but-finite radius rather than NAN... - Real minTurnRadius = (maxTurnRate > 0.0f) ? minSpeed / maxTurnRate : BIGNUM; - - if (timeToTravelThatDist) - *timeToTravelThatDist = (minSpeed > 0.0f) ? (minTurnRadius / minSpeed) : 0.0f; - - return minTurnRadius; -} - - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - if (getIsDownhillOnly() && obj->getPosition()->z < goalPos.z) - { - return; - } - - Real maxAcceleration = getMaxAcceleration( obj->getBodyModule()->getDamageState() ); - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState() ); - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - // Locomotion for infantry. - // - // Orient toward goal position - // - Real actualSpeed = physics->getForwardSpeed2D(); - Real angle = obj->getOrientation(); -// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); -// Real desiredAngle = angle + relAngle; - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); - - if (m_template->m_wanderWidthFactor != 0.0f) { - Real angleLimit = PI/8 * m_template->m_wanderWidthFactor; - // This is the wander offline code - it forces the desired angle away from the goal, so we wander back & forth. jba. - if (getFlag(OFFSET_INCREASING)) { - m_angleOffset += m_offsetIncrement*actualSpeed; - if (m_angleOffset > angleLimit) { - setFlag(OFFSET_INCREASING, false); - } - } else { - m_angleOffset -= m_offsetIncrement*actualSpeed; - if (m_angleOffset<-angleLimit) { - setFlag(OFFSET_INCREASING, true); - } - } - desiredAngle = normalizeAngle(desiredAngle+m_angleOffset); - } - - Real relAngle = stdAngleDiff(desiredAngle, angle); - locoUpdate_moveTowardsAngle(obj, desiredAngle); - - // - // Modulate speed according to turning. The more we have to turn, the slower we go - // - const Real QUARTERPI = PI / 4.0f; - Real angleCoeff = (Real)fabs( relAngle ) / (QUARTERPI); - if (angleCoeff > 1.0f) - angleCoeff = 1.0; - - Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; - - //Real slowDownDist = (actualSpeed - m_template->m_minSpeed) / getBraking(); - Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); - if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - { - goalSpeed = m_template->m_minSpeed; - } - - - - // - // Maintain goal speed - // - Real speedDelta = goalSpeed - actualSpeed; - if (speedDelta != 0.0f) - { - Real mass = physics->getMass(); - Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - const Coord3D *dir = obj->getUnitDirectionVector2D(); - - Coord3D force; - force.x = accelForce * dir->x; - force.y = accelForce * dir->y; - force.z = 0.0f; - - - - // apply forces to object - physics->applyMotiveForce( &force ); - } -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - Real maxAcceleration = getMaxAcceleration( obj->getBodyModule()->getDamageState() ); - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState() ); - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - // Locomotion for climbing infantry. - - - Bool moveBackwards = false; - - Real dx, dy, dz; - - Coord3D pos = *obj->getPosition(); - - dx = pos.x - goalPos.x; - dy = pos.y - goalPos.y; - dz = pos.z - goalPos.z; - if (dz*dz > sqr(PATHFIND_CELL_SIZE_F)) { - setFlag(CLIMBING, true); - } - if (fabs(dz)<1) { - setFlag(CLIMBING, false); - } - - - //setFlag(CLIMBING, true); - - if (getFlag(CLIMBING)) { - Coord3D delta = goalPos; - delta.x -= pos.x; - delta.y -= pos.y; - delta.z = 0; - delta.normalize(); - delta.x += pos.x; - delta.y += pos.y; - delta.z = TheTerrainLogic->getGroundHeight(delta.x, delta.y); - if (delta.z < pos.z-0.1) { - moveBackwards = true; - } - - Real groundSlope = fabs(delta.z - pos.z); - if (groundSlope<1.0f) groundSlope = 1.0f; - - if (groundSlope>1.0f) { - desiredSpeed /= groundSlope*4; - } - } - setFlag(MOVING_BACKWARDS, moveBackwards); - - // - // Orient toward goal position - // - Real angle = obj->getOrientation(); -// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); -// Real desiredAngle = angle + relAngle; - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); - Real relAngle = stdAngleDiff(desiredAngle, angle); - - if (moveBackwards) { - desiredAngle = stdAngleDiff(desiredAngle, PI); - relAngle = stdAngleDiff(desiredAngle, angle); - } - - locoUpdate_moveTowardsAngle(obj, desiredAngle); - - // - // Modulate speed according to turning. The more we have to turn, the slower we go - // - const Real QUARTERPI = PI / 4.0f; - Real angleCoeff = (Real)fabs( relAngle ) / (QUARTERPI); - if (angleCoeff > 1.0f) - angleCoeff = 1.0; - - Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; - - Real actualSpeed = physics->getForwardSpeed2D(); - - if (moveBackwards) { - actualSpeed = -actualSpeed; - } - - //Real slowDownDist = (actualSpeed - m_template->m_minSpeed) / getBraking(); - Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); - if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - { - goalSpeed = m_template->m_minSpeed; - } - - // - // Maintain goal speed - // - Real speedDelta = goalSpeed - actualSpeed; - if (moveBackwards) { - speedDelta = -goalSpeed+actualSpeed; - } - if (speedDelta != 0.0f) - { - Real mass = physics->getMass(); - Real acceleration; - if (moveBackwards) { - acceleration = (speedDelta < 0.0f) ? -maxAcceleration : getBraking(); - } else { - acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); - } - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - const Coord3D *dir = obj->getUnitDirectionVector2D(); - - Coord3D force; - force.x = accelForce * dir->x; - force.y = accelForce * dir->y; - force.z = 0.0f; - - // apply forces to object - physics->applyMotiveForce( &force ); - } -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ -#ifdef CIRCLE_FOR_LANDING - if (m_circleThresh > 0.0f) - { - // if we are going a mostly-vertical maneuver, circle in order to - // gain/lose altitude, then resume course... - const Coord3D* pos = obj->getPosition(); - Real dx = goalPos.x - pos->x; - Real dy = goalPos.y - pos->y; - Real dz = goalPos.z - pos->z; - if (fabs(dz) > m_circleThresh) - { - // aim for the spot on the opposite side of the circle. - - // find the direction towards our goal pos - Real angleTowardPos = - (isNearlyZero(dx) && isNearlyZero(dy)) ? - obj->getOrientation() : - atan2(dy, dx); - - Real aimDir = (PI - PI/8); - angleTowardPos += aimDir; - - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real turnRadius = calcMinTurnRadius(bdt, NULL) * 4; - - // project a spot "radius" dist away from it, in that dir - Coord3D desiredPos = goalPos; - desiredPos.x += Cos(angleTowardPos) * turnRadius; - desiredPos.y += Sin(angleTowardPos) * turnRadius; - moveTowardsPositionOther(obj, physics, desiredPos, 0, desiredSpeed); - return; - } - } -#endif - - // handle the 2D component. - moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionHover(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - // handle the 2D component. - moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - - // Only hover locomotors care about their OverWater special effects. (OverWater also affects speed, so this is not a client thing) - Coord3D newPosition = *obj->getPosition(); - if( TheTerrainLogic->isUnderwater( newPosition.x, newPosition.y ) ) - { - if( ! getFlag( OVER_WATER ) ) - { - // Change my model condition because I used to not be over water, but now I am - setFlag( OVER_WATER, TRUE ); - obj->setModelConditionState( MODELCONDITION_OVER_WATER ); - } - } - else - { - if( getFlag( OVER_WATER ) ) - { - // Here, I was, but now I'm not - setFlag( OVER_WATER, FALSE ); - obj->clearModelConditionState( MODELCONDITION_OVER_WATER ); - } - } -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - - Real maxForwardSpeed = getMaxSpeedForCondition(bdt); - desiredSpeed = clamp(m_template->m_minSpeed, desiredSpeed, maxForwardSpeed); - Real actualForwardSpeed = physics->getForwardSpeed3D(); - - if (getBraking() > 0) - { - //Real slowDownDist = (actualForwardSpeed - m_template->m_minSpeed) / getBraking(); - Real slowDownDist = calcSlowDownDist(actualForwardSpeed, m_template->m_minSpeed, getBraking()); - if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - desiredSpeed = m_template->m_minSpeed; - } - - Coord3D localGoalPos = goalPos; -#ifdef USE_ZDIR_DAMPING - Real zDirDamping = 0.0f; -#endif - - //out of the handleBehaviorZ() function - Coord3D pos = *obj->getPosition(); - if( m_preferredHeight != 0.0f && !getFlag(PRECISE_Z_POS) ) - { - // If we have a preferred flight height, and we haven't been told explicitly to ignore it... - Real surfaceHt = getSurfaceHtAtPt(pos.x, pos.y); - localGoalPos.z = m_preferredHeight + surfaceHt; -// localGoalPos.z = goalPos.z; - Real delta = localGoalPos.z - pos.z; - delta *= getPreferredHeightDamping(); - localGoalPos.z = pos.z + delta; - -#ifdef USE_ZDIR_DAMPING - // closer we get to the preferred height, less we adjust z-thrust, - // so we tend to "level out" at that height. we don't use this till - // below, but go ahead and calc it now... - Real MAX_VERTICAL_DAMP_RANGE = m_preferredHeight * 0.5; - delta = fabs(delta); - if (delta > MAX_VERTICAL_DAMP_RANGE) - delta = MAX_VERTICAL_DAMP_RANGE; - zDirDamping = 1.0f - (delta / MAX_VERTICAL_DAMP_RANGE); -#endif - } - - Vector3 forwardDir = obj->getTransformMatrix()->Get_X_Vector(); - - // Maintain goal speed - Real forwardSpeedDelta = desiredSpeed - actualForwardSpeed; - Real maxAccel = (forwardSpeedDelta > 0.0f || getBraking() == 0) ? getMaxAcceleration(bdt) : -getBraking(); - Real maxTurnRate = getMaxTurnRate(bdt); - - // what direction do we need to thrust in, in order to reach the goalpos? - Vector3 desiredThrustDir; - calcDirectionToApplyThrust(obj, physics, localGoalPos, maxAccel, desiredThrustDir); - - // we might not be able to thrust in that dir, so thrust as closely as we can - Real maxThrustAngle = (maxTurnRate > 0) ? (m_template->m_maxThrustAngle) : 0; - Vector3 thrustDir; - Real thrustAngle = tryToRotateVector3D(maxThrustAngle, forwardDir, desiredThrustDir, thrustDir); - - // note that we are trying to orient in the direction of our vel, not the dir of our thrust. - if (!isNearlyZero(physics->getVelocityMagnitude())) - { - const Coord3D* veltmp = physics->getVelocity(); - Vector3 vel(veltmp->x, veltmp->y, veltmp->z); - Bool adjust = true; - if( obj->getStatusBits().test( OBJECT_STATUS_BRAKING ) ) - { - //Real closeInDist = 150.0f; // TODO: get/set this from missileAI? - //Real af = 1.0f - __min((onPathDistToGoal / closeInDist), 1.0); - - //if (af > 0.0f) { - - // vel.Set( - // vel.X * (1.0f - af) + (goalPos.x - pos.x) * af, - // vel.Y * (1.0f - af) + (goalPos.y - pos.y) * af, - // vel.Z * (1.0f - af) + (goalPos.z - pos.z) * af - // ); - // if (isNearlyZero(sqr(vel.X) + sqr(vel.Y) + sqr(vel.Z))) { - // // we are at target. - // adjust = false; - // } - // maxTurnRate = (1.0f + (af * 2.0f) ) * maxTurnRate; - //} - - // DEBUG_LOG((">>> moveTowardsPositionThrust - Braking - maxTurnRate = %f\n", maxTurnRate)); - - // align to target, cause that's where we're going anyway. - - vel.Set(goalPos.x - pos.x, goalPos.y-pos.y, goalPos.z-pos.z); - if (isNearlyZero(sqr(vel.X)+sqr(vel.Y)+sqr(vel.Z))) { - // we are at target. - adjust = false; - } - maxTurnRate = 3*maxTurnRate; - } -#ifdef USE_ZDIR_DAMPING - if (zDirDamping != 0.0f) - { - Vector3 vel2D(veltmp->x, veltmp->y, 0); - // no need to normalize -- this call does that internally - tryToRotateVector3D(-zDirDamping, vel, vel2D, vel); - } -#endif - if (adjust) { - /*Real orient =*/ tryToOrientInThisDirection3D(obj, maxTurnRate, vel); - } - } - - if (forwardSpeedDelta != 0.0f || thrustAngle != 0.0f) - { - if (maxForwardSpeed <= 0.0f) - { - maxForwardSpeed = 0.01f; // In some cases, this is 0, hack for now. jba. - } - Real damping = clamp(0.0f, maxAccel / maxForwardSpeed, 1.0f); - Vector3 curVel(physics->getVelocity()->x, physics->getVelocity()->y, physics->getVelocity()->z); - - Vector3 accelVec = thrustDir * maxAccel - curVel * damping; - //DEBUG_LOG(("accel %f (max %f) vel %f (max %f) damping %f\n",accelVec.Length(),maxAccel,curVel.Length(),maxForwardSpeed,damping)); - - Real mass = physics->getMass(); - - Coord3D force; - force.x = mass * accelVec.X; - force.y = mass * accelVec.Y; - force.z = mass * accelVec.Z; - - // apply forces to object - physics->applyMotiveForce( &force ); - } -} - -//------------------------------------------------------------------------------------------------- -/*static*/ Real Locomotor::getSurfaceHtAtPt(Real x, Real y) -{ - Real ht = 0; - - Real z,waterZ; - if (TheTerrainLogic->isUnderwater(x, y, &waterZ, &z)) { - ht += waterZ; - } else { - ht += z; - } - - return ht; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::calcLiftToUseAtPt(Object* obj, PhysicsBehavior *physics, Real curZ, Real surfaceAtPt, Real preferredHeight) -{ - /* - take the classic equation: - - x = x0 + v*t + 0.5*a*t^2 - - and solve for acceleration. - */ - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxGrossLift = getMaxLift(bdt); - Real maxNetLift = maxGrossLift + TheGlobalData->m_gravity; // note that gravity is always negative. - if (maxNetLift < 0) - maxNetLift = 0; - Real curVelZ = physics->getVelocity()->z; - // going down, braking is limited by net lift; going up, braking is limited by gravity - Real maxAccel; - if (getFlag(ULTRA_ACCURATE)) - maxAccel = (curVelZ < 0) ? 2*maxNetLift : -2*maxNetLift; - else - maxAccel = (curVelZ < 0) ? maxNetLift : TheGlobalData->m_gravity; - // see how far we need to slow to dead stop, given max braking - Real desiredAccel; - const Real TINY_ACCEL = 0.001f; - if (fabs(maxAccel) > TINY_ACCEL) - { - Real deltaZ = preferredHeight - curZ; - // calc how far it will take for us to go from cur speed to zero speed, at max accel. - // Real brakeDist = calcSlowDownDist(curVelZ, 0, maxAccel); - // in theory, the above is the correct calculation, but in practice, - // doesn't work in some situations (eg, opening of USA01 map). Why, I dunno. - // But for now I have gone back to the old, looks-incorrect-to-me-but-works calc. (srj) - Real brakeDist = (sqr(curVelZ) / fabs(maxAccel)); - if (fabs(brakeDist) > fabs(deltaZ)) - { - // if the dist-to-accel (or dist-to-brake) is further than the dist-to-go, - // use the max accel. - desiredAccel = maxAccel; - } - else if (fabs(curVelZ) > m_template->m_speedLimitZ) - { - // or, if we're going too fast, limit it here. - desiredAccel = m_template->m_speedLimitZ - curVelZ; - } - else - { - // ok, figure out the correct accel to use to get us there at zero. - // - // dz = v t + 0.5 a t^2 - // thus - // a = 2(dz - v t)/t^2 - // and - // t = (-v +- sqrt(v*v + 2*a*dz))/a - // - // but if we assume t=1, then - // a=2(dz-v) - // then, plug it back in and see if t is really 1... - desiredAccel = 2.0f * (deltaZ - curVelZ); - } - } - else - { - desiredAccel = 0.0f; - } - Real liftToUse = desiredAccel - TheGlobalData->m_gravity; - if (getFlag(ULTRA_ACCURATE)) - { - // in ultra-accurate mode, we allow cheating. - const Real UP_FACTOR = 3.0f; - if (liftToUse > UP_FACTOR*maxGrossLift) - liftToUse = UP_FACTOR*maxGrossLift; - // srj sez: we used to clip lift to zero here (not allowing neg lift). - // however, I now think that allowing neg lift in ultra-accurate mode is - // a good and desirable thing; in particular, it enables jets to complete - // "short" landings more accurately (previously they sometimes would "float" - // down, which sucked.) if you need to bump this back to zero, check it carefully... - else if (liftToUse < -maxGrossLift) - liftToUse = -maxGrossLift; - } - else - { - if (liftToUse > maxGrossLift) - liftToUse = maxGrossLift; - else if (liftToUse < 0.0f) - liftToUse = 0.0f; - } - - return liftToUse; -} - -//------------------------------------------------------------------------------------------------- -PhysicsTurningType Locomotor::rotateObjAroundLocoPivot(Object* obj, const Coord3D& goalPos, - Real maxTurnRate, Real *relAngle) -{ - Real angle = obj->getOrientation(); - Real offset = getTurnPivotOffset(); - - PhysicsTurningType turn = TURN_NONE; - - if (getFlag(IS_BRAKING)) offset = 0.0f; // When braking we do exact movement towards goal, instead of physics. - //Rotating about pivot moves the object, and can make us miss our goal, so it is disabled. jba. - if (offset != 0.0f) - { - Real radius = obj->getGeometryInfo().getBoundingCircleRadius(); - Real turnPointOffset = offset * radius; - - Coord3D turnPos = *obj->getPosition(); - const Coord3D* dir = obj->getUnitDirectionVector2D(); - turnPos.x += dir->x * turnPointOffset; - turnPos.y += dir->y * turnPointOffset; - Real dx =goalPos.x - turnPos.x; - Real dy = goalPos.y - turnPos.y; - // If we are very close to the goal, we twitch due to rounding error. So just return. jba. - if (fabs(dx)<0.1f && fabs(dy)<0.1f) return TURN_NONE; - Real desiredAngle = atan2(dy, dx); - Real amount = stdAngleDiff(desiredAngle, angle); - if (relAngle) *relAngle = amount; - if (amount>maxTurnRate) { - amount = maxTurnRate; - turn = TURN_POSITIVE; - } else if (amount < -maxTurnRate) { - amount = -maxTurnRate; - turn = TURN_NEGATIVE; - } else { - turn = TURN_NONE; - } - -#if 0 - Coord3D desiredPos = *obj->getPosition(); // well, desired Dir, anyway - desiredPos.x += Cos(angle + amount) * radius; - desiredPos.y += Sin(angle + amount) * radius; - - - // so, the thing is, we want to rotate ourselves so that our *center* is rotated - // by the given amount, but the rotation must be around turnPos. so do a little - // back-calculation. - Real angleDesiredForTurnPos = atan2(desiredPos.y - turnPos.y, desiredPos.x - turnPos.x); - amount = angleDesiredForTurnPos - angle; -#endif - /// @todo srj -- there's probably a more efficient & more direct way to do this. find it. - Matrix3D mtx; - Matrix3D tmp(1); - tmp.Translate(turnPos.x, turnPos.y, 0); - tmp.In_Place_Pre_Rotate_Z(amount); - tmp.Translate(-turnPos.x, -turnPos.y, 0); - - mtx.mul(tmp, *obj->getTransformMatrix()); - - obj->setTransformMatrix(&mtx); - } - else - { - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); - Real amount = stdAngleDiff(desiredAngle, angle); - if (relAngle) *relAngle = amount; - if (amount>maxTurnRate) { - amount = maxTurnRate; - turn = TURN_POSITIVE; - } else if (amount < -maxTurnRate) { - amount = -maxTurnRate; - turn = TURN_NEGATIVE; - } else { - turn = TURN_NONE; - } - obj->setOrientation( normalizeAngle(angle + amount) ); - } - return turn; -} - -//------------------------------------------------------------------------------------------------- -/* - return true if we can maintain the position without being called every frame (eg, we are - resting on the ground), false if not (eg, we are hovering or circling) -*/ -Bool Locomotor::handleBehaviorZ(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos) -{ - Bool requiresConstantCalling = TRUE; - - // keep the agent aligned on the terrain - switch(m_template->m_behaviorZ) - { - case Z_NO_Z_MOTIVE_FORCE: - // nothing to do. - requiresConstantCalling = FALSE; - break; - - case Z_SEA_LEVEL: - requiresConstantCalling = TRUE; - if( !obj->isDisabledByType( DISABLED_HELD ) ) - { - Coord3D pos = *obj->getPosition(); - Real waterZ; - if (TheTerrainLogic->isUnderwater(pos.x, pos.y, &waterZ)) { - pos.z = waterZ; - } else { - pos.z = TheTerrainLogic->getLayerHeight(pos.x, pos.y, obj->getLayer()); - } - obj->setPosition(&pos); - } - break; - - case Z_FIXED_SURFACE_RELATIVE_HEIGHT: - case Z_FIXED_ABSOLUTE_HEIGHT: - requiresConstantCalling = TRUE; - { - Coord3D pos = *obj->getPosition(); - Bool surfaceRel = (m_template->m_behaviorZ == Z_FIXED_SURFACE_RELATIVE_HEIGHT); - Real surfaceHt = surfaceRel ? getSurfaceHtAtPt(pos.x, pos.y) : 0.0f; - pos.z = m_preferredHeight + (surfaceRel ? surfaceHt : 0); - obj->setPosition(&pos); - } - break; - - case Z_RELATIVE_TO_GROUND_AND_BUILDINGS: - requiresConstantCalling = TRUE; - { - // srj sez: use getGroundOrStructureHeight(), because someday it will cache building heights... - Coord3D pos = *obj->getPosition(); - Real surfaceHt = ThePartitionManager->getGroundOrStructureHeight(pos.x, pos.y); - - pos.z = m_preferredHeight + surfaceHt; - - obj->setPosition(&pos); - - } - break; - case Z_SMOOTH_RELATIVE_TO_HIGHEST_LAYER: - requiresConstantCalling = TRUE; - { - if (m_preferredHeight != 0.0f || getFlag(PRECISE_Z_POS)) - { - Coord3D pos = *obj->getPosition(); - - // srj sez: if we aren't on the ground, never find the ground layer - PathfindLayerEnum layerAtDest = obj->getLayer(); - if (layerAtDest == LAYER_GROUND) - layerAtDest = TheTerrainLogic->getHighestLayerForDestination( &pos ); - - Real surfaceHt; - Coord3D normal; - const Bool clip = false; // return the height, even if off the edge of the bridge proper. - surfaceHt = TheTerrainLogic->getLayerHeight( pos.x, pos.y, layerAtDest, &normal, clip ); - - Real preferredHeight = m_preferredHeight + surfaceHt; - if (getFlag(PRECISE_Z_POS)) - preferredHeight = goalPos.z; - - Real delta = preferredHeight - pos.z; - delta *= getPreferredHeightDamping(); - preferredHeight = pos.z + delta; - - Real liftToUse = calcLiftToUseAtPt(obj, physics, pos.z, surfaceHt, preferredHeight); - - //DEBUG_LOG(("HandleBZ %d LiftToUse %f\n",TheGameLogic->getFrame(),liftToUse)); - if (liftToUse != 0.0f) - { - Coord3D force; - force.x = 0.0f; - force.y = 0.0f; - force.z = liftToUse * physics->getMass(); - physics->applyMotiveForce(&force); - } - } - } - break; - - case Z_SURFACE_RELATIVE_HEIGHT: - case Z_ABSOLUTE_HEIGHT: - requiresConstantCalling = TRUE; - { - if (m_preferredHeight != 0.0f || getFlag(PRECISE_Z_POS)) - { - Coord3D pos = *obj->getPosition(); - - Bool surfaceRel = (m_template->m_behaviorZ == Z_SURFACE_RELATIVE_HEIGHT); - Real surfaceHt = surfaceRel ? getSurfaceHtAtPt(pos.x, pos.y) : 0.0f; - Real preferredHeight = m_preferredHeight + (surfaceRel ? surfaceHt : 0); - if (getFlag(PRECISE_Z_POS)) - preferredHeight = goalPos.z; - - Real delta = preferredHeight - pos.z; - delta *= getPreferredHeightDamping(); - preferredHeight = pos.z + delta; - - Real liftToUse = calcLiftToUseAtPt(obj, physics, pos.z, surfaceHt, preferredHeight); - - //DEBUG_LOG(("HandleBZ %d LiftToUse %f\n",TheGameLogic->getFrame(),liftToUse)); - if (liftToUse != 0.0f) - { - Coord3D force; - force.x = 0.0f; - force.y = 0.0f; - force.z = liftToUse * physics->getMass(); - physics->applyMotiveForce(&force); - } - } - } - break; - } - - return requiresConstantCalling; -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxAcceleration = getMaxAcceleration(bdt); - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - Real maxSpeed = getMaxSpeedForCondition(bdt); - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - Real goalSpeed = desiredSpeed; - Real actualSpeed = physics->getForwardSpeed2D(); - - // Locomotion for other things, ie don't know what it is jba :) - // - // Orient toward goal position - // exception: if very close (ie, we could get there in 2 frames or less),\ - // and ULTRA_ACCURATE, just slide into place - // - const Coord3D* pos = obj->getPosition(); - Coord3D dirToApplyForce = *obj->getUnitDirectionVector2D(); - -//DEBUG_ASSERTLOG(!getFlag(ULTRA_ACCURATE),("thresh %f %f (%f %f)\n", -//fabs(goalPos.y - pos->y),fabs(goalPos.x - pos->x), -//fabs(goalPos.y - pos->y)/goalSpeed,fabs(goalPos.x - pos->x)/goalSpeed)); - if (getFlag(ULTRA_ACCURATE) && - fabs(goalPos.y - pos->y) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor && - fabs(goalPos.x - pos->x) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor) - { - // don't turn, just slide in the right direction - physics->setTurning(TURN_NONE); - dirToApplyForce.x = goalPos.x - pos->x; - dirToApplyForce.y = goalPos.y - pos->y; - dirToApplyForce.z = 0.0f; - dirToApplyForce.normalize(); - } - else - { - PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos); - physics->setTurning(rotating); - } - - if (!getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - { - Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); - if (onPathDistToGoal < slowDownDist) - { - goalSpeed = m_template->m_minSpeed; - } - } - - // - // Maintain goal speed - // - Real speedDelta = goalSpeed - actualSpeed; - if (speedDelta != 0.0f) - { - Real mass = physics->getMass(); - Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - Coord3D force; - force.x = accelForce * dirToApplyForce.x; - force.y = accelForce * dirToApplyForce.y; - force.z = 0.0f; - - // apply forces to object - physics->applyMotiveForce( &force ); - } - -} - - -//------------------------------------------------------------------------------------------------- -/* - return true if we can maintain the position without being called every frame (eg, we are - resting on the ground), false if not (eg, we are hovering or circling) -*/ -Bool Locomotor::locoUpdate_maintainCurrentPosition(Object* obj) -{ - if (!getFlag(MAINTAIN_POS_IS_VALID)) - { - m_maintainPos = *obj->getPosition(); - setFlag(MAINTAIN_POS_IS_VALID, true); - } - - m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; - setFlag(IS_BRAKING, false); - PhysicsBehavior *physics = obj->getPhysics(); - if (physics == NULL) - { - DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); - return TRUE; - } - -#ifdef DEBUG_OBJECT_ID_EXISTS -// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_maintainCurrentPosition %f %f %f, speed %f (%f)\n",m_maintainPos.x,m_maintainPos.y,m_maintainPos.z,physics->getSpeed(),physics->getForwardSpeed2D())); -#endif - - Bool requiresConstantCalling = TRUE; // assume the worst. - switch (m_template->m_appearance) - { - case LOCO_THRUST: - maintainCurrentPositionThrust(obj, physics); - requiresConstantCalling = TRUE; - break; - case LOCO_LEGS_TWO: - maintainCurrentPositionLegs(obj, physics); - requiresConstantCalling = FALSE; - break; - case LOCO_CLIMBER: - maintainCurrentPositionLegs(obj, physics); - requiresConstantCalling = FALSE; - break; - case LOCO_WHEELS_FOUR: - case LOCO_MOTORCYCLE: - maintainCurrentPositionWheels(obj, physics); - requiresConstantCalling = FALSE; - break; - case LOCO_TREADS: - maintainCurrentPositionTreads(obj, physics); - requiresConstantCalling = FALSE; - break; - case LOCO_HOVER: - maintainCurrentPositionHover(obj, physics); - requiresConstantCalling = TRUE; - break; - case LOCO_WINGS: - maintainCurrentPositionWings(obj, physics); - requiresConstantCalling = TRUE; - break; - case LOCO_OTHER: - default: - maintainCurrentPositionOther(obj, physics); - requiresConstantCalling = TRUE; - break; - } - - // but we do need to do this even if not moving, for hovering/Thrusting things. - if (handleBehaviorZ(obj, physics, m_maintainPos)) - requiresConstantCalling = TRUE; - - return requiresConstantCalling; -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::maintainCurrentPositionThrust(Object* obj, PhysicsBehavior *physics) -{ - DEBUG_ASSERTCRASH(getFlag(MAINTAIN_POS_IS_VALID), ("invalid maintain pos")); - /// @todo srj -- should these also use the "circling radius" stuff, like wings? - moveTowardsPositionThrust(obj, physics, m_maintainPos, 0, getMinSpeed()); -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physics) -{ - DEBUG_ASSERTCRASH(getFlag(MAINTAIN_POS_IS_VALID), ("invalid maintain pos")); - physics->setTurning(TURN_NONE); - if (physics->isMotive() && obj->isAboveTerrain()) // no need to stop something that isn't moving (or is just sitting on the ground) - { - - // aim for the spot on the opposite side of the circle. - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real turnRadius = m_template->m_circlingRadius; - if (turnRadius == 0.0f) - turnRadius = calcMinTurnRadius(bdt, NULL); - - // find the direction towards our "maintain pos" - const Coord3D* pos = obj->getPosition(); - Real dx = m_maintainPos.x - pos->x; - Real dy = m_maintainPos.y - pos->y; - Real angleTowardMaintainPos = - (isNearlyZero(dx) && isNearlyZero(dy)) ? - obj->getOrientation() : - atan2(dy, dx); - - Real aimDir = (PI - PI/8); - if (turnRadius < 0) - { - turnRadius = -turnRadius; - aimDir = -aimDir; - } - angleTowardMaintainPos += aimDir; - - // project a spot "radius" dist away from it, in that dir - Coord3D desiredPos = m_maintainPos; - desiredPos.x += Cos(angleTowardMaintainPos) * turnRadius; - desiredPos.y += Sin(angleTowardMaintainPos) * turnRadius; - moveTowardsPositionWings(obj, physics, desiredPos, 0, m_template->m_minSpeed); - } -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physics) -{ - physics->setTurning(TURN_NONE); - if (physics->isMotive()) // no need to stop something that isn't moving. - { - DEBUG_ASSERTCRASH(m_template->m_minSpeed == 0.0f, ("HOVER should always have zero minSpeeds (otherwise, they WING)")); - - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxAcceleration = getMaxAcceleration(bdt); - Real actualSpeed = physics->getForwardSpeed2D(); - // - // Stop - // - Real minSpeed = max( 1.0E-10f, m_template->m_minSpeed ); - Real speedDelta = minSpeed - actualSpeed; - if (fabs(speedDelta) > minSpeed) - { - Real mass = physics->getMass(); - Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - const Coord3D *dir = obj->getUnitDirectionVector2D(); - - Coord3D force; - force.x = accelForce * dir->x; - force.y = accelForce * dir->y; - force.z = 0.0f; - - - // Apply a random kick (if applicable) to dirty-up visually. - // The idea is that chopper pilots have to do course corrections all the time - // Because of changes in wind, pressure, etc. - // Those changes are added here, then the - - - - // apply forces to object - physics->applyMotiveForce( &force ); - } - } - -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::maintainCurrentPositionOther(Object* obj, PhysicsBehavior *physics) -{ - - physics->setTurning(TURN_NONE); - if (physics->isMotive()) // no need to stop something that isn't moving. - { - physics->scrubVelocity2D(0); // stop. - } - -} - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- -LocomotorSet::LocomotorSet() -{ - m_locomotors.clear(); - m_validLocomotorSurfaces = 0; - m_downhillOnly = FALSE; - -} - -//------------------------------------------------------------------------------------------------- -LocomotorSet::LocomotorSet(const LocomotorSet& that) -{ - DEBUG_CRASH(("unimplemented")); -} - -//------------------------------------------------------------------------------------------------- -LocomotorSet& LocomotorSet::operator=(const LocomotorSet& that) -{ - if (this != &that) - { - DEBUG_CRASH(("unimplemented")); - } - return *this; -} - -//------------------------------------------------------------------------------------------------- -LocomotorSet::~LocomotorSet() -{ - clear(); -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void LocomotorSet::crc( Xfer *xfer ) -{ - -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void LocomotorSet::xfer( Xfer *xfer ) -{ - // version - const XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // count of vector - UnsignedShort count = m_locomotors.size(); - xfer->xferUnsignedShort( &count ); - - // data - if (xfer->getXferMode() == XFER_SAVE) - { - for (LocomotorVector::iterator it = m_locomotors.begin(); it != m_locomotors.end(); ++it) - { - Locomotor* loco = *it; - AsciiString name = loco->getTemplateName(); - xfer->xferAsciiString(&name); - xfer->xferSnapshot(loco); - } - } - else if (xfer->getXferMode() == XFER_LOAD) - { - // vector should be empty at this point - if (m_locomotors.empty() == FALSE) - { - DEBUG_CRASH(( "LocomotorSet::xfer - vector is not empty, but should be\n" )); - throw XFER_LIST_NOT_EMPTY; - } - - for (UnsignedShort i = 0; i < count; ++i) - { - AsciiString name; - xfer->xferAsciiString(&name); - - const LocomotorTemplate* lt = TheLocomotorStore->findLocomotorTemplate(NAMEKEY(name)); - if (lt == NULL) - { - DEBUG_CRASH(( "LocomotorSet::xfer - template %s not found\n", name.str() )); - throw XFER_UNKNOWN_STRING; - } - - Locomotor* loco = TheLocomotorStore->newLocomotor(lt); - xfer->xferSnapshot(loco); - m_locomotors.push_back(loco); - } - } - - xfer->xferInt(&m_validLocomotorSurfaces); - xfer->xferBool(&m_downhillOnly); - -} - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void LocomotorSet::loadPostProcess( void ) -{ - -} // end loadPostProcess - -//------------------------------------------------------------------------------------------------- -void LocomotorSet::xferSelfAndCurLocoPtr(Xfer *xfer, Locomotor** loco) -{ - xfer->xferSnapshot(this); - - if (xfer->getXferMode() == XFER_SAVE) - { - AsciiString name; - if (*loco) - name = (*loco)->getTemplateName(); - xfer->xferAsciiString(&name); - } - else if (xfer->getXferMode() == XFER_LOAD) - { - AsciiString name; - xfer->xferAsciiString(&name); - - if (name.isEmpty()) - { - *loco = NULL; - } - else - { - for (int i = 0; i < m_locomotors.size(); ++i) - { - if (m_locomotors[i]->getTemplateName() == name) - { - *loco = m_locomotors[i]; - return; - } - } - - DEBUG_CRASH(( "LocomotorSet::xfer - template %s not found\n", name.str() )); - throw XFER_UNKNOWN_STRING; - } - } -} - -//------------------------------------------------------------------------------------------------- -void LocomotorSet::clear() -{ - for (int i = 0; i < m_locomotors.size(); ++i) - { - if (m_locomotors[i]) - m_locomotors[i]->deleteInstance(); - } - m_locomotors.clear(); - m_validLocomotorSurfaces = 0; - m_downhillOnly = FALSE; -} - -//------------------------------------------------------------------------------------------------- -void LocomotorSet::addLocomotor(const LocomotorTemplate* lt) -{ - Locomotor* loco = TheLocomotorStore->newLocomotor(lt); - if (loco) - { - m_locomotors.push_back(loco); - m_validLocomotorSurfaces |= loco->getLegalSurfaces(); - if (loco->getIsDownhillOnly()) - { - m_downhillOnly = TRUE; - } - else // Previous locos were gravity only, but this one isn't! - { - DEBUG_ASSERTCRASH(!m_downhillOnly,("LocomotorSet, YOU CAN NOT MIX DOWNHILL-ONLY LOCOMOTORS WITH NON-DOWNHILL-ONLY ONES.")); - } - - } -} - -//------------------------------------------------------------------------------------------------- -Locomotor* LocomotorSet::findLocomotor(LocomotorSurfaceTypeMask t) -{ - for (LocomotorVector::iterator it = m_locomotors.begin(); it != m_locomotors.end(); ++it) - { - Locomotor* curLocomotor = *it; - if (curLocomotor && (curLocomotor->getLegalSurfaces() & t)) - return curLocomotor; - } - return NULL; -} - - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: Locomotor.cpp /////////////////////////////////////////////////////////////////////////////// +// Author: Steven Johnson, Feb 2002 +// Desc: Locomotor descriptions +/////////////////////////////////////////////////////////////////////////////////////////////////// + + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#define DEFINE_SURFACECATEGORY_NAMES +#define DEFINE_LOCO_Z_NAMES +#define DEFINE_LOCO_APPEARANCE_NAMES + +#include "Common/INI.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/PartitionManager.h" +#include "GameLogic/Locomotor.h" +#include "GameLogic/Object.h" +#include "GameLogic/AI.h" +#include "GameLogic/AIPathfind.h" +#include "GameLogic/Module/PhysicsUpdate.h" +#include "GameLogic/Module/BodyModule.h" +#include "GameLogic/Module/AIUpdate.h" + +#ifdef RTS_INTERNAL +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +static const Real DONUT_TIME_DELAY_SECONDS=2.5f; +static const Real DONUT_DISTANCE=4.0*PATHFIND_CELL_SIZE_F; + + +#define MAX_BRAKING_FACTOR 5.0f +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +LocomotorStore *TheLocomotorStore = NULL; ///< the Locomotor store definition + +const Real BIGNUM = 99999.0f; + +static const char *TheLocomotorPriorityNames[] = +{ + "MOVES_BACK", + "MOVES_MIDDLE", + "MOVES_FRONT", + + NULL +}; + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +static Real calcSlowDownDist(Real curSpeed, Real desiredSpeed, Real maxBraking) +{ + Real delta = curSpeed - desiredSpeed; + if (delta <= 0) + return 0.0f; + + Real dist = (sqr(delta) / fabs(maxBraking)) * 0.5f; + + // use a little fudge so that things can stop "on a dime" more easily... + const Real FUDGE = 1.05f; + return dist * FUDGE; +} + +//----------------------------------------------------------------------------- +inline Bool isNearlyZero(Real a) +{ + const Real TINY_EPSILON = 0.001f; + return fabs(a) < TINY_EPSILON; +} + +//----------------------------------------------------------------------------- +inline Bool isNearly(Real a, Real val) +{ + const Real TINY_EPSILON = 0.001f; + return fabs(a - val) < TINY_EPSILON; +} + +//----------------------------------------------------------------------------- +// return the angle delta (in 3-space) we turned. +static Real tryToRotateVector3D( + Real maxAngle, // if negative, it's a percent (0...1) of the dist to rotate 'em + const Vector3& inCurDir, + const Vector3& inGoalDir, + Vector3& actualDir +) +{ + if (isNearlyZero(maxAngle)) + { + actualDir = inCurDir; + return 0.0f; + } + + Vector3 curDir = inCurDir; + curDir.Normalize(); + + Vector3 goalDir = inGoalDir; + goalDir.Normalize(); + + // dot of two unit vectors is cos of angle between them. + Real cosine = Vector3::Dot_Product(curDir, goalDir); + // bound it in case of numerical error + Real angleBetween = (Real)ACos(clamp(-1.0f, cosine, 1.0f)); + + if (maxAngle < 0) + { + maxAngle = -maxAngle * angleBetween; + if (isNearlyZero(maxAngle)) + { + actualDir = inCurDir; + return 0.0f; + } + } + + if (fabs(angleBetween) <= maxAngle) + { + // close enough + actualDir = goalDir; + } + else + { + // nah, try as much as we can in the right dir. + // we need to rotate around the axis perpendicular to these two vecs. + // but: cross of two vectors is the perpendicular axis! +#ifdef ALLOW_TEMPORARIES + Vector3 objCrossGoal = Vector3::Cross_Product(curDir, goalDir); + objCrossGoal.Normalize(); +#else + Vector3 objCrossGoal; + Vector3::Normalized_Cross_Product(curDir, goalDir, &objCrossGoal); +#endif + + angleBetween = maxAngle; + Matrix3D rotMtx(objCrossGoal, angleBetween); + actualDir = rotMtx.Rotate_Vector(curDir); + } + + return angleBetween; +} + +//------------------------------------------------------------------------------------------------- +static Real tryToOrientInThisDirection3D(Object* obj, Real maxTurnRate, const Vector3& desiredDir) +{ + Vector3 actualDir; + Real relAngle = tryToRotateVector3D(maxTurnRate, obj->getTransformMatrix()->Get_X_Vector(), desiredDir, actualDir); + if (relAngle != 0.0f) + { + Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z); + + Matrix3D newXform; + newXform.buildTransformMatrix( objPos, actualDir ); + + obj->setTransformMatrix( &newXform ); + } + return relAngle; +} + +//------------------------------------------------------------------------------------------------- +inline Real tryToOrientInThisDirection3D(Object* obj, Real maxTurnRate, const Coord3D* dir) +{ + return tryToOrientInThisDirection3D(obj, maxTurnRate, Vector3(dir->x, dir->y, dir->z)); +} + +//----------------------------------------------------------------------------- +static void calcDirectionToApplyThrust( + const Object* obj, + const PhysicsBehavior* physics, + const Coord3D& ingoalPos, + Real maxAccel, + Vector3& goalDir +) +{ + /* + our meta-goal here is to calculate the direction we should apply our motive force + in order to minimize the angle between (our velocity) and (direction towards goalpos). + + this is complicated by the fact that we generally have an intrinsic velocity already, + that must be accounted for, and by the fact that we can only apply force in our + forward-x-direction (with a thrust-angle-range), and (due to limited range) might not + be able to apply the force in the optimal direction! + */ + + // convert to Vector3, to use all its handy stuff + Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z); + Vector3 goalPos(ingoalPos.x, ingoalPos.y, ingoalPos.z); + + Vector3 vecToGoal = goalPos - objPos; + if (isNearlyZero(vecToGoal.Length2())) + { + // goal pos is essentially same as current pos, so just stay the same & return + goalDir = obj->getTransformMatrix()->Get_X_Vector(); + return; + } + + /* + get our cur vel into a useful Vector3 form + */ + Vector3 curVel(physics->getVelocity()->x, physics->getVelocity()->y, physics->getVelocity()->z); + + // add gravity to our vel so that we account for it in our calcs + curVel.Z += TheGlobalData->m_gravity; + + Bool foundSolution = false; + Real distToGoalSqr = vecToGoal.Length2(); + Real distToGoal = sqrt(distToGoalSqr); + Real curVelMagSqr = curVel.Length2(); + Real curVelMag = sqrt(curVelMagSqr); + Real maxAccelSqr = sqr(maxAccel); + + Real denom = curVelMagSqr - maxAccelSqr; + if (!isNearlyZero(denom)) + { + // solve the (greatly simplified) quadratic... + Real t = (distToGoal * (curVelMag + maxAccel)) / denom; + Real t2 = (distToGoal * (curVelMag - maxAccel)) / denom; + if (t >= 0 || t2 >= 0) + { + // choose the smallest positive t. + if (t < 0 || (t2 >= 0 && t2 < t)) + t = t2; + + // plug it in. + if (!isNearlyZero(t)) + { + goalDir.X = (vecToGoal.X / t) - curVel.X; + goalDir.Y = (vecToGoal.Y / t) - curVel.Y; + goalDir.Z = (vecToGoal.Z / t) - curVel.Z; + goalDir.Normalize(); + foundSolution = true; + } + } + } + if (!foundSolution) + { + // Doh... no (useful) solution. revert to dumb. + goalDir = vecToGoal; + goalDir.Normalize(); + } + +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +LocomotorTemplate::LocomotorTemplate() +{ + // these values mean "make the same as undamaged if not explicitly specified" + m_maxSpeedDamaged = -1.0f; + m_maxTurnRateDamaged = -1.0f; + m_accelerationDamaged = -1.0f; + m_liftDamaged = -1.0f; + + m_surfaces = 0; + m_maxSpeed = 0.0f; + m_maxTurnRate = 0.0f; + m_acceleration = 0.0f; + m_lift = 0.0f; + m_braking = BIGNUM; + m_minSpeed = 0.0f; + m_minTurnSpeed = BIGNUM; + m_behaviorZ = Z_NO_Z_MOTIVE_FORCE; + m_appearance = LOCO_OTHER; + m_movePriority = LOCO_MOVES_MIDDLE; + m_preferredHeight = 0; + m_preferredHeightDamping = 1.0f; + m_circlingRadius = 0; + + m_maxThrustAngle = 0; + m_speedLimitZ = 999999.0f; + m_extra2DFriction = 0.0f; + + m_accelPitchLimit = 0; + m_decelPitchLimit = 0; + m_bounceKick = 0; + +// m_pitchStiffness = 0; +// m_rollStiffness = 0; +// m_pitchDamping = 0; +// m_rollDamping = 0; +// it's highly unlikely you want zero for the defaults for stiffness and damping... (srj) +// for stiffness: stiffness of the "springs" in the suspension 0 = no stiffness, 1 = totally stiff (huh huh, he said "stiff") +// for damping: 0=perfect spring, bounces forever. 1=glued to terrain. + m_pitchStiffness = 0.1f; + m_rollStiffness = 0.1f; + m_pitchDamping = 0.9f; + m_rollDamping = 0.9f; + m_forwardVelCoef = 0; + m_pitchByZVelCoef = 0; + m_thrustRoll = 0.0f; + m_wobbleRate = 0.0f; + m_minWobble = 0.0f; + m_maxWobble = 0.0f; + m_lateralVelCoef = 0; + m_forwardAccelCoef = 0; + m_lateralAccelCoef = 0; + m_uniformAxialDamping = 1.0f; + m_turnPivotOffset = 0; + m_apply2DFrictionWhenAirborne = false; + m_downhillOnly = false; + m_allowMotiveForceWhileAirborne = false; + m_locomotorWorksWhenDead = false; + m_airborneTargetingHeight = INT_MAX; + m_stickToGround = false; + m_canMoveBackward = false; + m_hasSuspension = false; + m_wheelTurnAngle = 0; + m_maximumWheelExtension = 0; + m_maximumWheelCompression = 0; + m_closeEnoughDist = 1.0f; + m_isCloseEnoughDist3D = FALSE; + m_ultraAccurateSlideIntoPlaceFactor = 0.0f; + + m_wanderWidthFactor = 0.0f; + m_wanderLengthFactor = 1.0f; + m_wanderAboutPointRadius = 0.0f; + + m_rudderCorrectionDegree = 0.0f; + m_rudderCorrectionRate = 0.0f; + m_elevatorCorrectionDegree = 0.0f; + m_elevatorCorrectionRate = 0.0f; + +} + +//------------------------------------------------------------------------------------------------- +LocomotorTemplate::~LocomotorTemplate() +{ + +} + +//------------------------------------------------------------------------------------------------- +void LocomotorTemplate::validate() +{ + // this is ok; parachutes need it! + //DEBUG_ASSERTCRASH(m_lift == 0.0f || m_lift > fabs(TheGlobalData->m_gravity), ("Lift is too low to counteract gravity!")); + //DEBUG_ASSERTCRASH(m_liftDamaged == 0.0f || m_liftDamaged > fabs(TheGlobalData->m_gravity), ("LiftDamaged is too low to counteract gravity!")); + //DEBUG_ASSERTCRASH(m_preferredHeight == 0.0f || (m_behaviorZ == Z_SURFACE_RELATIVE_HEIGHT || m_behaviorZ == Z_ABSOLUTE_HEIGHT || m_appearance == LOCO_THRUST), + // ("You must use Z_SURFACE_RELATIVE_HEIGHT or Z_ABSOLUTE_HEIGHT (or THRUST) to use preferredHeight")); + + // for 'damaged' stuff that was omitted, set 'em to be the same as 'undamaged'... + if (m_maxSpeedDamaged < 0.0f) + m_maxSpeedDamaged = m_maxSpeed; + + if (m_maxTurnRateDamaged < 0.0f) + m_maxTurnRateDamaged = m_maxTurnRate; + + if (m_accelerationDamaged < 0.0f) + m_accelerationDamaged = m_acceleration; + + if (m_liftDamaged < 0.0f) + m_liftDamaged = m_lift; + + if (m_appearance == LOCO_WINGS) + { + if (m_minSpeed <= 0.0f) + { + DEBUG_CRASH(("WINGS should always have positive minSpeeds (otherwise, they hover)")); + m_minSpeed = 0.01f; + } + if (m_minTurnSpeed <= 0.0f) + { + DEBUG_CRASH(("WINGS should always have positive minTurnSpeed")); + m_minTurnSpeed = 0.01f; + } + } + + if (m_appearance == LOCO_THRUST) + { + if (m_behaviorZ != Z_NO_Z_MOTIVE_FORCE || + m_lift != 0.0f || + m_liftDamaged != 0.0f) + { + DEBUG_CRASH(("THRUST locos may not use ZAxisBehavior or lift!\n")); + throw INI_INVALID_DATA; + } + if (m_maxSpeed <= 0.0f) + { + // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing + DEBUG_LOG(("THRUST locos may not have zero m_maxSpeed; healing...\n")); + m_maxSpeed = 0.01f; + } + if (m_maxSpeedDamaged <= 0.0f) + { + // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing + DEBUG_LOG(("THRUST locos may not have zero m_maxSpeedDamaged; healing...\n")); + m_maxSpeedDamaged = 0.01f; + } + if (m_minSpeed <= 0.0f) + { + // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing + DEBUG_LOG(("THRUST locos may not have zero m_minSpeed; healing...\n")); + m_minSpeed = 0.01f; + } + } +} + +//------------------------------------------------------------------------------------------------- +static void parseFrictionPerSec( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + Real fricPerSec = INI::scanReal(ini->getNextToken()); + Real fricPerFrame = fricPerSec * SECONDS_PER_LOGICFRAME_REAL; + *(Real *)store = fricPerFrame; +} + +//------------------------------------------------------------------------------------------------- +const FieldParse* LocomotorTemplate::getFieldParse() const +{ + static const FieldParse TheFieldParse[] = + { + { "Surfaces", INI::parseBitString32, TheLocomotorSurfaceTypeNames, offsetof(LocomotorTemplate, m_surfaces) }, + { "Speed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_maxSpeed) }, + { "SpeedDamaged", INI::parseVelocityReal, NULL, offsetof( LocomotorTemplate, m_maxSpeedDamaged ) }, + { "TurnRate", INI::parseAngularVelocityReal, NULL, offsetof(LocomotorTemplate, m_maxTurnRate) }, + { "TurnRateDamaged", INI::parseAngularVelocityReal, NULL, offsetof( LocomotorTemplate, m_maxTurnRateDamaged ) }, + { "Acceleration", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_acceleration) }, + { "AccelerationDamaged", INI::parseAccelerationReal, NULL, offsetof( LocomotorTemplate, m_accelerationDamaged ) }, + { "Lift", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_lift) }, + { "LiftDamaged", INI::parseAccelerationReal, NULL, offsetof( LocomotorTemplate, m_liftDamaged ) }, + { "Braking", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_braking) }, + { "MinSpeed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_minSpeed) }, + { "MinTurnSpeed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_minTurnSpeed) }, + { "PreferredHeight", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_preferredHeight) }, + { "PreferredHeightDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_preferredHeightDamping) }, + { "CirclingRadius", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_circlingRadius) }, + { "Extra2DFriction", parseFrictionPerSec, NULL, offsetof(LocomotorTemplate, m_extra2DFriction) }, + { "SpeedLimitZ", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_speedLimitZ) }, + { "MaxThrustAngle", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_maxThrustAngle) }, // yes, angle, not angular-vel + { "ZAxisBehavior", INI::parseIndexList, TheLocomotorBehaviorZNames, offsetof(LocomotorTemplate, m_behaviorZ) }, + { "Appearance", INI::parseIndexList, TheLocomotorAppearanceNames, offsetof(LocomotorTemplate, m_appearance) }, \ + { "GroupMovementPriority", INI::parseIndexList, TheLocomotorPriorityNames, offsetof(LocomotorTemplate, m_movePriority) }, \ + + { "AccelerationPitchLimit", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_accelPitchLimit) }, + { "DecelerationPitchLimit", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_decelPitchLimit) }, + { "BounceAmount", INI::parseAngularVelocityReal, NULL, offsetof(LocomotorTemplate, m_bounceKick) }, + { "PitchStiffness", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchStiffness) }, + { "RollStiffness", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rollStiffness) }, + { "PitchDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchDamping) }, + { "RollDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rollDamping) }, + { "ThrustRoll", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_thrustRoll) }, + { "ThrustWobbleRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wobbleRate) }, + { "ThrustMinWobble", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_minWobble) }, + { "ThrustMaxWobble", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maxWobble) }, + { "PitchInDirectionOfZVelFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchByZVelCoef) }, + { "ForwardVelocityPitchFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_forwardVelCoef) }, + { "LateralVelocityRollFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_lateralVelCoef) }, + { "ForwardAccelerationPitchFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_forwardAccelCoef) }, + { "LateralAccelerationRollFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_lateralAccelCoef) }, + { "UniformAxialDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_uniformAxialDamping) }, + { "TurnPivotOffset", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_turnPivotOffset) }, + { "Apply2DFrictionWhenAirborne", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_apply2DFrictionWhenAirborne) }, + { "DownhillOnly", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_downhillOnly) }, + { "AllowAirborneMotiveForce", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_allowMotiveForceWhileAirborne) }, + { "LocomotorWorksWhenDead", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_locomotorWorksWhenDead) }, + { "AirborneTargetingHeight", INI::parseInt, NULL, offsetof( LocomotorTemplate, m_airborneTargetingHeight ) }, + { "StickToGround", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_stickToGround) }, + { "CanMoveBackwards", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_canMoveBackward) }, + { "HasSuspension", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_hasSuspension) }, + { "FrontWheelTurnAngle", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_wheelTurnAngle) }, + { "MaximumWheelExtension", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maximumWheelExtension) }, + { "MaximumWheelCompression", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maximumWheelCompression) }, + { "CloseEnoughDist", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_closeEnoughDist) }, + { "CloseEnoughDist3D", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_isCloseEnoughDist3D) }, + { "SlideIntoPlaceTime", INI::parseDurationReal, NULL, offsetof(LocomotorTemplate, m_ultraAccurateSlideIntoPlaceFactor) }, + + { "WanderWidthFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderWidthFactor) }, + { "WanderLengthFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderLengthFactor) }, + { "WanderAboutPointRadius", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderAboutPointRadius) }, + + { "RudderCorrectionDegree", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rudderCorrectionDegree) }, + { "RudderCorrectionRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rudderCorrectionRate) }, + { "ElevatorCorrectionDegree", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_elevatorCorrectionDegree) }, + { "ElevatorCorrectionRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_elevatorCorrectionRate) }, + { NULL, NULL, NULL, 0 } // keep this last + + }; + return TheFieldParse; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +LocomotorStore::LocomotorStore() +{ +} + +//------------------------------------------------------------------------------------------------- +LocomotorStore::~LocomotorStore() +{ + // delete all the templates, then clear out the table. + LocomotorTemplateMap::iterator it; + for (it = m_locomotorTemplates.begin(); it != m_locomotorTemplates.end(); ++it) { + it->second->deleteInstance(); + } + + m_locomotorTemplates.clear(); +} + +//------------------------------------------------------------------------------------------------- +LocomotorTemplate* LocomotorStore::findLocomotorTemplate(NameKeyType namekey) +{ + if (namekey == NAMEKEY_INVALID) + return NULL; + + LocomotorTemplateMap::iterator it = m_locomotorTemplates.find(namekey); + if (it == m_locomotorTemplates.end()) + return NULL; + else + return (*it).second; +} + +//------------------------------------------------------------------------------------------------- +const LocomotorTemplate* LocomotorStore::findLocomotorTemplate(NameKeyType namekey) const +{ + if (namekey == NAMEKEY_INVALID) + return NULL; + + LocomotorTemplateMap::const_iterator it = m_locomotorTemplates.find(namekey); + if (it == m_locomotorTemplates.end()) + { + return NULL; + } + else + { + return (*it).second; + } +} + +//------------------------------------------------------------------------------------------------- +void LocomotorStore::update() +{ +} + +//------------------------------------------------------------------------------------------------- +void LocomotorStore::reset() +{ + // cleanup overrides. + LocomotorTemplateMap::iterator it; + for (it = m_locomotorTemplates.begin(); it != m_locomotorTemplates.end(); ) { + Overridable *locoTemp = it->second->deleteOverrides(); + if (!locoTemp) + { + m_locomotorTemplates.erase(it); + } + else + { + ++it; + } + } +} + +//------------------------------------------------------------------------------------------------- +LocomotorTemplate *LocomotorStore::newOverride( LocomotorTemplate *locoTemplate ) +{ + if (locoTemplate == NULL) + return NULL; + + // allocate new template + LocomotorTemplate *newTemplate = newInstance(LocomotorTemplate); + + // copy data from final override to 'newTemplate' as a set of initial default values + *newTemplate = *locoTemplate; + locoTemplate->setNextOverride(newTemplate); + + newTemplate->markAsOverride(); + + // return the newly created override for us to set values with etc + return newTemplate; + +} // end newOverride + +//------------------------------------------------------------------------------------------------- +/*static*/ void LocomotorStore::parseLocomotorTemplateDefinition(INI* ini) +{ + if (!TheLocomotorStore) + throw INI_INVALID_DATA; + + Bool isOverride = false; + // read the Locomotor name + const char* token = ini->getNextToken(); + NameKeyType namekey = NAMEKEY(token); + + LocomotorTemplate *loco = TheLocomotorStore->findLocomotorTemplate(namekey); + if (loco) { + if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES) { + loco = TheLocomotorStore->newOverride((LocomotorTemplate*) loco->friend_getFinalOverride()); + } + isOverride = true; + } else { + loco = newInstance(LocomotorTemplate); + if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES) { + loco->markAsOverride(); + } + } + + loco->friend_setName(token); + ini->initFromINI(loco, loco->getFieldParse()); + loco->validate(); + + // if this is an override, then we want the pointer on the existing named locomotor to point us + // to the override, so don't add it to the map. + if (!isOverride) + TheLocomotorStore->m_locomotorTemplates[namekey] = loco; +} + +//------------------------------------------------------------------------------------------------- +/*static*/ void INI::parseLocomotorTemplateDefinition( INI* ini ) +{ + LocomotorStore::parseLocomotorTemplateDefinition(ini); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- +Locomotor::Locomotor(const LocomotorTemplate* tmpl) +{ + m_template = tmpl; + m_brakingFactor = 1.0f; + m_maxLift = BIGNUM; + m_maxSpeed = BIGNUM; + m_maxAccel = BIGNUM; + m_maxBraking = BIGNUM; + m_maxTurnRate = BIGNUM; + m_flags = 0; + m_closeEnoughDist = m_template->m_closeEnoughDist; + setFlag(IS_CLOSE_ENOUGH_DIST_3D, m_template->m_isCloseEnoughDist3D); +#ifdef CIRCLE_FOR_LANDING + m_circleThresh = 0.0f; +#endif + m_preferredHeight = m_template->m_preferredHeight; + m_preferredHeightDamping = m_template->m_preferredHeightDamping; + + m_angleOffset = GameLogicRandomValueReal(-PI/6, PI/6); + m_offsetIncrement = (PI/40) * (GameLogicRandomValueReal(0.8f, 1.2f)/m_template->m_wanderLengthFactor); + setFlag(OFFSET_INCREASING, GameLogicRandomValue(0,1)); + m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; + + m_speedMultiplier = 1.0; +} + +//------------------------------------------------------------------------------------------------- +Locomotor::Locomotor(const Locomotor& that) +{ + //Added By Sadullah Nader + //Initializations + m_angleOffset = 0.0f; + m_maintainPos.zero(); + + // + + m_template = that.m_template; + m_brakingFactor = that.m_brakingFactor; + m_maxLift = that.m_maxLift; + m_maxSpeed = that.m_maxSpeed; + m_maxAccel = that.m_maxAccel; + m_maxBraking = that.m_maxBraking; + m_maxTurnRate = that.m_maxTurnRate; + m_flags = that.m_flags; + m_closeEnoughDist = that.m_closeEnoughDist; +#ifdef CIRCLE_FOR_LANDING + m_circleThresh = that.m_circleThresh; +#endif + m_preferredHeight = that.m_preferredHeight; + m_preferredHeightDamping = that.m_preferredHeightDamping; + m_angleOffset = that.m_angleOffset; + m_offsetIncrement = that.m_offsetIncrement; +} + +//------------------------------------------------------------------------------------------------- +Locomotor& Locomotor::operator=(const Locomotor& that) +{ + if (this != &that) + { + m_template = that.m_template; + m_brakingFactor = that.m_brakingFactor; + m_maxLift = that.m_maxLift; + m_maxSpeed = that.m_maxSpeed; + m_maxAccel = that.m_maxAccel; + m_maxBraking = that.m_maxBraking; + m_maxTurnRate = that.m_maxTurnRate; + m_flags = that.m_flags; + m_closeEnoughDist = that.m_closeEnoughDist; +#ifdef CIRCLE_FOR_LANDING + m_circleThresh = that.m_circleThresh; +#endif + m_preferredHeight = that.m_preferredHeight; + m_preferredHeightDamping = that.m_preferredHeightDamping; + } + return *this; +} + +//------------------------------------------------------------------------------------------------- +Locomotor::~Locomotor() +{ +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void Locomotor::crc( Xfer *xfer ) +{ + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void Locomotor::xfer( Xfer *xfer ) +{ + // version + const XferVersion currentVersion = 2; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + if (version>=2) { + xfer->xferUnsignedInt(&m_donutTimer); + } + + xfer->xferCoord3D(&m_maintainPos); + xfer->xferReal(&m_brakingFactor); + xfer->xferReal(&m_maxLift); + xfer->xferReal(&m_maxSpeed); + xfer->xferReal(&m_maxAccel); + xfer->xferReal(&m_maxBraking); + xfer->xferReal(&m_maxTurnRate); + xfer->xferReal(&m_closeEnoughDist); +#ifdef CIRCLE_FOR_LANDING + DEBUG_CRASH(("not supported, must fix me")); +#endif + xfer->xferUnsignedInt(&m_flags); + xfer->xferReal(&m_preferredHeight); + xfer->xferReal(&m_preferredHeightDamping); + xfer->xferReal(&m_angleOffset); + xfer->xferReal(&m_offsetIncrement); + + xfer->xferReal(&m_speedMultiplier); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void Locomotor::loadPostProcess( void ) +{ + +} // end loadPostProcess + +//------------------------------------------------------------------------------------------------- +void Locomotor::startMove(void) +{ + // Reset the donut timer. + m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getMaxSpeedForCondition(BodyDamageType condition) const +{ + Real speed; + + if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) + speed = m_template->m_maxSpeed; + else + speed = m_template->m_maxSpeedDamaged; + + speed *= m_speedMultiplier; + + if (speed > m_maxSpeed) + speed = m_maxSpeed; + + return speed; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getMaxTurnRate(BodyDamageType condition) const +{ + Real turn; + + if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) + turn = m_template->m_maxTurnRate; + else + turn = m_template->m_maxTurnRateDamaged; + + turn *= m_speedMultiplier; + + if (turn > m_maxTurnRate) + turn = m_maxTurnRate; + + const Real TURN_FACTOR = 2; + if (getFlag(ULTRA_ACCURATE)) + turn *= TURN_FACTOR; // monster turning ability + + return turn; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getMaxAcceleration(BodyDamageType condition) const +{ + Real accel; + + if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) + accel = m_template->m_acceleration; + else + accel = m_template->m_accelerationDamaged; + + accel *= m_speedMultiplier; + + if (accel > m_maxAccel) + accel = m_maxAccel; + + return accel; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getBraking() const +{ + Real braking = m_template->m_braking; + + braking *= m_speedMultiplier; + + if (braking > m_maxBraking) + braking = m_maxBraking; + + return braking; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getMaxLift(BodyDamageType condition) const +{ + Real lift; + + if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) + lift = m_template->m_lift; + else + lift = m_template->m_liftDamaged; + + lift *= m_speedMultiplier; + + if (lift > m_maxLift) + lift = m_maxLift; + + return lift; +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::locoUpdate_moveTowardsAngle(Object* obj, Real goalAngle) +{ + setFlag(MAINTAIN_POS_IS_VALID, false); + + if (obj == NULL || m_template == NULL) + return; + + PhysicsBehavior *physics = obj->getPhysics(); + if (physics == NULL) + { + DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); + return; + } + + // Skip moveTowardsAngle if physics say you're stunned + if(physics->getIsStunned()) + { + return; + } + +#ifdef DEBUG_OBJECT_ID_EXISTS +// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_moveTowardsAngle %f (%f deg), spd %f (%f)\n",goalAngle,goalAngle*180/PI,physics->getSpeed(),physics->getForwardSpeed2D())); +#endif + + Real minSpeed = getMinSpeed(); + if (minSpeed > 0) + { + // can't stay in one place; move in the desired direction at min speed. + Coord3D desiredPos = *obj->getPosition(); + desiredPos.x += Cos(goalAngle) * minSpeed * 2; + desiredPos.y += Sin(goalAngle) * minSpeed * 2; + // pass a huge num for "dist to goal", so that we don't think we're nearing + // our destination and thus slow down... + const Real onPathDistToGoal = 99999.0f; + Bool blocked = false; + locoUpdate_moveTowardsPosition(obj, desiredPos, onPathDistToGoal, minSpeed, &blocked); + + // don't need to call handleBehaviorZ() here, since locoUpdate_moveTowardsPosition() will do so + return; + } + else + { + DEBUG_ASSERTCRASH(m_template->m_appearance != LOCO_THRUST, ("THRUST should always have minspeeds!\n")); + Coord3D desiredPos = *obj->getPosition(); + desiredPos.x += Cos(goalAngle) * 1000.0f; + desiredPos.y += Sin(goalAngle) * 1000.0f; + PhysicsTurningType rotating = rotateTowardsPosition(obj, desiredPos); + physics->setTurning(rotating); + handleBehaviorZ(obj, physics, *obj->getPosition()); + } + +} + +//------------------------------------------------------------------------------------------------- +PhysicsTurningType Locomotor::rotateTowardsPosition(Object* obj, const Coord3D& goalPos, Real *relAngle) +{ + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real turnRate = getMaxTurnRate(bdt); + + PhysicsTurningType rotating = rotateObjAroundLocoPivot(obj, goalPos, turnRate, relAngle); + return rotating; +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::setPhysicsOptions(Object* obj) +{ + PhysicsBehavior *physics = obj->getPhysics(); + if (physics == NULL) + { + DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); + return; + } + + // crank up the friction in ultra-accurate mode to increase movement precision. + const Real EXTRA_FRIC = 0.5f; + Real extraExtraFriction = getFlag(ULTRA_ACCURATE) ? EXTRA_FRIC : 0.0f; + physics->setExtraFriction(m_template->m_extra2DFriction + extraExtraFriction); + physics->setAllowAirborneFriction(getApply2DFrictionWhenAirborne()); // you'd think we wouldn't want friction in the air, but it's needed for realistic behavior. + physics->setStickToGround(getStickToGround()); // walking guys aren't allowed to catch huge (or even small) air. +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalPos, + Real onPathDistToGoal, Real desiredSpeed, Bool *blocked) +{ + setFlag(MAINTAIN_POS_IS_VALID, false); + + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxSpeed = getMaxSpeedForCondition(bdt); + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + Real distToStopAtMaxSpeed = (maxSpeed/getBraking()) * (maxSpeed)/2.0f; + if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > distToStopAtMaxSpeed) + { + setFlag(IS_BRAKING, false); + m_brakingFactor = 1.0f; + } + PhysicsBehavior *physics = obj->getPhysics(); + if (physics == NULL) + { + DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); + return; + } + + // Skip moveTowardsPosition if physics say you're stunned + if(physics->getIsStunned()) + { + return; + } + +#ifdef DEBUG_OBJECT_ID_EXISTS +// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_moveTowardsPosition %f %f %f (dtg %f, spd %f), speed %f (%f)\n",goalPos.x,goalPos.y,goalPos.z,onPathDistToGoal,desiredSpeed,physics->getSpeed(),physics->getForwardSpeed2D())); +#endif + + // + // do not allow for invalid positions that the pathfinder cannot handle ... for airborne + // objects we don't need the pathfinder so we'll ignore this + // + if( BitIsSet( m_template->m_surfaces, LOCOMOTORSURFACE_AIR ) == false && + !TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, obj->getPosition()) && + !getFlag(ALLOW_INVALID_POSITION)) + { + // Somehow, we have gotten to an invalid location. + if (fixInvalidPosition(obj, physics)) + { + // the we adjusted us toward a legal position, so just return. + return; + } + } + + // If the actual distance is farther, then use the actual distance so we get there. + Real dx = goalPos.x - obj->getPosition()->x; + Real dy = goalPos.y - obj->getPosition()->y; + Real dz = goalPos.z - obj->getPosition()->z; + Real dist = sqrt(dx*dx+dy*dy); + if (dist>onPathDistToGoal) + { + if (!obj->isKindOf(KINDOF_PROJECTILE) && dist>2*onPathDistToGoal) + { + setFlag(IS_BRAKING, true); + } + onPathDistToGoal = dist; + } + + Coord3D nullAccel; + + Bool treatAsAirborne = false; + Coord3D pos = *obj->getPosition(); + Real heightAboveSurface = pos.z - TheTerrainLogic->getLayerHeight(pos.x, pos.y, obj->getLayer()); + + if( obj->getStatusBits().test( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) ) + { + heightAboveSurface -= obj->getCarrierDeckHeight(); + } + + if (heightAboveSurface > -(3*3)*TheGlobalData->m_gravity) + { + // If we get high enough to stay up for 3 frames, then we left the ground. + treatAsAirborne = true; + } + // We apply a zero acceleration to all units, as the call to + // applyMotiveForce flags an object as being "driven" by a locomotor, rather + // than being pushed around by objects bumping it. + nullAccel.x = nullAccel.y = nullAccel.z = 0; + physics->applyMotiveForce(&nullAccel); + + if (*blocked) + { + if (desiredSpeed > physics->getVelocityMagnitude()) + { + *blocked = false; + } + if (treatAsAirborne && BitIsSet( m_template->m_surfaces, LOCOMOTORSURFACE_AIR ) ) + { + // Airborne flying objects don't collide for now. jba. + *blocked = false; + } + } + + if (*blocked) + { + physics->scrubVelocity2D(desiredSpeed); // stop if we are about to run into the blocking object. + Real turnRate = getMaxTurnRate(obj->getBodyModule()->getDamageState()); + if (m_template->m_wanderWidthFactor == 0.0f) + { + *blocked = (TURN_NONE != rotateObjAroundLocoPivot(obj, goalPos, turnRate)); + } + + // it is very important to be sure to call this in all situations, even if not moving in 2d space. + handleBehaviorZ(obj, physics, goalPos); + return; + } + + if ( +// srj sez: I don't know why we didn't want HOVERs to allow to "brake". +// we actually really want them to, because it allows much more precise destination positioning. +// m_template->m_appearance == LOCO_HOVER || + m_template->m_appearance == LOCO_WINGS) + { + setFlag(IS_BRAKING, false); + } + + Bool wasBraking = obj->getStatusBits().test( OBJECT_STATUS_BRAKING ); + + physics->setTurning(TURN_NONE); + if (getAllowMotiveForceWhileAirborne() || !treatAsAirborne) + { + switch (m_template->m_appearance) + { + case LOCO_LEGS_TWO: + moveTowardsPositionLegs(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_CLIMBER: + moveTowardsPositionClimb(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_WHEELS_FOUR: + case LOCO_MOTORCYCLE: + moveTowardsPositionWheels( obj, physics, goalPos, onPathDistToGoal, desiredSpeed ); + break; + case LOCO_TREADS: + moveTowardsPositionTreads(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_HOVER: + moveTowardsPositionHover(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_WINGS: + moveTowardsPositionWings(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_THRUST: + moveTowardsPositionThrust(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_OTHER: + default: + moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + } + } + + handleBehaviorZ(obj, physics, goalPos); + // Objects that are braking don't follow the normal physics, so they end up at their destination exactly. + obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ), getFlag(IS_BRAKING) ); + + if (wasBraking) + { + #define MIN_VEL (PATHFIND_CELL_SIZE_F/(LOGICFRAMES_PER_SECOND)) + + Coord3D pos = *obj->getPosition(); + if (obj->isKindOf(KINDOF_PROJECTILE)) + { + // Projectiles never stop braking once they start. jba. + obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ) ); + // Projectiles cheat in 3 dimensions. + dist = sqrt(dx*dx+dy*dy+dz*dz); + Real vel = physics->getVelocityMagnitude(); + if (vel < MIN_VEL) + vel = MIN_VEL; + if (vel > dist) + vel = dist; // do not overcompensate! + // Normalize. + if (dist > 0.001f) + { + dist = 1.0f / dist; + dx *= dist; + dy *= dist; + dz *= dist; + + // DEBUG_LOG((">>> Locomotor Braking - d(xyz) = %f / %f / %f\n", dx * vel, dy * vel, dz * vel)); + + pos.x += dx * vel; + pos.y += dy * vel; + pos.z += dz * vel; + } + } + else + { + // not projectiles only cheat in x & y. + // Normalize. + if (dist > 0.001f) + { + Real vel = fabs(physics->getForwardSpeed2D()); + if (vel < MIN_VEL) + vel = MIN_VEL; + if (vel > dist) + vel = dist; // do not overcompensate! + dist = 1.0f / dist; + dx *= dist; + dy *= dist; + pos.x += dx * vel; + pos.y += dy * vel; + } + } + obj->setPosition(&pos); + } + +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxSpeed = getMaxSpeedForCondition(bdt); + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + Real maxAcceleration = getMaxAcceleration(bdt); + + // Locomotion for treaded vehicles, ie tanks. + + // + // Orient toward goal position + // +// Real angle = obj->getOrientation(); +// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); +// Real desiredAngle = angle + relAngle; + Real relAngle ; + PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos, &relAngle); + physics->setTurning(rotating); + + // + // Modulate speed according to turning. The more we have to turn, the slower we go + // + const Real QUAETERPI = PI / 4.0f; + Real angleCoeff = (Real)fabs( relAngle ) / QUAETERPI; + if (angleCoeff > 1.0f) + angleCoeff = 1.0; + + + Real dx = obj->getPosition()->x - goalPos.x; + Real dy = obj->getPosition()->y - goalPos.y; + + + Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; + + +// if (speed < m_minTurnSpeed) +// speed = m_minTurnSpeed; + + Real actualSpeed = physics->getForwardSpeed2D(); + Real slowDownTime = actualSpeed / getBraking(); + Real slowDownDist = (actualSpeed/1.50f) * slowDownTime; + + if (sqr(dx)+sqr(dy) 0.05) { + goalSpeed = actualSpeed*0.6f; + } + + if (onPathDistToGoal < slowDownDist && !getFlag(IS_BRAKING) && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + { + setFlag(IS_BRAKING, true); + m_brakingFactor = 1.1f; + } + + if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > 2.0*slowDownDist) + { + setFlag(IS_BRAKING, false); + } + + if (getFlag(IS_BRAKING)) + { + m_brakingFactor = slowDownDist/onPathDistToGoal; + m_brakingFactor *= m_brakingFactor; + if (m_brakingFactor>MAX_BRAKING_FACTOR) { + m_brakingFactor = MAX_BRAKING_FACTOR; + } + if (slowDownDist>onPathDistToGoal) { + goalSpeed = actualSpeed-getBraking(); + if (goalSpeed<0.0f) goalSpeed= 0.0f; + } else if (slowDownDist>onPathDistToGoal*0.75f) { + goalSpeed = actualSpeed-getBraking()/2.0f; + if (goalSpeed<0.0f) goalSpeed = 0.0f; + } else { + goalSpeed = actualSpeed; + } + } + + + //DEBUG_LOG(("Actual speed %f, Braking factor %f, slowDownDist %f, Pathdist %f, goalSpeed %f\n", + // actualSpeed, m_brakingFactor, slowDownDist, onPathDistToGoal, goalSpeed)); + + // + // Maintain goal speed + // + Real speedDelta = goalSpeed - actualSpeed; + if (speedDelta != 0.0f) + { + Real mass = physics->getMass(); + Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -m_brakingFactor*getBraking(); + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + const Coord3D *dir = obj->getUnitDirectionVector2D(); + + Coord3D force; + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; + force.z = 0.0f; + + // apply forces to object + physics->applyMotiveForce( &force ); + } +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxSpeed = getMaxSpeedForCondition(bdt); + Real maxTurnRate = getMaxTurnRate(bdt); + Real maxAcceleration = getMaxAcceleration(bdt); + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + // Locomotion for wheeled vehicles, ie trucks. + // + // See if we are turning. If so, use the min turn speed. + // + Real turnSpeed = m_template->m_minTurnSpeed; + Real angle = obj->getOrientation(); +// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); +// Real desiredAngle = angle + relAngle; + Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + Real relAngle = stdAngleDiff(desiredAngle, angle); + + Bool moveBackwards = false; + + // Wheeled vehicles can only turn while moving, so make sure the turn speed is reasonable. + if (turnSpeed < maxSpeed/4.0f) + { + turnSpeed = maxSpeed/4.0f; + } + + + Real actualSpeed = physics->getForwardSpeed2D(); + Bool do3pointTurn = false; +#if 1 + if (actualSpeed==0.0f) { + setFlag(MOVING_BACKWARDS, false); + if (m_template->m_canMoveBackward && fabs(relAngle) > PI/2) { + setFlag(MOVING_BACKWARDS, true ); + setFlag(DOING_THREE_POINT_TURN, onPathDistToGoal>5*obj->getGeometryInfo().getMajorRadius()); + } + + } + if (getFlag(MOVING_BACKWARDS)) { + if (fabs(relAngle) < PI/2) { + moveBackwards = false; + setFlag(MOVING_BACKWARDS, false); + } else { + moveBackwards = true; + setFlag(DOING_THREE_POINT_TURN, onPathDistToGoal>5*obj->getGeometryInfo().getMajorRadius()); + do3pointTurn = getFlag(DOING_THREE_POINT_TURN); + if (!do3pointTurn) { + desiredAngle = stdAngleDiff(desiredAngle, PI); + relAngle = stdAngleDiff(desiredAngle, angle); + } + } + } +#endif + + const Real SMALL_TURN = PI / 20.0f; + if ((Real)fabs( relAngle ) > SMALL_TURN) + { + if (desiredSpeed>turnSpeed) + { + desiredSpeed = turnSpeed; + } + } + + Real goalSpeed = desiredSpeed; + if (moveBackwards) { + actualSpeed = -actualSpeed; + } + + + + Real slowDownTime = actualSpeed / getBraking() + 1.0f; + Real slowDownDist = (actualSpeed/1.5f) * slowDownTime + actualSpeed; + Real effectiveSlowDownDist = slowDownDist; + if (effectiveSlowDownDist < 1*PATHFIND_CELL_SIZE) { + effectiveSlowDownDist = 1*PATHFIND_CELL_SIZE; + } + + + const Real FIFTEEN_DEGREES = PI / 12.0f; + const Real PROJECT_FRAMES = LOGICFRAMES_PER_SECOND/2; // Project out 1/2 second. + if (fabs( relAngle ) > FIFTEEN_DEGREES) + { + // If we're turning more than 10 degrees, check & see if we're moving into "impassable territory" + Real distance = PROJECT_FRAMES * (goalSpeed+actualSpeed)/2.0f; + Real targetAngle = obj->getOrientation(); + Real turnFactor = ((goalSpeed+actualSpeed)/2.0f)/turnSpeed; + if (turnFactor > 1.0f) + turnFactor = 1.0f; + Real turnAmount = PROJECT_FRAMES*turnFactor*maxTurnRate/4.0f; + if (relAngle < 0) + { + targetAngle -= turnAmount; + } + else + { + targetAngle += turnAmount; + } + Coord3D offset; + offset.x = Cos(targetAngle)*distance; + offset.y = Sin(targetAngle)*distance; + offset.z = 0; + + const Coord3D* pos = obj->getPosition(); + + Coord3D nextPos; + nextPos.x = pos->x+offset.x; + nextPos.y = pos->y+offset.y; + nextPos.z = pos->z; + + pos = obj->getPosition(); + + Coord3D halfPos; + halfPos.x = pos->x+offset.x/2; + halfPos.y = pos->y+offset.y/2; + halfPos.z = pos->z; + + if (!TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &halfPos) || + !TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &nextPos)) + { + PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos); + physics->setTurning(rotating); + + // apply a zero force to object so that it acts "driven" + Coord3D force; + force.zero(); + physics->applyMotiveForce( &force ); + return; + } + + } + + if (onPathDistToGoal < effectiveSlowDownDist && !getFlag(IS_BRAKING) && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + { + setFlag(IS_BRAKING, true); + m_brakingFactor = 1.1f; + } + + + if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > 2.0*slowDownDist) + { + setFlag(IS_BRAKING, false); + } + + if (onPathDistToGoal > DONUT_DISTANCE) { + m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; + } else { + if (m_donutTimer < TheGameLogic->getFrame()) { + setFlag(IS_BRAKING, true); + } + } + + if (getFlag(IS_BRAKING)) + { + m_brakingFactor = slowDownDist/onPathDistToGoal; + m_brakingFactor *= m_brakingFactor; + if (m_brakingFactor>MAX_BRAKING_FACTOR) { + m_brakingFactor = MAX_BRAKING_FACTOR; + } + m_brakingFactor = 1.0f; + if (slowDownDist>onPathDistToGoal) { + goalSpeed = actualSpeed-getBraking(); + if (goalSpeed<0.0f) goalSpeed= 0.0f; + } else if (slowDownDist>onPathDistToGoal*0.75f) { + goalSpeed = actualSpeed-getBraking()/2.0f; + if (goalSpeed<0.0f) goalSpeed = 0.0f; + } else { + goalSpeed = actualSpeed; + } + } + + + //DEBUG_LOG(("Actual speed %f, Braking factor %f, slowDownDist %f, Pathdist %f, goalSpeed %f\n", + // actualSpeed, m_brakingFactor, slowDownDist, onPathDistToGoal, goalSpeed)); + + + // Wheeled can only turn while moving. + Real turnFactor = actualSpeed/turnSpeed; + if (turnFactor<0) { + turnFactor = -turnFactor; // in case we're sliding backwards in a 3 pt turn. + } + if (turnFactor > 1.0f) + turnFactor = 1.0f; + Real turnAmount = turnFactor*maxTurnRate; + + PhysicsTurningType rotating; + if (moveBackwards && !do3pointTurn) { + Coord3D backwardPos = *obj->getPosition(); + backwardPos.x += -(goalPos.x - obj->getPosition()->x); + backwardPos.y += -(goalPos.y - obj->getPosition()->y); + rotating = rotateObjAroundLocoPivot(obj, backwardPos, turnAmount); + } else { + rotating = rotateObjAroundLocoPivot(obj, goalPos, turnAmount); + } + + physics->setTurning(rotating); + + // + // Maintain goal speed + // + Real speedDelta = goalSpeed - actualSpeed; + if (moveBackwards) { + speedDelta = -goalSpeed+actualSpeed; + } + if (speedDelta != 0.0f) + { + Real mass = physics->getMass(); + Real acceleration; + if (moveBackwards) { + acceleration = (speedDelta < 0.0f) ? -maxAcceleration : m_brakingFactor*getBraking(); + } else { + acceleration = (speedDelta > 0.0f) ? maxAcceleration : -m_brakingFactor*getBraking(); + } + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + //DEBUG_LOG(("Braking %d, actualSpeed %f, goalSpeed %f, delta %f, accel %f\n", getFlag(IS_BRAKING), + //actualSpeed, goalSpeed, speedDelta, accelForce)); + + const Coord3D *dir = obj->getUnitDirectionVector2D(); + + Coord3D force; + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; + force.z = 0.0f; + + // apply forces to object + physics->applyMotiveForce( &force ); + } + +} +//------------------------------------------------------------------------------------------------- +Bool Locomotor::fixInvalidPosition(Object* obj, PhysicsBehavior *physics) +{ + if (obj->isKindOf(KINDOF_DOZER)) { + // don't fix him. + return false; + } +#define no_IGNORE_INVALID +#ifdef IGNORE_INVALID + // Right now we ignore invalid positions, so when units clip the edge of a building or cliff + // they don't get stuck. jba. 12SEPT02 + return false; +#else + Int dx = 0; + Int dy = 0; + Int i, j; + for (j=-1; j<2; j++) { + for (i=-1; i<2; i++) { + Coord3D thePos = *obj->getPosition(); + thePos.x += i*PATHFIND_CELL_SIZE_F; + thePos.y += j*PATHFIND_CELL_SIZE_F; + if (!TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &thePos)) { + if (i<0) dx += 1; + if (i>0) dx -= 1; + if (j<0) dy += 1; + if (j>0) dy -= 1; + } + } + } + if (dx || dy) { + + Coord3D correction; + correction.x = dx*physics->getMass()/5; + correction.y = dy*physics->getMass()/5; + correction.z = 0; + + Coord3D correctionNormalized = correction; + correctionNormalized.normalize(); + + Coord3D velocity; + // Kill current velocity in the direction of the correction. + velocity = *physics->getVelocity(); + Real dot = (velocity.x*correctionNormalized.x) + (velocity.y*correctionNormalized.y); + if (dot>.25f) { + // It was already leaving. + return false; + } + + + // Kill current accel + //physics->clearAcceleration(); + + if (dot<0) { + dot = sqrt(-dot); + correctionNormalized.x *= dot*physics->getMass(); + correctionNormalized.y *= dot*physics->getMass(); + physics->applyMotiveForce(&correctionNormalized); + } + + // apply correction. + physics->applyMotiveForce(&correction); + return true; + } + return false; +#endif +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::calcMinTurnRadius(BodyDamageType condition, Real* timeToTravelThatDist) const +{ + Real minSpeed = getMinSpeed(); // in dist/frame + Real maxTurnRate = getMaxTurnRate(condition); // in rads/frame + + /* + our minimum circumference will be like so: + + Real minTurnCircum = maxSpeed * (2*PI / maxTurnRate); + + so therefore our minimum turn radius is: + + Real minTurnRadius = minTurnCircum / 2*PI; + + so we just eliminate the middleman: + */ + // if we can't turn, return a huge-but-finite radius rather than NAN... + Real minTurnRadius = (maxTurnRate > 0.0f) ? minSpeed / maxTurnRate : BIGNUM; + + if (timeToTravelThatDist) + *timeToTravelThatDist = (minSpeed > 0.0f) ? (minTurnRadius / minSpeed) : 0.0f; + + return minTurnRadius; +} + + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + if (getIsDownhillOnly() && obj->getPosition()->z < goalPos.z) + { + return; + } + + Real maxAcceleration = getMaxAcceleration( obj->getBodyModule()->getDamageState() ); + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState() ); + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + // Locomotion for infantry. + // + // Orient toward goal position + // + Real actualSpeed = physics->getForwardSpeed2D(); + Real angle = obj->getOrientation(); +// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); +// Real desiredAngle = angle + relAngle; + Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + + if (m_template->m_wanderWidthFactor != 0.0f) { + Real angleLimit = PI/8 * m_template->m_wanderWidthFactor; + // This is the wander offline code - it forces the desired angle away from the goal, so we wander back & forth. jba. + if (getFlag(OFFSET_INCREASING)) { + m_angleOffset += m_offsetIncrement*actualSpeed; + if (m_angleOffset > angleLimit) { + setFlag(OFFSET_INCREASING, false); + } + } else { + m_angleOffset -= m_offsetIncrement*actualSpeed; + if (m_angleOffset<-angleLimit) { + setFlag(OFFSET_INCREASING, true); + } + } + desiredAngle = normalizeAngle(desiredAngle+m_angleOffset); + } + + Real relAngle = stdAngleDiff(desiredAngle, angle); + locoUpdate_moveTowardsAngle(obj, desiredAngle); + + // + // Modulate speed according to turning. The more we have to turn, the slower we go + // + const Real QUARTERPI = PI / 4.0f; + Real angleCoeff = (Real)fabs( relAngle ) / (QUARTERPI); + if (angleCoeff > 1.0f) + angleCoeff = 1.0; + + Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; + + //Real slowDownDist = (actualSpeed - m_template->m_minSpeed) / getBraking(); + Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); + if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + { + goalSpeed = m_template->m_minSpeed; + } + + + + // + // Maintain goal speed + // + Real speedDelta = goalSpeed - actualSpeed; + if (speedDelta != 0.0f) + { + Real mass = physics->getMass(); + Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + const Coord3D *dir = obj->getUnitDirectionVector2D(); + + Coord3D force; + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; + force.z = 0.0f; + + + + // apply forces to object + physics->applyMotiveForce( &force ); + } +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + Real maxAcceleration = getMaxAcceleration( obj->getBodyModule()->getDamageState() ); + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState() ); + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + // Locomotion for climbing infantry. + + + Bool moveBackwards = false; + + Real dx, dy, dz; + + Coord3D pos = *obj->getPosition(); + + dx = pos.x - goalPos.x; + dy = pos.y - goalPos.y; + dz = pos.z - goalPos.z; + if (dz*dz > sqr(PATHFIND_CELL_SIZE_F)) { + setFlag(CLIMBING, true); + } + if (fabs(dz)<1) { + setFlag(CLIMBING, false); + } + + + //setFlag(CLIMBING, true); + + if (getFlag(CLIMBING)) { + Coord3D delta = goalPos; + delta.x -= pos.x; + delta.y -= pos.y; + delta.z = 0; + delta.normalize(); + delta.x += pos.x; + delta.y += pos.y; + delta.z = TheTerrainLogic->getGroundHeight(delta.x, delta.y); + if (delta.z < pos.z-0.1) { + moveBackwards = true; + } + + Real groundSlope = fabs(delta.z - pos.z); + if (groundSlope<1.0f) groundSlope = 1.0f; + + if (groundSlope>1.0f) { + desiredSpeed /= groundSlope*4; + } + } + setFlag(MOVING_BACKWARDS, moveBackwards); + + // + // Orient toward goal position + // + Real angle = obj->getOrientation(); +// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); +// Real desiredAngle = angle + relAngle; + Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + Real relAngle = stdAngleDiff(desiredAngle, angle); + + if (moveBackwards) { + desiredAngle = stdAngleDiff(desiredAngle, PI); + relAngle = stdAngleDiff(desiredAngle, angle); + } + + locoUpdate_moveTowardsAngle(obj, desiredAngle); + + // + // Modulate speed according to turning. The more we have to turn, the slower we go + // + const Real QUARTERPI = PI / 4.0f; + Real angleCoeff = (Real)fabs( relAngle ) / (QUARTERPI); + if (angleCoeff > 1.0f) + angleCoeff = 1.0; + + Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; + + Real actualSpeed = physics->getForwardSpeed2D(); + + if (moveBackwards) { + actualSpeed = -actualSpeed; + } + + //Real slowDownDist = (actualSpeed - m_template->m_minSpeed) / getBraking(); + Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); + if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + { + goalSpeed = m_template->m_minSpeed; + } + + // + // Maintain goal speed + // + Real speedDelta = goalSpeed - actualSpeed; + if (moveBackwards) { + speedDelta = -goalSpeed+actualSpeed; + } + if (speedDelta != 0.0f) + { + Real mass = physics->getMass(); + Real acceleration; + if (moveBackwards) { + acceleration = (speedDelta < 0.0f) ? -maxAcceleration : getBraking(); + } else { + acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); + } + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + const Coord3D *dir = obj->getUnitDirectionVector2D(); + + Coord3D force; + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; + force.z = 0.0f; + + // apply forces to object + physics->applyMotiveForce( &force ); + } +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ +#ifdef CIRCLE_FOR_LANDING + if (m_circleThresh > 0.0f) + { + // if we are going a mostly-vertical maneuver, circle in order to + // gain/lose altitude, then resume course... + const Coord3D* pos = obj->getPosition(); + Real dx = goalPos.x - pos->x; + Real dy = goalPos.y - pos->y; + Real dz = goalPos.z - pos->z; + if (fabs(dz) > m_circleThresh) + { + // aim for the spot on the opposite side of the circle. + + // find the direction towards our goal pos + Real angleTowardPos = + (isNearlyZero(dx) && isNearlyZero(dy)) ? + obj->getOrientation() : + atan2(dy, dx); + + Real aimDir = (PI - PI/8); + angleTowardPos += aimDir; + + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real turnRadius = calcMinTurnRadius(bdt, NULL) * 4; + + // project a spot "radius" dist away from it, in that dir + Coord3D desiredPos = goalPos; + desiredPos.x += Cos(angleTowardPos) * turnRadius; + desiredPos.y += Sin(angleTowardPos) * turnRadius; + moveTowardsPositionOther(obj, physics, desiredPos, 0, desiredSpeed); + return; + } + } +#endif + + // handle the 2D component. + moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionHover(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + // handle the 2D component. + moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + + // Only hover locomotors care about their OverWater special effects. (OverWater also affects speed, so this is not a client thing) + Coord3D newPosition = *obj->getPosition(); + if( TheTerrainLogic->isUnderwater( newPosition.x, newPosition.y ) ) + { + if( ! getFlag( OVER_WATER ) ) + { + // Change my model condition because I used to not be over water, but now I am + setFlag( OVER_WATER, TRUE ); + obj->setModelConditionState( MODELCONDITION_OVER_WATER ); + } + } + else + { + if( getFlag( OVER_WATER ) ) + { + // Here, I was, but now I'm not + setFlag( OVER_WATER, FALSE ); + obj->clearModelConditionState( MODELCONDITION_OVER_WATER ); + } + } +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + + Real maxForwardSpeed = getMaxSpeedForCondition(bdt); + desiredSpeed = clamp(m_template->m_minSpeed, desiredSpeed, maxForwardSpeed); + Real actualForwardSpeed = physics->getForwardSpeed3D(); + + if (getBraking() > 0) + { + //Real slowDownDist = (actualForwardSpeed - m_template->m_minSpeed) / getBraking(); + Real slowDownDist = calcSlowDownDist(actualForwardSpeed, m_template->m_minSpeed, getBraking()); + if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + desiredSpeed = m_template->m_minSpeed; + } + + Coord3D localGoalPos = goalPos; +#ifdef USE_ZDIR_DAMPING + Real zDirDamping = 0.0f; +#endif + + //out of the handleBehaviorZ() function + Coord3D pos = *obj->getPosition(); + if( m_preferredHeight != 0.0f && !getFlag(PRECISE_Z_POS) ) + { + // If we have a preferred flight height, and we haven't been told explicitly to ignore it... + Real surfaceHt = getSurfaceHtAtPt(pos.x, pos.y); + localGoalPos.z = m_preferredHeight + surfaceHt; +// localGoalPos.z = goalPos.z; + Real delta = localGoalPos.z - pos.z; + delta *= getPreferredHeightDamping(); + localGoalPos.z = pos.z + delta; + +#ifdef USE_ZDIR_DAMPING + // closer we get to the preferred height, less we adjust z-thrust, + // so we tend to "level out" at that height. we don't use this till + // below, but go ahead and calc it now... + Real MAX_VERTICAL_DAMP_RANGE = m_preferredHeight * 0.5; + delta = fabs(delta); + if (delta > MAX_VERTICAL_DAMP_RANGE) + delta = MAX_VERTICAL_DAMP_RANGE; + zDirDamping = 1.0f - (delta / MAX_VERTICAL_DAMP_RANGE); +#endif + } + + Vector3 forwardDir = obj->getTransformMatrix()->Get_X_Vector(); + + // Maintain goal speed + Real forwardSpeedDelta = desiredSpeed - actualForwardSpeed; + Real maxAccel = (forwardSpeedDelta > 0.0f || getBraking() == 0) ? getMaxAcceleration(bdt) : -getBraking(); + Real maxTurnRate = getMaxTurnRate(bdt); + + // what direction do we need to thrust in, in order to reach the goalpos? + Vector3 desiredThrustDir; + calcDirectionToApplyThrust(obj, physics, localGoalPos, maxAccel, desiredThrustDir); + + // we might not be able to thrust in that dir, so thrust as closely as we can + Real maxThrustAngle = (maxTurnRate > 0) ? (m_template->m_maxThrustAngle) : 0; + Vector3 thrustDir; + Real thrustAngle = tryToRotateVector3D(maxThrustAngle, forwardDir, desiredThrustDir, thrustDir); + + // note that we are trying to orient in the direction of our vel, not the dir of our thrust. + if (!isNearlyZero(physics->getVelocityMagnitude())) + { + const Coord3D* veltmp = physics->getVelocity(); + Vector3 vel(veltmp->x, veltmp->y, veltmp->z); + Bool adjust = true; + if( obj->getStatusBits().test( OBJECT_STATUS_BRAKING ) ) + { + //Real closeInDist = 150.0f; // TODO: get/set this from missileAI? + //Real af = 1.0f - __min((onPathDistToGoal / closeInDist), 1.0); + + //if (af > 0.0f) { + + // vel.Set( + // vel.X * (1.0f - af) + (goalPos.x - pos.x) * af, + // vel.Y * (1.0f - af) + (goalPos.y - pos.y) * af, + // vel.Z * (1.0f - af) + (goalPos.z - pos.z) * af + // ); + // if (isNearlyZero(sqr(vel.X) + sqr(vel.Y) + sqr(vel.Z))) { + // // we are at target. + // adjust = false; + // } + // maxTurnRate = (1.0f + (af * 2.0f) ) * maxTurnRate; + //} + + // DEBUG_LOG((">>> moveTowardsPositionThrust - Braking - maxTurnRate = %f\n", maxTurnRate)); + + // align to target, cause that's where we're going anyway. + + vel.Set(goalPos.x - pos.x, goalPos.y-pos.y, goalPos.z-pos.z); + if (isNearlyZero(sqr(vel.X)+sqr(vel.Y)+sqr(vel.Z))) { + // we are at target. + adjust = false; + } + maxTurnRate = 3*maxTurnRate; + } +#ifdef USE_ZDIR_DAMPING + if (zDirDamping != 0.0f) + { + Vector3 vel2D(veltmp->x, veltmp->y, 0); + // no need to normalize -- this call does that internally + tryToRotateVector3D(-zDirDamping, vel, vel2D, vel); + } +#endif + if (adjust) { + /*Real orient =*/ tryToOrientInThisDirection3D(obj, maxTurnRate, vel); + } + } + + if (forwardSpeedDelta != 0.0f || thrustAngle != 0.0f) + { + if (maxForwardSpeed <= 0.0f) + { + maxForwardSpeed = 0.01f; // In some cases, this is 0, hack for now. jba. + } + Real damping = clamp(0.0f, maxAccel / maxForwardSpeed, 1.0f); + Vector3 curVel(physics->getVelocity()->x, physics->getVelocity()->y, physics->getVelocity()->z); + + Vector3 accelVec = thrustDir * maxAccel - curVel * damping; + //DEBUG_LOG(("accel %f (max %f) vel %f (max %f) damping %f\n",accelVec.Length(),maxAccel,curVel.Length(),maxForwardSpeed,damping)); + + Real mass = physics->getMass(); + + Coord3D force; + force.x = mass * accelVec.X; + force.y = mass * accelVec.Y; + force.z = mass * accelVec.Z; + + // apply forces to object + physics->applyMotiveForce( &force ); + } +} + +//------------------------------------------------------------------------------------------------- +/*static*/ Real Locomotor::getSurfaceHtAtPt(Real x, Real y) +{ + Real ht = 0; + + Real z,waterZ; + if (TheTerrainLogic->isUnderwater(x, y, &waterZ, &z)) { + ht += waterZ; + } else { + ht += z; + } + + return ht; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::calcLiftToUseAtPt(Object* obj, PhysicsBehavior *physics, Real curZ, Real surfaceAtPt, Real preferredHeight) +{ + /* + take the classic equation: + + x = x0 + v*t + 0.5*a*t^2 + + and solve for acceleration. + */ + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxGrossLift = getMaxLift(bdt); + Real maxNetLift = maxGrossLift + TheGlobalData->m_gravity; // note that gravity is always negative. + if (maxNetLift < 0) + maxNetLift = 0; + Real curVelZ = physics->getVelocity()->z; + // going down, braking is limited by net lift; going up, braking is limited by gravity + Real maxAccel; + if (getFlag(ULTRA_ACCURATE)) + maxAccel = (curVelZ < 0) ? 2*maxNetLift : -2*maxNetLift; + else + maxAccel = (curVelZ < 0) ? maxNetLift : TheGlobalData->m_gravity; + // see how far we need to slow to dead stop, given max braking + Real desiredAccel; + const Real TINY_ACCEL = 0.001f; + if (fabs(maxAccel) > TINY_ACCEL) + { + Real deltaZ = preferredHeight - curZ; + // calc how far it will take for us to go from cur speed to zero speed, at max accel. + // Real brakeDist = calcSlowDownDist(curVelZ, 0, maxAccel); + // in theory, the above is the correct calculation, but in practice, + // doesn't work in some situations (eg, opening of USA01 map). Why, I dunno. + // But for now I have gone back to the old, looks-incorrect-to-me-but-works calc. (srj) + Real brakeDist = (sqr(curVelZ) / fabs(maxAccel)); + if (fabs(brakeDist) > fabs(deltaZ)) + { + // if the dist-to-accel (or dist-to-brake) is further than the dist-to-go, + // use the max accel. + desiredAccel = maxAccel; + } + else if (fabs(curVelZ) > m_template->m_speedLimitZ) + { + // or, if we're going too fast, limit it here. + desiredAccel = m_template->m_speedLimitZ - curVelZ; + } + else + { + // ok, figure out the correct accel to use to get us there at zero. + // + // dz = v t + 0.5 a t^2 + // thus + // a = 2(dz - v t)/t^2 + // and + // t = (-v +- sqrt(v*v + 2*a*dz))/a + // + // but if we assume t=1, then + // a=2(dz-v) + // then, plug it back in and see if t is really 1... + desiredAccel = 2.0f * (deltaZ - curVelZ); + } + } + else + { + desiredAccel = 0.0f; + } + Real liftToUse = desiredAccel - TheGlobalData->m_gravity; + if (getFlag(ULTRA_ACCURATE)) + { + // in ultra-accurate mode, we allow cheating. + const Real UP_FACTOR = 3.0f; + if (liftToUse > UP_FACTOR*maxGrossLift) + liftToUse = UP_FACTOR*maxGrossLift; + // srj sez: we used to clip lift to zero here (not allowing neg lift). + // however, I now think that allowing neg lift in ultra-accurate mode is + // a good and desirable thing; in particular, it enables jets to complete + // "short" landings more accurately (previously they sometimes would "float" + // down, which sucked.) if you need to bump this back to zero, check it carefully... + else if (liftToUse < -maxGrossLift) + liftToUse = -maxGrossLift; + } + else + { + if (liftToUse > maxGrossLift) + liftToUse = maxGrossLift; + else if (liftToUse < 0.0f) + liftToUse = 0.0f; + } + + return liftToUse; +} + +//------------------------------------------------------------------------------------------------- +PhysicsTurningType Locomotor::rotateObjAroundLocoPivot(Object* obj, const Coord3D& goalPos, + Real maxTurnRate, Real *relAngle) +{ + Real angle = obj->getOrientation(); + Real offset = getTurnPivotOffset(); + + PhysicsTurningType turn = TURN_NONE; + + if (getFlag(IS_BRAKING)) offset = 0.0f; // When braking we do exact movement towards goal, instead of physics. + //Rotating about pivot moves the object, and can make us miss our goal, so it is disabled. jba. + if (offset != 0.0f) + { + Real radius = obj->getGeometryInfo().getBoundingCircleRadius(); + Real turnPointOffset = offset * radius; + + Coord3D turnPos = *obj->getPosition(); + const Coord3D* dir = obj->getUnitDirectionVector2D(); + turnPos.x += dir->x * turnPointOffset; + turnPos.y += dir->y * turnPointOffset; + Real dx =goalPos.x - turnPos.x; + Real dy = goalPos.y - turnPos.y; + // If we are very close to the goal, we twitch due to rounding error. So just return. jba. + if (fabs(dx)<0.1f && fabs(dy)<0.1f) return TURN_NONE; + Real desiredAngle = atan2(dy, dx); + Real amount = stdAngleDiff(desiredAngle, angle); + if (relAngle) *relAngle = amount; + if (amount>maxTurnRate) { + amount = maxTurnRate; + turn = TURN_POSITIVE; + } else if (amount < -maxTurnRate) { + amount = -maxTurnRate; + turn = TURN_NEGATIVE; + } else { + turn = TURN_NONE; + } + +#if 0 + Coord3D desiredPos = *obj->getPosition(); // well, desired Dir, anyway + desiredPos.x += Cos(angle + amount) * radius; + desiredPos.y += Sin(angle + amount) * radius; + + + // so, the thing is, we want to rotate ourselves so that our *center* is rotated + // by the given amount, but the rotation must be around turnPos. so do a little + // back-calculation. + Real angleDesiredForTurnPos = atan2(desiredPos.y - turnPos.y, desiredPos.x - turnPos.x); + amount = angleDesiredForTurnPos - angle; +#endif + /// @todo srj -- there's probably a more efficient & more direct way to do this. find it. + Matrix3D mtx; + Matrix3D tmp(1); + tmp.Translate(turnPos.x, turnPos.y, 0); + tmp.In_Place_Pre_Rotate_Z(amount); + tmp.Translate(-turnPos.x, -turnPos.y, 0); + + mtx.mul(tmp, *obj->getTransformMatrix()); + + obj->setTransformMatrix(&mtx); + } + else + { + Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + Real amount = stdAngleDiff(desiredAngle, angle); + if (relAngle) *relAngle = amount; + if (amount>maxTurnRate) { + amount = maxTurnRate; + turn = TURN_POSITIVE; + } else if (amount < -maxTurnRate) { + amount = -maxTurnRate; + turn = TURN_NEGATIVE; + } else { + turn = TURN_NONE; + } + obj->setOrientation( normalizeAngle(angle + amount) ); + } + return turn; +} + +//------------------------------------------------------------------------------------------------- +/* + return true if we can maintain the position without being called every frame (eg, we are + resting on the ground), false if not (eg, we are hovering or circling) +*/ +Bool Locomotor::handleBehaviorZ(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos) +{ + Bool requiresConstantCalling = TRUE; + + // keep the agent aligned on the terrain + switch(m_template->m_behaviorZ) + { + case Z_NO_Z_MOTIVE_FORCE: + // nothing to do. + requiresConstantCalling = FALSE; + break; + + case Z_SEA_LEVEL: + requiresConstantCalling = TRUE; + if( !obj->isDisabledByType( DISABLED_HELD ) ) + { + Coord3D pos = *obj->getPosition(); + Real waterZ; + if (TheTerrainLogic->isUnderwater(pos.x, pos.y, &waterZ)) { + pos.z = waterZ; + } else { + pos.z = TheTerrainLogic->getLayerHeight(pos.x, pos.y, obj->getLayer()); + } + obj->setPosition(&pos); + } + break; + + case Z_FIXED_SURFACE_RELATIVE_HEIGHT: + case Z_FIXED_ABSOLUTE_HEIGHT: + requiresConstantCalling = TRUE; + { + Coord3D pos = *obj->getPosition(); + Bool surfaceRel = (m_template->m_behaviorZ == Z_FIXED_SURFACE_RELATIVE_HEIGHT); + Real surfaceHt = surfaceRel ? getSurfaceHtAtPt(pos.x, pos.y) : 0.0f; + pos.z = m_preferredHeight + (surfaceRel ? surfaceHt : 0); + obj->setPosition(&pos); + } + break; + + case Z_RELATIVE_TO_GROUND_AND_BUILDINGS: + requiresConstantCalling = TRUE; + { + // srj sez: use getGroundOrStructureHeight(), because someday it will cache building heights... + Coord3D pos = *obj->getPosition(); + Real surfaceHt = ThePartitionManager->getGroundOrStructureHeight(pos.x, pos.y); + + pos.z = m_preferredHeight + surfaceHt; + + obj->setPosition(&pos); + + } + break; + case Z_SMOOTH_RELATIVE_TO_HIGHEST_LAYER: + requiresConstantCalling = TRUE; + { + if (m_preferredHeight != 0.0f || getFlag(PRECISE_Z_POS)) + { + Coord3D pos = *obj->getPosition(); + + // srj sez: if we aren't on the ground, never find the ground layer + PathfindLayerEnum layerAtDest = obj->getLayer(); + if (layerAtDest == LAYER_GROUND) + layerAtDest = TheTerrainLogic->getHighestLayerForDestination( &pos ); + + Real surfaceHt; + Coord3D normal; + const Bool clip = false; // return the height, even if off the edge of the bridge proper. + surfaceHt = TheTerrainLogic->getLayerHeight( pos.x, pos.y, layerAtDest, &normal, clip ); + + Real preferredHeight = m_preferredHeight + surfaceHt; + if (getFlag(PRECISE_Z_POS)) + preferredHeight = goalPos.z; + + Real delta = preferredHeight - pos.z; + delta *= getPreferredHeightDamping(); + preferredHeight = pos.z + delta; + + Real liftToUse = calcLiftToUseAtPt(obj, physics, pos.z, surfaceHt, preferredHeight); + + //DEBUG_LOG(("HandleBZ %d LiftToUse %f\n",TheGameLogic->getFrame(),liftToUse)); + if (liftToUse != 0.0f) + { + Coord3D force; + force.x = 0.0f; + force.y = 0.0f; + force.z = liftToUse * physics->getMass(); + physics->applyMotiveForce(&force); + } + } + } + break; + + case Z_SURFACE_RELATIVE_HEIGHT: + case Z_ABSOLUTE_HEIGHT: + requiresConstantCalling = TRUE; + { + if (m_preferredHeight != 0.0f || getFlag(PRECISE_Z_POS)) + { + Coord3D pos = *obj->getPosition(); + + Bool surfaceRel = (m_template->m_behaviorZ == Z_SURFACE_RELATIVE_HEIGHT); + Real surfaceHt = surfaceRel ? getSurfaceHtAtPt(pos.x, pos.y) : 0.0f; + Real preferredHeight = m_preferredHeight + (surfaceRel ? surfaceHt : 0); + if (getFlag(PRECISE_Z_POS)) + preferredHeight = goalPos.z; + + Real delta = preferredHeight - pos.z; + delta *= getPreferredHeightDamping(); + preferredHeight = pos.z + delta; + + Real liftToUse = calcLiftToUseAtPt(obj, physics, pos.z, surfaceHt, preferredHeight); + + //DEBUG_LOG(("HandleBZ %d LiftToUse %f\n",TheGameLogic->getFrame(),liftToUse)); + if (liftToUse != 0.0f) + { + Coord3D force; + force.x = 0.0f; + force.y = 0.0f; + force.z = liftToUse * physics->getMass(); + physics->applyMotiveForce(&force); + } + } + } + break; + } + + return requiresConstantCalling; +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxAcceleration = getMaxAcceleration(bdt); + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + Real maxSpeed = getMaxSpeedForCondition(bdt); + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + Real goalSpeed = desiredSpeed; + Real actualSpeed = physics->getForwardSpeed2D(); + + // Locomotion for other things, ie don't know what it is jba :) + // + // Orient toward goal position + // exception: if very close (ie, we could get there in 2 frames or less),\ + // and ULTRA_ACCURATE, just slide into place + // + const Coord3D* pos = obj->getPosition(); + Coord3D dirToApplyForce = *obj->getUnitDirectionVector2D(); + +//DEBUG_ASSERTLOG(!getFlag(ULTRA_ACCURATE),("thresh %f %f (%f %f)\n", +//fabs(goalPos.y - pos->y),fabs(goalPos.x - pos->x), +//fabs(goalPos.y - pos->y)/goalSpeed,fabs(goalPos.x - pos->x)/goalSpeed)); + if (getFlag(ULTRA_ACCURATE) && + fabs(goalPos.y - pos->y) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor && + fabs(goalPos.x - pos->x) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor) + { + // don't turn, just slide in the right direction + physics->setTurning(TURN_NONE); + dirToApplyForce.x = goalPos.x - pos->x; + dirToApplyForce.y = goalPos.y - pos->y; + dirToApplyForce.z = 0.0f; + dirToApplyForce.normalize(); + } + else + { + PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos); + physics->setTurning(rotating); + } + + if (!getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + { + Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); + if (onPathDistToGoal < slowDownDist) + { + goalSpeed = m_template->m_minSpeed; + } + } + + // + // Maintain goal speed + // + Real speedDelta = goalSpeed - actualSpeed; + if (speedDelta != 0.0f) + { + Real mass = physics->getMass(); + Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + Coord3D force; + force.x = accelForce * dirToApplyForce.x; + force.y = accelForce * dirToApplyForce.y; + force.z = 0.0f; + + // apply forces to object + physics->applyMotiveForce( &force ); + } + +} + + +//------------------------------------------------------------------------------------------------- +/* + return true if we can maintain the position without being called every frame (eg, we are + resting on the ground), false if not (eg, we are hovering or circling) +*/ +Bool Locomotor::locoUpdate_maintainCurrentPosition(Object* obj) +{ + if (!getFlag(MAINTAIN_POS_IS_VALID)) + { + m_maintainPos = *obj->getPosition(); + setFlag(MAINTAIN_POS_IS_VALID, true); + } + + m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; + setFlag(IS_BRAKING, false); + PhysicsBehavior *physics = obj->getPhysics(); + if (physics == NULL) + { + DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); + return TRUE; + } + +#ifdef DEBUG_OBJECT_ID_EXISTS +// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_maintainCurrentPosition %f %f %f, speed %f (%f)\n",m_maintainPos.x,m_maintainPos.y,m_maintainPos.z,physics->getSpeed(),physics->getForwardSpeed2D())); +#endif + + Bool requiresConstantCalling = TRUE; // assume the worst. + switch (m_template->m_appearance) + { + case LOCO_THRUST: + maintainCurrentPositionThrust(obj, physics); + requiresConstantCalling = TRUE; + break; + case LOCO_LEGS_TWO: + maintainCurrentPositionLegs(obj, physics); + requiresConstantCalling = FALSE; + break; + case LOCO_CLIMBER: + maintainCurrentPositionLegs(obj, physics); + requiresConstantCalling = FALSE; + break; + case LOCO_WHEELS_FOUR: + case LOCO_MOTORCYCLE: + maintainCurrentPositionWheels(obj, physics); + requiresConstantCalling = FALSE; + break; + case LOCO_TREADS: + maintainCurrentPositionTreads(obj, physics); + requiresConstantCalling = FALSE; + break; + case LOCO_HOVER: + maintainCurrentPositionHover(obj, physics); + requiresConstantCalling = TRUE; + break; + case LOCO_WINGS: + maintainCurrentPositionWings(obj, physics); + requiresConstantCalling = TRUE; + break; + case LOCO_OTHER: + default: + maintainCurrentPositionOther(obj, physics); + requiresConstantCalling = TRUE; + break; + } + + // but we do need to do this even if not moving, for hovering/Thrusting things. + if (handleBehaviorZ(obj, physics, m_maintainPos)) + requiresConstantCalling = TRUE; + + return requiresConstantCalling; +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::maintainCurrentPositionThrust(Object* obj, PhysicsBehavior *physics) +{ + DEBUG_ASSERTCRASH(getFlag(MAINTAIN_POS_IS_VALID), ("invalid maintain pos")); + /// @todo srj -- should these also use the "circling radius" stuff, like wings? + moveTowardsPositionThrust(obj, physics, m_maintainPos, 0, getMinSpeed()); +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physics) +{ + DEBUG_ASSERTCRASH(getFlag(MAINTAIN_POS_IS_VALID), ("invalid maintain pos")); + physics->setTurning(TURN_NONE); + if (physics->isMotive() && obj->isAboveTerrain()) // no need to stop something that isn't moving (or is just sitting on the ground) + { + + // aim for the spot on the opposite side of the circle. + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real turnRadius = m_template->m_circlingRadius; + if (turnRadius == 0.0f) + turnRadius = calcMinTurnRadius(bdt, NULL); + + // find the direction towards our "maintain pos" + const Coord3D* pos = obj->getPosition(); + Real dx = m_maintainPos.x - pos->x; + Real dy = m_maintainPos.y - pos->y; + Real angleTowardMaintainPos = + (isNearlyZero(dx) && isNearlyZero(dy)) ? + obj->getOrientation() : + atan2(dy, dx); + + Real aimDir = (PI - PI/8); + if (turnRadius < 0) + { + turnRadius = -turnRadius; + aimDir = -aimDir; + } + angleTowardMaintainPos += aimDir; + + // project a spot "radius" dist away from it, in that dir + Coord3D desiredPos = m_maintainPos; + desiredPos.x += Cos(angleTowardMaintainPos) * turnRadius; + desiredPos.y += Sin(angleTowardMaintainPos) * turnRadius; + moveTowardsPositionWings(obj, physics, desiredPos, 0, m_template->m_minSpeed); + } +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physics) +{ + physics->setTurning(TURN_NONE); + if (physics->isMotive()) // no need to stop something that isn't moving. + { + DEBUG_ASSERTCRASH(m_template->m_minSpeed == 0.0f, ("HOVER should always have zero minSpeeds (otherwise, they WING)")); + + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxAcceleration = getMaxAcceleration(bdt); + Real actualSpeed = physics->getForwardSpeed2D(); + // + // Stop + // + Real minSpeed = max( 1.0E-10f, m_template->m_minSpeed ); + Real speedDelta = minSpeed - actualSpeed; + if (fabs(speedDelta) > minSpeed) + { + Real mass = physics->getMass(); + Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + const Coord3D *dir = obj->getUnitDirectionVector2D(); + + Coord3D force; + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; + force.z = 0.0f; + + + // Apply a random kick (if applicable) to dirty-up visually. + // The idea is that chopper pilots have to do course corrections all the time + // Because of changes in wind, pressure, etc. + // Those changes are added here, then the + + + + // apply forces to object + physics->applyMotiveForce( &force ); + } + } + +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::maintainCurrentPositionOther(Object* obj, PhysicsBehavior *physics) +{ + + physics->setTurning(TURN_NONE); + if (physics->isMotive()) // no need to stop something that isn't moving. + { + physics->scrubVelocity2D(0); // stop. + } + +} + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- +LocomotorSet::LocomotorSet() +{ + m_locomotors.clear(); + m_validLocomotorSurfaces = 0; + m_downhillOnly = FALSE; + +} + +//------------------------------------------------------------------------------------------------- +LocomotorSet::LocomotorSet(const LocomotorSet& that) +{ + DEBUG_CRASH(("unimplemented")); +} + +//------------------------------------------------------------------------------------------------- +LocomotorSet& LocomotorSet::operator=(const LocomotorSet& that) +{ + if (this != &that) + { + DEBUG_CRASH(("unimplemented")); + } + return *this; +} + +//------------------------------------------------------------------------------------------------- +LocomotorSet::~LocomotorSet() +{ + clear(); +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void LocomotorSet::crc( Xfer *xfer ) +{ + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void LocomotorSet::xfer( Xfer *xfer ) +{ + // version + const XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // count of vector + UnsignedShort count = m_locomotors.size(); + xfer->xferUnsignedShort( &count ); + + // data + if (xfer->getXferMode() == XFER_SAVE) + { + for (LocomotorVector::iterator it = m_locomotors.begin(); it != m_locomotors.end(); ++it) + { + Locomotor* loco = *it; + AsciiString name = loco->getTemplateName(); + xfer->xferAsciiString(&name); + xfer->xferSnapshot(loco); + } + } + else if (xfer->getXferMode() == XFER_LOAD) + { + // vector should be empty at this point + if (m_locomotors.empty() == FALSE) + { + DEBUG_CRASH(( "LocomotorSet::xfer - vector is not empty, but should be\n" )); + throw XFER_LIST_NOT_EMPTY; + } + + for (UnsignedShort i = 0; i < count; ++i) + { + AsciiString name; + xfer->xferAsciiString(&name); + + const LocomotorTemplate* lt = TheLocomotorStore->findLocomotorTemplate(NAMEKEY(name)); + if (lt == NULL) + { + DEBUG_CRASH(( "LocomotorSet::xfer - template %s not found\n", name.str() )); + throw XFER_UNKNOWN_STRING; + } + + Locomotor* loco = TheLocomotorStore->newLocomotor(lt); + xfer->xferSnapshot(loco); + m_locomotors.push_back(loco); + } + } + + xfer->xferInt(&m_validLocomotorSurfaces); + xfer->xferBool(&m_downhillOnly); + +} + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void LocomotorSet::loadPostProcess( void ) +{ + +} // end loadPostProcess + +//------------------------------------------------------------------------------------------------- +void LocomotorSet::xferSelfAndCurLocoPtr(Xfer *xfer, Locomotor** loco) +{ + xfer->xferSnapshot(this); + + if (xfer->getXferMode() == XFER_SAVE) + { + AsciiString name; + if (*loco) + name = (*loco)->getTemplateName(); + xfer->xferAsciiString(&name); + } + else if (xfer->getXferMode() == XFER_LOAD) + { + AsciiString name; + xfer->xferAsciiString(&name); + + if (name.isEmpty()) + { + *loco = NULL; + } + else + { + for (int i = 0; i < m_locomotors.size(); ++i) + { + if (m_locomotors[i]->getTemplateName() == name) + { + *loco = m_locomotors[i]; + return; + } + } + + DEBUG_CRASH(( "LocomotorSet::xfer - template %s not found\n", name.str() )); + throw XFER_UNKNOWN_STRING; + } + } +} + +//------------------------------------------------------------------------------------------------- +void LocomotorSet::clear() +{ + for (int i = 0; i < m_locomotors.size(); ++i) + { + if (m_locomotors[i]) + m_locomotors[i]->deleteInstance(); + } + m_locomotors.clear(); + m_validLocomotorSurfaces = 0; + m_downhillOnly = FALSE; +} + +//------------------------------------------------------------------------------------------------- +void LocomotorSet::addLocomotor(const LocomotorTemplate* lt) +{ + Locomotor* loco = TheLocomotorStore->newLocomotor(lt); + if (loco) + { + m_locomotors.push_back(loco); + m_validLocomotorSurfaces |= loco->getLegalSurfaces(); + if (loco->getIsDownhillOnly()) + { + m_downhillOnly = TRUE; + } + else // Previous locos were gravity only, but this one isn't! + { + DEBUG_ASSERTCRASH(!m_downhillOnly,("LocomotorSet, YOU CAN NOT MIX DOWNHILL-ONLY LOCOMOTORS WITH NON-DOWNHILL-ONLY ONES.")); + } + + } +} + +//------------------------------------------------------------------------------------------------- +Locomotor* LocomotorSet::findLocomotor(LocomotorSurfaceTypeMask t) +{ + for (LocomotorVector::iterator it = m_locomotors.begin(); it != m_locomotors.end(); ++it) + { + Locomotor* curLocomotor = *it; + if (curLocomotor && (curLocomotor->getLegalSurfaces() & t)) + return curLocomotor; + } + return NULL; +} + + diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/LocomotorSetUpgrade.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/LocomotorSetUpgrade.cpp index 3dd166bb8ed..c5550ff49ef 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/LocomotorSetUpgrade.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/LocomotorSetUpgrade.cpp @@ -30,21 +30,33 @@ // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine +#define DEFINE_LOCOMOTORSET_NAMES //Gain access to TheLocomotorSetNames[] + #include "Common/Xfer.h" #include "GameLogic/Object.h" #include "GameLogic/Module/LocomotorSetUpgrade.h" #include "GameLogic/Module/AIUpdate.h" - - //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- LocomotorSetUpgradeModuleData::LocomotorSetUpgradeModuleData(void) { m_setUpgraded = TRUE; + m_useLocomotorType = FALSE; + m_LocomotorType = LOCOMOTORSET_INVALID; // m_needsParkedAircraft = FALSE; } - +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +/*static*/ void LocomotorSetUpgradeModuleData::parseLocomotorType(INI* ini, void* instance, void* store, const void* /*userData*/) +{ + const char* token = ini->getNextToken(); + if (stricmp(token, "None") != 0) { + LocomotorSetUpgradeModuleData* self = (LocomotorSetUpgradeModuleData*)instance; + self->m_useLocomotorType = true; + *(LocomotorSetType*)store = (LocomotorSetType)INI::scanIndexList(token, TheLocomotorSetNames); + } +} //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- void LocomotorSetUpgradeModuleData::buildFieldParse(MultiIniFieldParse& p) @@ -55,6 +67,7 @@ void LocomotorSetUpgradeModuleData::buildFieldParse(MultiIniFieldParse& p) static const FieldParse dataFieldParse[] = { { "EnableUpgrade", INI::parseBool, NULL, offsetof(LocomotorSetUpgradeModuleData, m_setUpgraded) }, + { "ExplicitLocomotorType", LocomotorSetUpgradeModuleData::parseLocomotorType, NULL, offsetof(LocomotorSetUpgradeModuleData, m_LocomotorType)}, //{ "NeedsParkedAircraft", INI::parseBool, NULL, offsetof(WeaponSetUpgradeModuleData, m_needsParkedAircraft) }, { 0, 0, 0, 0 } }; @@ -81,8 +94,15 @@ void LocomotorSetUpgrade::upgradeImplementation( ) { const LocomotorSetUpgradeModuleData* data = getLocomotorSetUpgradeModuleData(); AIUpdateInterface* ai = getObject()->getAIUpdateInterface(); - if (ai) - ai->setLocomotorUpgrade(data->m_setUpgraded); + if (ai) { + if (data->m_useLocomotorType && data->m_LocomotorType != LOCOMOTORSET_NORMAL_UPGRADED) { + ai->chooseLocomotorSet(data->m_LocomotorType); + } + else { + ai->setLocomotorUpgrade(data->m_setUpgraded); + } + } + } // ------------------------------------------------------------------------------------------------ From 7e1cc566a61401f85e8ac11fbcf6939919ed7734 Mon Sep 17 00:00:00 2001 From: andreasw Date: Mon, 21 Jul 2025 19:27:03 +0200 Subject: [PATCH 37/42] Implement UpgradeSpecialPower --- GeneralsMD/Code/GameEngine/CMakeLists.txt | 2 + .../GameLogic/Module/DelayedUpgradeBehavior.h | 13 +- .../GameLogic/Module/RadiusDecalBehavior.h | 2 + .../GameLogic/Module/UpgradeSpecialPower.h | 78 ++++++++ .../Source/Common/System/MemoryInit.cpp | 1 + .../Source/Common/Thing/ModuleFactory.cpp | 2 + .../SpecialPower/UpgradeSpecialPower.cpp | 175 ++++++++++++++++++ .../Object/Update/RadiusDecalBehavior.cpp | 12 +- 8 files changed, 279 insertions(+), 6 deletions(-) create mode 100644 GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UpgradeSpecialPower.h create mode 100644 GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/UpgradeSpecialPower.cpp diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index 40a8255edde..18fd9478830 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -436,6 +436,7 @@ set(GAMEENGINE_SRC Include/GameLogic/Module/SpecialPowerUpdateModule.h Include/GameLogic/Module/SpectreGunshipDeploymentUpdate.h Include/GameLogic/Module/SpectreGunshipUpdate.h + Include/GameLogic/Module/UpgradeSpecialPower.h Include/GameLogic/Module/SpyVisionSpecialPower.h Include/GameLogic/Module/SpyVisionUpdate.h Include/GameLogic/Module/SquishCollide.h @@ -972,6 +973,7 @@ set(GAMEENGINE_SRC Source/GameLogic/Object/SpecialPower/OCLSpecialPower.cpp Source/GameLogic/Object/SpecialPower/SpecialAbility.cpp Source/GameLogic/Object/SpecialPower/SpecialPowerModule.cpp + Source/GameLogic/Object/SpecialPower/UpgradeSpecialPower.cpp Source/GameLogic/Object/SpecialPower/SpyVisionSpecialPower.cpp Source/GameLogic/Object/Update/AIUpdate.cpp Source/GameLogic/Object/Update/AIUpdate/AssaultTransportAIUpdate.cpp diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DelayedUpgradeBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DelayedUpgradeBehavior.h index 74228c5902d..c4b69c3068e 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DelayedUpgradeBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DelayedUpgradeBehavior.h @@ -23,8 +23,8 @@ //////////////////////////////////////////////////////////////////////////////// // FILE: DelayedUpgradeBehavior.h ///////////////////////////////////////////////////////////////////////// -// Author: Colin Day, December 2001 -// Desc: Update that will count down a lifetime and destroy object when it reaches zero +// Author: Andi W, July 2025 +// Desc: Update that will trigger an upgrade after some time /////////////////////////////////////////////////////////////////////////////////////////////////// #pragma once @@ -48,14 +48,14 @@ class DelayedUpgradeBehaviorModuleData : public UpdateModuleData Bool m_initiallyActive; AsciiString m_upgradeToTrigger; UnsignedInt m_triggerDelay; - UnsignedInt m_triggerNumShots; + //UnsignedInt m_triggerNumShots; DelayedUpgradeBehaviorModuleData() { m_initiallyActive = false; m_upgradeToTrigger.clear(); m_triggerDelay = 0; - m_triggerNumShots = 0; + //m_triggerNumShots = 0; } static void buildFieldParse(MultiIniFieldParse& p) @@ -65,7 +65,7 @@ class DelayedUpgradeBehaviorModuleData : public UpdateModuleData { "StartsActive", INI::parseBool, NULL, offsetof(DelayedUpgradeBehaviorModuleData, m_initiallyActive) }, { "UpgradeToTrigger", INI::parseAsciiString, NULL, offsetof(DelayedUpgradeBehaviorModuleData, m_upgradeToTrigger) }, { "TriggerAfterTime", INI::parseDurationUnsignedInt, NULL, offsetof(DelayedUpgradeBehaviorModuleData, m_triggerDelay) }, - { "TriggerAfterShotsFired", INI::parseUnsignedInt, NULL, offsetof(DelayedUpgradeBehaviorModuleData, m_triggerNumShots) }, + //{ "TriggerAfterShotsFired", INI::parseUnsignedInt, NULL, offsetof(DelayedUpgradeBehaviorModuleData, m_triggerNumShots) }, { 0, 0, 0, 0 } }; @@ -103,6 +103,9 @@ class DelayedUpgradeBehavior : public UpdateModule, public UpgradeMux // UpdateModule virtual UpdateSleepTime update(); + // This should be active while disabled + virtual DisabledMaskType getDisabledTypesToProcess() const { return DISABLEDMASK_ALL; } + protected: void triggerUpgrade(); diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h index 9fba409cdc5..acaa1984030 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h @@ -79,6 +79,8 @@ class RadiusDecalBehavior : public UpdateModule, public UpgradeMux // UpdateModuleInterface virtual UpdateSleepTime update(); + virtual DisabledMaskType getDisabledTypesToProcess() const { return MAKE_DISABLED_MASK(DISABLED_HELD); } + protected: diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UpgradeSpecialPower.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UpgradeSpecialPower.h new file mode 100644 index 00000000000..8ec2286d3f2 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UpgradeSpecialPower.h @@ -0,0 +1,78 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: UpgradeSpecialPower.h ///////////////////////////////////////////////////////////////// +// Author: Andreas W, July 25 +// Desc: Special Power will grant an upgrade to the object +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __UPGRADE_SPECIAL_POWER_H_ +#define __UPGRADE_SPECIAL_POWER_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "GameLogic/Module/SpecialPowerModule.h" + +// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// +class FXList; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class UpgradeSpecialPowerModuleData : public SpecialPowerModuleData +{ + +public: + + UpgradeSpecialPowerModuleData(void); + + static void buildFieldParse(MultiIniFieldParse& p); + + AsciiString m_upgradeName; ///< name of the upgrade to be granted. + +}; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class UpgradeSpecialPower : public SpecialPowerModule +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(UpgradeSpecialPower, "UpgradeSpecialPower") + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA(UpgradeSpecialPower, UpgradeSpecialPowerModuleData) + +public: + + UpgradeSpecialPower(Thing* thing, const ModuleData* moduleData); + // virtual destructor prototype provided by memory pool object + + virtual void doSpecialPower(UnsignedInt commandOptions); + + virtual void doSpecialPowerAtObject(Object* obj, UnsignedInt commandOptions); + +protected: + + void grantUpgrade(Object* object); +}; + +#endif diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp index 61af1d740f1..3fbabf06db2 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp @@ -401,6 +401,7 @@ static PoolSizeRec sizes[] = { "GrantScienceUpgrade", 256, 32 }, { "ReplaceObjectUpgrade", 32, 32 }, { "ModelConditionUpgrade", 32, 32 }, + { "UpgradeSpecialPower", 64, 32 }, { "SpyVisionSpecialPower", 256, 32 }, { "StealthDetectorUpdate", 256, 32 }, { "StealthUpdate", 512, 128 }, diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index 26e059f1edf..e7dfe842d35 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -277,6 +277,7 @@ #include "GameLogic/Module/OCLSpecialPower.h" #include "GameLogic/Module/SpecialAbility.h" #include "GameLogic/Module/SpyVisionSpecialPower.h" +#include "GameLogic/Module/UpgradeSpecialPower.h" #include "GameLogic/Module/CashBountyPower.h" #include "GameLogic/Module/CleanupAreaPower.h" #include "GameLogic/Module/FireWeaponPower.h" @@ -569,6 +570,7 @@ void ModuleFactory::init( void ) addModule( FireWeaponPower ); addModule( SpecialAbility ); addModule( SpyVisionSpecialPower ); + addModule( UpgradeSpecialPower ); addModule( CashBountyPower ); addModule( CleanupAreaPower ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/UpgradeSpecialPower.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/UpgradeSpecialPower.cpp new file mode 100644 index 00000000000..7f839c16def --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/UpgradeSpecialPower.cpp @@ -0,0 +1,175 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: UpgradeSpecialPower.cpp ///////////////////////////////////////////////////////////////// +// Author: Andreas W, July 25 +// Desc: Special Power will grant an upgrade to the object +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#include "Common/Xfer.h" +#include "Common/Player.h" +#include "Common/Upgrade.h" +#include "GameLogic/Object.h" +#include "GameLogic/Module/UpgradeSpecialPower.h" + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +UpgradeSpecialPowerModuleData::UpgradeSpecialPowerModuleData(void) +{ + m_upgradeName = ""; +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPowerModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + SpecialPowerModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + { "UpgradeToGrant", INI::parseAsciiString, NULL, offsetof(UpgradeSpecialPowerModuleData, m_upgradeName) }, + { 0, 0, 0, 0 } + }; + p.add(dataFieldParse); + +} // end buildFieldParse + +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +UpgradeSpecialPower::UpgradeSpecialPower(Thing* thing, const ModuleData* moduleData) + : SpecialPowerModule(thing, moduleData) +{ + +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +UpgradeSpecialPower::~UpgradeSpecialPower(void) +{ + +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPower::grantUpgrade(Object* object) { + + // get module data + const UpgradeSpecialPowerModuleData* modData = getUpgradeSpecialPowerModuleData(); + + const UpgradeTemplate* upgradeTemplate = TheUpgradeCenter->findUpgrade(modData->m_upgradeName); + if (!upgradeTemplate) + { + DEBUG_ASSERTCRASH(0, ("UpgradeSpecialPower for %s can't find upgrade template %s.", getObject()->getName(), modData->m_upgradeName)); + return; + } + + Player* player = object->getControllingPlayer(); + if (upgradeTemplate->getUpgradeType() == UPGRADE_TYPE_PLAYER) + { + // get the player + player->addUpgrade(upgradeTemplate, UPGRADE_STATUS_COMPLETE); + } + else + { + object->giveUpgrade(upgradeTemplate); + } + + player->getAcademyStats()->recordUpgrade(upgradeTemplate, TRUE); +} + + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPower::doSpecialPower(UnsignedInt commandOptions) +{ + if (getObject()->isDisabled()) + return; + + // call the base class action cause we are *EXTENDING* functionality + SpecialPowerModule::doSpecialPower(commandOptions); + + // Grant the upgrade + grantUpgrade(getObject()); +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPower::doSpecialPowerAtObject(Object* obj, UnsignedInt commandOptions) +{ + if (getObject()->isDisabled()) + return; + + // call the base class action cause we are *EXTENDING* functionality + SpecialPowerModule::doSpecialPowerAtObject(obj, commandOptions); + + // Grant the upgrade + grantUpgrade(obj); +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPower::crc(Xfer* xfer) +{ + + // extend base class + SpecialPowerModule::crc(xfer); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ + // ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPower::xfer(Xfer* xfer) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion(&version, currentVersion); + + // extend base class + SpecialPowerModule::xfer(xfer); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPower::loadPostProcess(void) +{ + + // extend base class + SpecialPowerModule::loadPostProcess(); + +} // end loadPostProcess diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp index 572613bf0e7..7b3fbd13a56 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp @@ -120,6 +120,12 @@ void RadiusDecalBehavior::clearDecal() //------------------------------------------------------------------------------------------------- UpdateSleepTime RadiusDecalBehavior::update( void ) { + if (getObject()->isDisabledByType(DISABLED_HELD)) { + if (!m_radiusDecal.isEmpty()) + clearDecal(); + return UPDATE_SLEEP_NONE; // We wait to be re-enabled + } + // Upgrade has not been triggered, or it might have been removed. if (!isUpgradeActive()) { clearDecal(); @@ -139,8 +145,12 @@ UpdateSleepTime RadiusDecalBehavior::update( void ) return UPDATE_SLEEP_NONE; } + // We get here if we were disabled + createRadiusDecal(); + return UPDATE_SLEEP_NONE; + // Something probably went wrong if we reach this point - return UPDATE_SLEEP_FOREVER; + //return UPDATE_SLEEP_FOREVER; } // ------------------------------------------------------------------------------------------------ From 3792fb800a2edfa68bdac0debc7e53856abda21b Mon Sep 17 00:00:00 2001 From: andreasw Date: Wed, 23 Jul 2025 17:40:00 +0200 Subject: [PATCH 38/42] fix line endings --- .../GameEngine/Include/GameLogic/Locomotor.h | 1032 +-- .../GameLogic/Module/ParkingPlaceBehavior.h | 476 +- .../Source/Common/System/MemoryInit.cpp | 1632 ++--- .../Source/Common/Thing/ModuleFactory.cpp | 1498 ++--- .../Source/GameLogic/Object/Locomotor.cpp | 5688 ++++++++--------- 5 files changed, 5163 insertions(+), 5163 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h index dfd03f1f638..d694becb90a 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h @@ -1,516 +1,516 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: Locomotor.h ///////////////////////////////////////////////////////////////////////////////// -// Author: Steven Johnson, Feb 2002 -// Desc: Locomotor Descriptions -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __Locomotor_H_ -#define __Locomotor_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "Common/NameKeyGenerator.h" -#include "Common/Override.h" -#include "Common/Snapshot.h" -#include "GameLogic/Damage.h" -#include "GameLogic/LocomotorSet.h" - -// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// -class Locomotor; -class LocomotorTemplate; -class INI; -class PhysicsBehavior; -enum BodyDamageType CPP_11(: Int); -enum PhysicsTurningType CPP_11(: Int); - -// if we ever re-enable jets circling for landing, we need this. so keep in around just in case. (srj) -#define NO_CIRCLE_FOR_LANDING - -//------------------------------------------------------------------------------------------------- -enum LocomotorAppearance CPP_11(: Int) -{ - LOCO_LEGS_TWO, - LOCO_WHEELS_FOUR, - LOCO_TREADS, - LOCO_HOVER, - LOCO_THRUST, - LOCO_WINGS, - LOCO_CLIMBER, // human climber - backs down cliffs. - LOCO_OTHER, - LOCO_MOTORCYCLE -}; - -enum LocomotorPriority CPP_11(: Int) -{ - LOCO_MOVES_BACK=0, // In a group, this one moves toward the back - LOCO_MOVES_MIDDLE=1, // In a group, this one stays in the middle - LOCO_MOVES_FRONT=2 // In a group, this one moves toward the front of the group -}; - -#ifdef DEFINE_LOCO_APPEARANCE_NAMES -static const char *TheLocomotorAppearanceNames[] = -{ - "TWO_LEGS", - "FOUR_WHEELS", - "TREADS", - "HOVER", - "THRUST", - "WINGS", - "CLIMBER", - "OTHER", - "MOTORCYCLE", - - NULL -}; -#endif - -//------------------------------------------------------------------------------------------------- -enum LocomotorBehaviorZ CPP_11(: Int) -{ - Z_NO_Z_MOTIVE_FORCE, // does whatever physics tells it, but has no z-force of its own. - Z_SEA_LEVEL, // keep at surface-of-water level - Z_SURFACE_RELATIVE_HEIGHT, // try to follow a specific height relative to terrain/water height - Z_ABSOLUTE_HEIGHT, // try follow a specific height regardless of terrain/water height - Z_FIXED_SURFACE_RELATIVE_HEIGHT, // stays fixed at surface-rel height, regardless of physics - Z_FIXED_ABSOLUTE_HEIGHT, // stays fixed at absolute height, regardless of physics - Z_RELATIVE_TO_GROUND_AND_BUILDINGS, // stays fixed at surface-rel height including buildings, regardless of physics - Z_SMOOTH_RELATIVE_TO_HIGHEST_LAYER // try to follow a height relative to the highest layer. -}; - -#ifdef DEFINE_LOCO_Z_NAMES -static const char *TheLocomotorBehaviorZNames[] = -{ - "NO_Z_MOTIVE_FORCE", - "SEA_LEVEL", - "SURFACE_RELATIVE_HEIGHT", - "ABSOLUTE_HEIGHT", - "FIXED_SURFACE_RELATIVE_HEIGHT", - "FIXED_ABSOLUTE_HEIGHT", - "FIXED_RELATIVE_TO_GROUND_AND_BUILDINGS", - "RELATIVE_TO_HIGHEST_LAYER", - - NULL -}; -#endif - -//------------------------------------------------------------------------------------------------- -class LocomotorTemplate : public Overridable -{ - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( LocomotorTemplate, "LocomotorTemplate" ) - friend class Locomotor; - -public: - - LocomotorTemplate(); - - /// field table for loading the values from an INI - const FieldParse* getFieldParse() const; - - void friend_setName(const AsciiString& n) { m_name = n; } - - void validate(); - -protected: - - -private: - /** - Units check: - - -- Velocity: dist/frame - -- Acceleration: dist/(frame*frame) - -- Forces: (mass*dist)/(frame*frame) - */ - AsciiString m_name; - LocomotorSurfaceTypeMask m_surfaces; ///< flags indicating the kinds of surfaces we can use - Real m_maxSpeed; ///< max speed - Real m_maxSpeedDamaged; ///< max speed when "damaged" - Real m_minSpeed; ///< we should never brake past this - Real m_maxTurnRate; ///< max rate at which we can turn, in rads/frame - Real m_maxTurnRateDamaged; ///< max turn rate when "damaged" - Real m_acceleration; ///< max acceleration - Real m_accelerationDamaged; ///< max acceleration when damaged - Real m_lift; ///< max lifting acceleration (flying objects only) - Real m_liftDamaged; ///< max lift when damaged - Real m_braking; ///< max braking (deceleration) - Real m_minTurnSpeed; ///< we must be going >= this speed in order to turn - Real m_preferredHeight; ///< our preferred height (if flying) - Real m_preferredHeightDamping; ///< how aggressively to adjust to preferred height: 1.0 = very much so, 0.1 = gradually, etc - Real m_circlingRadius; ///< for flying things, the radius at which they circle their "maintain" destination. (pos = cw, neg = ccw, 0 = smallest possible) - Real m_speedLimitZ; ///< try to avoid going up or down at more than this speed, if possible - Real m_extra2DFriction; ///< extra 2dfriction to apply (via Physics) - Real m_maxThrustAngle; ///< THRUST locos only: how much we deflect our thrust angle - LocomotorBehaviorZ m_behaviorZ; ///< z-axis behavior - LocomotorAppearance m_appearance; ///< how we should diddle the Drawable to imitate this motion - LocomotorPriority m_movePriority; ///< Where we move - front, middle, back. - - Real m_accelPitchLimit; ///< Maximum amount we will pitch up under acceleration (including recoil.) - Real m_decelPitchLimit; ///< Maximum amount we will pitch down under deceleration (including recoil.) - Real m_bounceKick; ///< How much simulating rough terrain "bounces" a wheel up. - Real m_pitchStiffness; ///< How stiff the springs are forward & back. - Real m_rollStiffness; ///< How stiff the springs are side to side. - Real m_pitchDamping; ///< How good the shock absorbers are. - Real m_rollDamping; ///< How good the shock absorbers are. - Real m_pitchByZVelCoef; ///< How much we pitch in response to z-speed. - Real m_thrustRoll; ///< Thrust roll around X axis - Real m_wobbleRate; ///< how fast thrust things "wobble" - Real m_minWobble; ///< how much thrust things "wobble" - Real m_maxWobble; ///< how much thrust things "wobble" - Real m_forwardVelCoef; ///< How much we pitch in response to speed. - Real m_lateralVelCoef; ///< How much we roll in response to speed. - Real m_forwardAccelCoef; ///< How much we pitch in response to acceleration. - Real m_lateralAccelCoef; ///< How much we roll in response to acceleration. - Real m_uniformAxialDamping; ///< For Attenuating the pitch and roll rates - Real m_turnPivotOffset; ///< should we pivot around noncenter? (-1.0 = rear, 0.0 = center, 1.0 = front) - Int m_airborneTargetingHeight; ///< The height transition at witch I should mark myself as a AA target. - - Real m_closeEnoughDist; ///< How close we have to approach the end of a path before stopping - Bool m_isCloseEnoughDist3D; ///< And is that calculation 3D, for very rare cases that need to move straight down. - Real m_ultraAccurateSlideIntoPlaceFactor; ///< how much we can fudge turning when ultra-accurate - - Bool m_locomotorWorksWhenDead; ///< should locomotor continue working even when object is "dead"? - Bool m_allowMotiveForceWhileAirborne; ///< can we apply motive when airborne? - Bool m_apply2DFrictionWhenAirborne; // apply "2d friction" even when airborne... useful for realistic-looking movement - Bool m_downhillOnly; // pinewood derby, moves only by gravity pulling downhill - Bool m_stickToGround; // if true, can't leave ground - Bool m_canMoveBackward; // if true, can move backwards. - Bool m_hasSuspension; ///< If true, calculate 4 wheel independent suspension values. - Real m_maximumWheelExtension; ///< Maximum distance wheels can move down. (negative value) - Real m_maximumWheelCompression; ///< Maximum distance wheels can move up. (positive value) - Real m_wheelTurnAngle; ///< How far the front wheels can turn. - - // Fields for wander locomotor - Real m_wanderWidthFactor; - Real m_wanderLengthFactor; - Real m_wanderAboutPointRadius; - - - Real m_rudderCorrectionDegree; - Real m_rudderCorrectionRate; - Real m_elevatorCorrectionDegree; - Real m_elevatorCorrectionRate; -}; - -typedef OVERRIDE LocomotorTemplateOverride; - -// --------------------------------------------------------- -class Locomotor : public MemoryPoolObject, public Snapshot -{ - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(Locomotor, "Locomotor" ) - - friend class LocomotorStore; - -public: - - void setPhysicsOptions(Object* obj); - - void locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalPos, - Real onPathDistToGoal, Real desiredSpeed, Bool *blocked); - void locoUpdate_moveTowardsAngle(Object* obj, Real angle); - /** - Kill any current (2D) velocity (but stay at current position, or as close as possible) - - return true if we can maintain the position without being called every frame (eg, we are - resting on the ground), false if not (eg, we are hovering or circling) - */ - Bool locoUpdate_maintainCurrentPosition(Object* obj); - - Real getMaxSpeedForCondition(BodyDamageType condition) const; ///< get max speed given condition - Real getMaxTurnRate(BodyDamageType condition) const; ///< get max turning rate given condition - Real getMaxAcceleration(BodyDamageType condition) const; ///< get acceleration given condition - Real getMaxLift(BodyDamageType condition) const; ///< get acceleration given condition - Real getBraking() const; ///< get braking given condition - - inline Real getPreferredHeight() const { return m_preferredHeight;} ///< Just return preferredheight, no damage consideration - inline void restorePreferredHeightFromTemplate() { m_preferredHeight = m_template->m_preferredHeight; }; - inline Real getPreferredHeightDamping() const { return m_preferredHeightDamping;} - inline LocomotorAppearance getAppearance() const { return m_template->m_appearance; } - inline LocomotorPriority getMovePriority() const { return m_template->m_movePriority; } - inline LocomotorSurfaceTypeMask getLegalSurfaces() const { return m_template->m_surfaces; } - - inline AsciiString getTemplateName() const { return m_template->m_name;} - inline Real getMinSpeed() const { return m_template->m_minSpeed;} - inline Real getAccelPitchLimit() const { return m_template->m_accelPitchLimit;} ///< Maximum amount we will pitch up or down under acceleration (including recoil.) - inline Real getDecelPitchLimit() const { return m_template->m_decelPitchLimit;} ///< Maximum amount we will pitch down under deceleration (including recoil.) - inline Real getBounceKick() const { return m_template->m_bounceKick;} ///< How much simulating rough terrain "bounces" a wheel up. - inline Real getPitchStiffness() const { return m_template->m_pitchStiffness;} ///< How stiff the springs are forward & back. - inline Real getRollStiffness() const { return m_template->m_rollStiffness;} ///< How stiff the springs are side to side. - inline Real getPitchDamping() const { return m_template->m_pitchDamping;} ///< How good the shock absorbers are. - inline Real getRollDamping() const { return m_template->m_rollDamping;} ///< How good the shock absorbers are. - inline Real getPitchByZVelCoef() const { return m_template->m_pitchByZVelCoef;} ///< How much we pitch in response to speed. - inline Real getThrustRoll() const { return m_template->m_thrustRoll; } ///< Thrust roll - inline Real getWobbleRate() const { return m_template->m_wobbleRate; } ///< how fast thrust things "wobble" - inline Real getMaxWobble() const { return m_template->m_maxWobble; } ///< how much thrust things "wobble" - inline Real getMinWobble() const { return m_template->m_minWobble; } ///< how much thrust things "wobble" - - inline Real getForwardVelCoef() const { return m_template->m_forwardVelCoef;} ///< How much we pitch in response to speed. - inline Real getLateralVelCoef() const { return m_template->m_lateralVelCoef;} ///< How much we roll in response to speed. - inline Real getForwardAccelCoef() const { return m_template->m_forwardAccelCoef;} ///< How much we pitch in response to acceleration. - inline Real getLateralAccelCoef() const { return m_template->m_lateralAccelCoef;} ///< How much we roll in response to acceleration. - inline Real getUniformAxialDamping() const { return m_template->m_uniformAxialDamping;} ///< How much we roll in response to acceleration. - inline Real getTurnPivotOffset() const { return m_template->m_turnPivotOffset;} - inline Bool getApply2DFrictionWhenAirborne() const { return m_template->m_apply2DFrictionWhenAirborne; } - inline Bool getIsDownhillOnly() const { return m_template->m_downhillOnly; } - inline Bool getAllowMotiveForceWhileAirborne() const { return m_template->m_allowMotiveForceWhileAirborne; } - inline Int getAirborneTargetingHeight() const { return m_template->m_airborneTargetingHeight; } - inline Bool getLocomotorWorksWhenDead() const { return m_template->m_locomotorWorksWhenDead; } - inline Bool getStickToGround() const { return m_template->m_stickToGround; } - inline Real getCloseEnoughDist() const { return m_closeEnoughDist; } - inline Bool isCloseEnoughDist3D() const { return getFlag(IS_CLOSE_ENOUGH_DIST_3D); } - inline Bool hasSuspension() const {return m_template->m_hasSuspension;} - inline Bool canMoveBackwards() const {return m_template->m_canMoveBackward;} - inline Real getMaxWheelExtension() const {return m_template->m_maximumWheelExtension;} - inline Real getMaxWheelCompression() const {return m_template->m_maximumWheelCompression;} - inline Real getWheelTurnAngle() const {return m_template->m_wheelTurnAngle;} - - - inline Real getRudderCorrectionDegree() const { return m_template->m_rudderCorrectionDegree;} ///< How much we roll in response to acceleration. - inline Real getRudderCorrectionRate() const { return m_template->m_rudderCorrectionRate;} ///< How much we roll in response to acceleration. - inline Real getElevatorCorrectionDegree() const { return m_template->m_elevatorCorrectionDegree;} ///< How much we roll in response to acceleration. - inline Real getElevatorCorrectionRate() const { return m_template->m_elevatorCorrectionRate;} ///< How much we roll in response to acceleration. - - - inline Real getWanderWidthFactor() const {return m_template->m_wanderWidthFactor;} - inline Real getWanderAboutPointRadius() const {return m_template->m_wanderAboutPointRadius;} - - Real calcMinTurnRadius(BodyDamageType condition, Real* timeToTravelThatDist) const; - - /// this is handy for doing things like forcing helicopters to crash realistically: cut their lift. - inline void setMaxLift(Real lift) { m_maxLift = lift; } - inline void setMaxSpeed(Real speed) - { - DEBUG_ASSERTCRASH(!(speed <= 0.0f && m_template->m_appearance == LOCO_THRUST), ("THRUST locos may not have zero speeds!\n")); - m_maxSpeed = speed; - } - inline void setMaxAcceleration(Real accel) { m_maxAccel = accel; } - inline void setMaxBraking(Real braking) { m_maxBraking = braking; } - inline void setMaxTurnRate(Real turn) { m_maxTurnRate = turn; } - inline void setAllowInvalidPosition(Bool allow) { setFlag(ALLOW_INVALID_POSITION, allow); } - inline void setCloseEnoughDist( Real dist ) { m_closeEnoughDist = dist; } - inline void setCloseEnoughDist3D( Bool setting ) { setFlag(IS_CLOSE_ENOUGH_DIST_3D, setting); } - inline Bool isInvalidPositionAllowed() const { return getFlag( ALLOW_INVALID_POSITION ); } - - inline void setPreferredHeight( Real height ) { m_preferredHeight = height; } - -#ifdef CIRCLE_FOR_LANDING - /** - if we are climbing/diving more than this, circle as needed rather - than just diving or climbing directly. (only useful for Winged things) - */ - inline void setAltitudeChangeThresholdForCircling(Real a) { m_circleThresh = a; } -#endif - - /** - when off (the default), things get to adjust their z-pos as their - loco says (in particular, airborne things tend to try to fly at a preferred height). - - when on, they do their best to reach the specified zpos, even if it's not at their preferred height. - this is used mainly for force missiles to swoop in on their target, and to force airplane takeoff/landing - to go smoothly. - */ - inline void setUsePreciseZPos(Bool u) { setFlag(PRECISE_Z_POS, u); } - - /** - when off (the default), units slow down as they approach their target. - - when on, units go full speed till the end, and may overshoot their target. - this is useful mainly in some weird, temporary situations where we know we are - going to follow this move with another one... or for carbombs. - */ - inline void setNoSlowDownAsApproachingDest(Bool u) { setFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST, u); } - - /** - when off (the default), units do their normal stuff. - - when on, we cheat and make very precise motion, regardless of loco settings. - this is accomplished by cranking up the unit's turning rate, friction, lift (for airborne things), - and possibly other things. This is useful mainly when doing maneuvers where precision - is VITAL, such as airplane takeoff/landing. - - For ground units, it also allows units to have a destination off of a pathfing grid. - - */ - inline void setUltraAccurate(Bool u) { setFlag(ULTRA_ACCURATE, u); } - inline Bool isUltraAccurate() const { return getFlag(ULTRA_ACCURATE); } - - inline Bool isMovingBackwards(void) const {return getFlag(MOVING_BACKWARDS);} - - void startMove(void); ///< Indicates that a move is starting, primarily to reset the donut timer. jba. - - static Real getSurfaceHtAtPt(Real x, Real y); - - inline void applySpeedMultiplier(Real scalar) { m_speedMultiplier *= scalar; } - // inline void setSpeedMultiplier(Real value) { m_speedMultiplier = value; } - inline Real getSpeedMultiplier(void) const { return m_speedMultiplier; } - -protected: - void moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionLegsWander(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionHover(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - void moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); - - void maintainCurrentPositionThrust(Object* obj, PhysicsBehavior *physics); - void maintainCurrentPositionOther(Object* obj, PhysicsBehavior *physics); - void maintainCurrentPositionLegs(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } - void maintainCurrentPositionWheels(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } - void maintainCurrentPositionTreads(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } - void maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physics); - void maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physics); - - PhysicsTurningType rotateTowardsPosition(Object* obj, const Coord3D& goalPos, Real *relAngle=NULL); - - /* - return true if we can maintain the position without being called every frame (eg, we are - resting on the ground), false if not (eg, we are hovering or circling) - */ - Bool handleBehaviorZ(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos); - PhysicsTurningType rotateObjAroundLocoPivot(Object* obj, const Coord3D& goalPos, Real maxTurnRate, Real *relAngle = NULL); - - Real calcLiftToUseAtPt(Object* obj, PhysicsBehavior *physics, Real curZ, Real surfaceAtPt, Real preferredHeight); - - Bool fixInvalidPosition(Object* obj, PhysicsBehavior *physics); - -protected: - // snapshot methods - virtual void crc( Xfer *xfer ); - virtual void xfer( Xfer *xfer ); - virtual void loadPostProcess( void ); - -protected: - - Locomotor(const LocomotorTemplate* tmpl); - - // Note, "Law of the Big Three" applies here - //Locomotor(); -- nope, we don't have a default ctor. (srj) - Locomotor(const Locomotor& that); - Locomotor& operator=(const Locomotor& that); - //~Locomotor(); - -private: - - // - // Note: these values are saved in save files, so you MUST NOT REMOVE OR CHANGE - // existing values! - // - enum LocoFlag - { - IS_BRAKING = 0, - ALLOW_INVALID_POSITION, - MAINTAIN_POS_IS_VALID, - PRECISE_Z_POS, - NO_SLOW_DOWN_AS_APPROACHING_DEST, - OVER_WATER, // To allow things to move slower/faster over water and do special effects - ULTRA_ACCURATE, - MOVING_BACKWARDS, // If we are moving backwards. - DOING_THREE_POINT_TURN, // If we are doing a 3 pt turn. - CLIMBING, // If we are in the process of climbing. - IS_CLOSE_ENOUGH_DIST_3D, - OFFSET_INCREASING - }; - - inline Bool getFlag(LocoFlag f) const { return (m_flags & (1 << f)) != 0; } - inline void setFlag(LocoFlag f, Bool b) { if (b) m_flags |= (1< > LocomotorTemplateMap; - - LocomotorTemplateMap m_locomotorTemplates; - -}; - -// EXTERNALS ////////////////////////////////////////////////////////////////////////////////////// -extern LocomotorStore *TheLocomotorStore; - -#endif // __Locomotor_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: Locomotor.h ///////////////////////////////////////////////////////////////////////////////// +// Author: Steven Johnson, Feb 2002 +// Desc: Locomotor Descriptions +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __Locomotor_H_ +#define __Locomotor_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Common/NameKeyGenerator.h" +#include "Common/Override.h" +#include "Common/Snapshot.h" +#include "GameLogic/Damage.h" +#include "GameLogic/LocomotorSet.h" + +// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// +class Locomotor; +class LocomotorTemplate; +class INI; +class PhysicsBehavior; +enum BodyDamageType CPP_11(: Int); +enum PhysicsTurningType CPP_11(: Int); + +// if we ever re-enable jets circling for landing, we need this. so keep in around just in case. (srj) +#define NO_CIRCLE_FOR_LANDING + +//------------------------------------------------------------------------------------------------- +enum LocomotorAppearance CPP_11(: Int) +{ + LOCO_LEGS_TWO, + LOCO_WHEELS_FOUR, + LOCO_TREADS, + LOCO_HOVER, + LOCO_THRUST, + LOCO_WINGS, + LOCO_CLIMBER, // human climber - backs down cliffs. + LOCO_OTHER, + LOCO_MOTORCYCLE +}; + +enum LocomotorPriority CPP_11(: Int) +{ + LOCO_MOVES_BACK=0, // In a group, this one moves toward the back + LOCO_MOVES_MIDDLE=1, // In a group, this one stays in the middle + LOCO_MOVES_FRONT=2 // In a group, this one moves toward the front of the group +}; + +#ifdef DEFINE_LOCO_APPEARANCE_NAMES +static const char *TheLocomotorAppearanceNames[] = +{ + "TWO_LEGS", + "FOUR_WHEELS", + "TREADS", + "HOVER", + "THRUST", + "WINGS", + "CLIMBER", + "OTHER", + "MOTORCYCLE", + + NULL +}; +#endif + +//------------------------------------------------------------------------------------------------- +enum LocomotorBehaviorZ CPP_11(: Int) +{ + Z_NO_Z_MOTIVE_FORCE, // does whatever physics tells it, but has no z-force of its own. + Z_SEA_LEVEL, // keep at surface-of-water level + Z_SURFACE_RELATIVE_HEIGHT, // try to follow a specific height relative to terrain/water height + Z_ABSOLUTE_HEIGHT, // try follow a specific height regardless of terrain/water height + Z_FIXED_SURFACE_RELATIVE_HEIGHT, // stays fixed at surface-rel height, regardless of physics + Z_FIXED_ABSOLUTE_HEIGHT, // stays fixed at absolute height, regardless of physics + Z_RELATIVE_TO_GROUND_AND_BUILDINGS, // stays fixed at surface-rel height including buildings, regardless of physics + Z_SMOOTH_RELATIVE_TO_HIGHEST_LAYER // try to follow a height relative to the highest layer. +}; + +#ifdef DEFINE_LOCO_Z_NAMES +static const char *TheLocomotorBehaviorZNames[] = +{ + "NO_Z_MOTIVE_FORCE", + "SEA_LEVEL", + "SURFACE_RELATIVE_HEIGHT", + "ABSOLUTE_HEIGHT", + "FIXED_SURFACE_RELATIVE_HEIGHT", + "FIXED_ABSOLUTE_HEIGHT", + "FIXED_RELATIVE_TO_GROUND_AND_BUILDINGS", + "RELATIVE_TO_HIGHEST_LAYER", + + NULL +}; +#endif + +//------------------------------------------------------------------------------------------------- +class LocomotorTemplate : public Overridable +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( LocomotorTemplate, "LocomotorTemplate" ) + friend class Locomotor; + +public: + + LocomotorTemplate(); + + /// field table for loading the values from an INI + const FieldParse* getFieldParse() const; + + void friend_setName(const AsciiString& n) { m_name = n; } + + void validate(); + +protected: + + +private: + /** + Units check: + + -- Velocity: dist/frame + -- Acceleration: dist/(frame*frame) + -- Forces: (mass*dist)/(frame*frame) + */ + AsciiString m_name; + LocomotorSurfaceTypeMask m_surfaces; ///< flags indicating the kinds of surfaces we can use + Real m_maxSpeed; ///< max speed + Real m_maxSpeedDamaged; ///< max speed when "damaged" + Real m_minSpeed; ///< we should never brake past this + Real m_maxTurnRate; ///< max rate at which we can turn, in rads/frame + Real m_maxTurnRateDamaged; ///< max turn rate when "damaged" + Real m_acceleration; ///< max acceleration + Real m_accelerationDamaged; ///< max acceleration when damaged + Real m_lift; ///< max lifting acceleration (flying objects only) + Real m_liftDamaged; ///< max lift when damaged + Real m_braking; ///< max braking (deceleration) + Real m_minTurnSpeed; ///< we must be going >= this speed in order to turn + Real m_preferredHeight; ///< our preferred height (if flying) + Real m_preferredHeightDamping; ///< how aggressively to adjust to preferred height: 1.0 = very much so, 0.1 = gradually, etc + Real m_circlingRadius; ///< for flying things, the radius at which they circle their "maintain" destination. (pos = cw, neg = ccw, 0 = smallest possible) + Real m_speedLimitZ; ///< try to avoid going up or down at more than this speed, if possible + Real m_extra2DFriction; ///< extra 2dfriction to apply (via Physics) + Real m_maxThrustAngle; ///< THRUST locos only: how much we deflect our thrust angle + LocomotorBehaviorZ m_behaviorZ; ///< z-axis behavior + LocomotorAppearance m_appearance; ///< how we should diddle the Drawable to imitate this motion + LocomotorPriority m_movePriority; ///< Where we move - front, middle, back. + + Real m_accelPitchLimit; ///< Maximum amount we will pitch up under acceleration (including recoil.) + Real m_decelPitchLimit; ///< Maximum amount we will pitch down under deceleration (including recoil.) + Real m_bounceKick; ///< How much simulating rough terrain "bounces" a wheel up. + Real m_pitchStiffness; ///< How stiff the springs are forward & back. + Real m_rollStiffness; ///< How stiff the springs are side to side. + Real m_pitchDamping; ///< How good the shock absorbers are. + Real m_rollDamping; ///< How good the shock absorbers are. + Real m_pitchByZVelCoef; ///< How much we pitch in response to z-speed. + Real m_thrustRoll; ///< Thrust roll around X axis + Real m_wobbleRate; ///< how fast thrust things "wobble" + Real m_minWobble; ///< how much thrust things "wobble" + Real m_maxWobble; ///< how much thrust things "wobble" + Real m_forwardVelCoef; ///< How much we pitch in response to speed. + Real m_lateralVelCoef; ///< How much we roll in response to speed. + Real m_forwardAccelCoef; ///< How much we pitch in response to acceleration. + Real m_lateralAccelCoef; ///< How much we roll in response to acceleration. + Real m_uniformAxialDamping; ///< For Attenuating the pitch and roll rates + Real m_turnPivotOffset; ///< should we pivot around noncenter? (-1.0 = rear, 0.0 = center, 1.0 = front) + Int m_airborneTargetingHeight; ///< The height transition at witch I should mark myself as a AA target. + + Real m_closeEnoughDist; ///< How close we have to approach the end of a path before stopping + Bool m_isCloseEnoughDist3D; ///< And is that calculation 3D, for very rare cases that need to move straight down. + Real m_ultraAccurateSlideIntoPlaceFactor; ///< how much we can fudge turning when ultra-accurate + + Bool m_locomotorWorksWhenDead; ///< should locomotor continue working even when object is "dead"? + Bool m_allowMotiveForceWhileAirborne; ///< can we apply motive when airborne? + Bool m_apply2DFrictionWhenAirborne; // apply "2d friction" even when airborne... useful for realistic-looking movement + Bool m_downhillOnly; // pinewood derby, moves only by gravity pulling downhill + Bool m_stickToGround; // if true, can't leave ground + Bool m_canMoveBackward; // if true, can move backwards. + Bool m_hasSuspension; ///< If true, calculate 4 wheel independent suspension values. + Real m_maximumWheelExtension; ///< Maximum distance wheels can move down. (negative value) + Real m_maximumWheelCompression; ///< Maximum distance wheels can move up. (positive value) + Real m_wheelTurnAngle; ///< How far the front wheels can turn. + + // Fields for wander locomotor + Real m_wanderWidthFactor; + Real m_wanderLengthFactor; + Real m_wanderAboutPointRadius; + + + Real m_rudderCorrectionDegree; + Real m_rudderCorrectionRate; + Real m_elevatorCorrectionDegree; + Real m_elevatorCorrectionRate; +}; + +typedef OVERRIDE LocomotorTemplateOverride; + +// --------------------------------------------------------- +class Locomotor : public MemoryPoolObject, public Snapshot +{ + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(Locomotor, "Locomotor" ) + + friend class LocomotorStore; + +public: + + void setPhysicsOptions(Object* obj); + + void locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalPos, + Real onPathDistToGoal, Real desiredSpeed, Bool *blocked); + void locoUpdate_moveTowardsAngle(Object* obj, Real angle); + /** + Kill any current (2D) velocity (but stay at current position, or as close as possible) + + return true if we can maintain the position without being called every frame (eg, we are + resting on the ground), false if not (eg, we are hovering or circling) + */ + Bool locoUpdate_maintainCurrentPosition(Object* obj); + + Real getMaxSpeedForCondition(BodyDamageType condition) const; ///< get max speed given condition + Real getMaxTurnRate(BodyDamageType condition) const; ///< get max turning rate given condition + Real getMaxAcceleration(BodyDamageType condition) const; ///< get acceleration given condition + Real getMaxLift(BodyDamageType condition) const; ///< get acceleration given condition + Real getBraking() const; ///< get braking given condition + + inline Real getPreferredHeight() const { return m_preferredHeight;} ///< Just return preferredheight, no damage consideration + inline void restorePreferredHeightFromTemplate() { m_preferredHeight = m_template->m_preferredHeight; }; + inline Real getPreferredHeightDamping() const { return m_preferredHeightDamping;} + inline LocomotorAppearance getAppearance() const { return m_template->m_appearance; } + inline LocomotorPriority getMovePriority() const { return m_template->m_movePriority; } + inline LocomotorSurfaceTypeMask getLegalSurfaces() const { return m_template->m_surfaces; } + + inline AsciiString getTemplateName() const { return m_template->m_name;} + inline Real getMinSpeed() const { return m_template->m_minSpeed;} + inline Real getAccelPitchLimit() const { return m_template->m_accelPitchLimit;} ///< Maximum amount we will pitch up or down under acceleration (including recoil.) + inline Real getDecelPitchLimit() const { return m_template->m_decelPitchLimit;} ///< Maximum amount we will pitch down under deceleration (including recoil.) + inline Real getBounceKick() const { return m_template->m_bounceKick;} ///< How much simulating rough terrain "bounces" a wheel up. + inline Real getPitchStiffness() const { return m_template->m_pitchStiffness;} ///< How stiff the springs are forward & back. + inline Real getRollStiffness() const { return m_template->m_rollStiffness;} ///< How stiff the springs are side to side. + inline Real getPitchDamping() const { return m_template->m_pitchDamping;} ///< How good the shock absorbers are. + inline Real getRollDamping() const { return m_template->m_rollDamping;} ///< How good the shock absorbers are. + inline Real getPitchByZVelCoef() const { return m_template->m_pitchByZVelCoef;} ///< How much we pitch in response to speed. + inline Real getThrustRoll() const { return m_template->m_thrustRoll; } ///< Thrust roll + inline Real getWobbleRate() const { return m_template->m_wobbleRate; } ///< how fast thrust things "wobble" + inline Real getMaxWobble() const { return m_template->m_maxWobble; } ///< how much thrust things "wobble" + inline Real getMinWobble() const { return m_template->m_minWobble; } ///< how much thrust things "wobble" + + inline Real getForwardVelCoef() const { return m_template->m_forwardVelCoef;} ///< How much we pitch in response to speed. + inline Real getLateralVelCoef() const { return m_template->m_lateralVelCoef;} ///< How much we roll in response to speed. + inline Real getForwardAccelCoef() const { return m_template->m_forwardAccelCoef;} ///< How much we pitch in response to acceleration. + inline Real getLateralAccelCoef() const { return m_template->m_lateralAccelCoef;} ///< How much we roll in response to acceleration. + inline Real getUniformAxialDamping() const { return m_template->m_uniformAxialDamping;} ///< How much we roll in response to acceleration. + inline Real getTurnPivotOffset() const { return m_template->m_turnPivotOffset;} + inline Bool getApply2DFrictionWhenAirborne() const { return m_template->m_apply2DFrictionWhenAirborne; } + inline Bool getIsDownhillOnly() const { return m_template->m_downhillOnly; } + inline Bool getAllowMotiveForceWhileAirborne() const { return m_template->m_allowMotiveForceWhileAirborne; } + inline Int getAirborneTargetingHeight() const { return m_template->m_airborneTargetingHeight; } + inline Bool getLocomotorWorksWhenDead() const { return m_template->m_locomotorWorksWhenDead; } + inline Bool getStickToGround() const { return m_template->m_stickToGround; } + inline Real getCloseEnoughDist() const { return m_closeEnoughDist; } + inline Bool isCloseEnoughDist3D() const { return getFlag(IS_CLOSE_ENOUGH_DIST_3D); } + inline Bool hasSuspension() const {return m_template->m_hasSuspension;} + inline Bool canMoveBackwards() const {return m_template->m_canMoveBackward;} + inline Real getMaxWheelExtension() const {return m_template->m_maximumWheelExtension;} + inline Real getMaxWheelCompression() const {return m_template->m_maximumWheelCompression;} + inline Real getWheelTurnAngle() const {return m_template->m_wheelTurnAngle;} + + + inline Real getRudderCorrectionDegree() const { return m_template->m_rudderCorrectionDegree;} ///< How much we roll in response to acceleration. + inline Real getRudderCorrectionRate() const { return m_template->m_rudderCorrectionRate;} ///< How much we roll in response to acceleration. + inline Real getElevatorCorrectionDegree() const { return m_template->m_elevatorCorrectionDegree;} ///< How much we roll in response to acceleration. + inline Real getElevatorCorrectionRate() const { return m_template->m_elevatorCorrectionRate;} ///< How much we roll in response to acceleration. + + + inline Real getWanderWidthFactor() const {return m_template->m_wanderWidthFactor;} + inline Real getWanderAboutPointRadius() const {return m_template->m_wanderAboutPointRadius;} + + Real calcMinTurnRadius(BodyDamageType condition, Real* timeToTravelThatDist) const; + + /// this is handy for doing things like forcing helicopters to crash realistically: cut their lift. + inline void setMaxLift(Real lift) { m_maxLift = lift; } + inline void setMaxSpeed(Real speed) + { + DEBUG_ASSERTCRASH(!(speed <= 0.0f && m_template->m_appearance == LOCO_THRUST), ("THRUST locos may not have zero speeds!\n")); + m_maxSpeed = speed; + } + inline void setMaxAcceleration(Real accel) { m_maxAccel = accel; } + inline void setMaxBraking(Real braking) { m_maxBraking = braking; } + inline void setMaxTurnRate(Real turn) { m_maxTurnRate = turn; } + inline void setAllowInvalidPosition(Bool allow) { setFlag(ALLOW_INVALID_POSITION, allow); } + inline void setCloseEnoughDist( Real dist ) { m_closeEnoughDist = dist; } + inline void setCloseEnoughDist3D( Bool setting ) { setFlag(IS_CLOSE_ENOUGH_DIST_3D, setting); } + inline Bool isInvalidPositionAllowed() const { return getFlag( ALLOW_INVALID_POSITION ); } + + inline void setPreferredHeight( Real height ) { m_preferredHeight = height; } + +#ifdef CIRCLE_FOR_LANDING + /** + if we are climbing/diving more than this, circle as needed rather + than just diving or climbing directly. (only useful for Winged things) + */ + inline void setAltitudeChangeThresholdForCircling(Real a) { m_circleThresh = a; } +#endif + + /** + when off (the default), things get to adjust their z-pos as their + loco says (in particular, airborne things tend to try to fly at a preferred height). + + when on, they do their best to reach the specified zpos, even if it's not at their preferred height. + this is used mainly for force missiles to swoop in on their target, and to force airplane takeoff/landing + to go smoothly. + */ + inline void setUsePreciseZPos(Bool u) { setFlag(PRECISE_Z_POS, u); } + + /** + when off (the default), units slow down as they approach their target. + + when on, units go full speed till the end, and may overshoot their target. + this is useful mainly in some weird, temporary situations where we know we are + going to follow this move with another one... or for carbombs. + */ + inline void setNoSlowDownAsApproachingDest(Bool u) { setFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST, u); } + + /** + when off (the default), units do their normal stuff. + + when on, we cheat and make very precise motion, regardless of loco settings. + this is accomplished by cranking up the unit's turning rate, friction, lift (for airborne things), + and possibly other things. This is useful mainly when doing maneuvers where precision + is VITAL, such as airplane takeoff/landing. + + For ground units, it also allows units to have a destination off of a pathfing grid. + + */ + inline void setUltraAccurate(Bool u) { setFlag(ULTRA_ACCURATE, u); } + inline Bool isUltraAccurate() const { return getFlag(ULTRA_ACCURATE); } + + inline Bool isMovingBackwards(void) const {return getFlag(MOVING_BACKWARDS);} + + void startMove(void); ///< Indicates that a move is starting, primarily to reset the donut timer. jba. + + static Real getSurfaceHtAtPt(Real x, Real y); + + inline void applySpeedMultiplier(Real scalar) { m_speedMultiplier *= scalar; } + // inline void setSpeedMultiplier(Real value) { m_speedMultiplier = value; } + inline Real getSpeedMultiplier(void) const { return m_speedMultiplier; } + +protected: + void moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionLegsWander(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionHover(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + void moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed); + + void maintainCurrentPositionThrust(Object* obj, PhysicsBehavior *physics); + void maintainCurrentPositionOther(Object* obj, PhysicsBehavior *physics); + void maintainCurrentPositionLegs(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } + void maintainCurrentPositionWheels(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } + void maintainCurrentPositionTreads(Object* obj, PhysicsBehavior *physics) { maintainCurrentPositionOther(obj, physics); } + void maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physics); + void maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physics); + + PhysicsTurningType rotateTowardsPosition(Object* obj, const Coord3D& goalPos, Real *relAngle=NULL); + + /* + return true if we can maintain the position without being called every frame (eg, we are + resting on the ground), false if not (eg, we are hovering or circling) + */ + Bool handleBehaviorZ(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos); + PhysicsTurningType rotateObjAroundLocoPivot(Object* obj, const Coord3D& goalPos, Real maxTurnRate, Real *relAngle = NULL); + + Real calcLiftToUseAtPt(Object* obj, PhysicsBehavior *physics, Real curZ, Real surfaceAtPt, Real preferredHeight); + + Bool fixInvalidPosition(Object* obj, PhysicsBehavior *physics); + +protected: + // snapshot methods + virtual void crc( Xfer *xfer ); + virtual void xfer( Xfer *xfer ); + virtual void loadPostProcess( void ); + +protected: + + Locomotor(const LocomotorTemplate* tmpl); + + // Note, "Law of the Big Three" applies here + //Locomotor(); -- nope, we don't have a default ctor. (srj) + Locomotor(const Locomotor& that); + Locomotor& operator=(const Locomotor& that); + //~Locomotor(); + +private: + + // + // Note: these values are saved in save files, so you MUST NOT REMOVE OR CHANGE + // existing values! + // + enum LocoFlag + { + IS_BRAKING = 0, + ALLOW_INVALID_POSITION, + MAINTAIN_POS_IS_VALID, + PRECISE_Z_POS, + NO_SLOW_DOWN_AS_APPROACHING_DEST, + OVER_WATER, // To allow things to move slower/faster over water and do special effects + ULTRA_ACCURATE, + MOVING_BACKWARDS, // If we are moving backwards. + DOING_THREE_POINT_TURN, // If we are doing a 3 pt turn. + CLIMBING, // If we are in the process of climbing. + IS_CLOSE_ENOUGH_DIST_3D, + OFFSET_INCREASING + }; + + inline Bool getFlag(LocoFlag f) const { return (m_flags & (1 << f)) != 0; } + inline void setFlag(LocoFlag f, Bool b) { if (b) m_flags |= (1< > LocomotorTemplateMap; + + LocomotorTemplateMap m_locomotorTemplates; + +}; + +// EXTERNALS ////////////////////////////////////////////////////////////////////////////////////// +extern LocomotorStore *TheLocomotorStore; + +#endif // __Locomotor_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h index fb8e53b3809..37d6a164bbe 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h @@ -1,238 +1,238 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: ParkingPlaceBehavior.h ///////////////////////////////////////////////////////////////////////// -// Author: Steven Johnson, June 2002 -// Desc: -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __ParkingPlaceBehavior_H_ -#define __ParkingPlaceBehavior_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "GameLogic/Module/BehaviorModule.h" -#include "GameLogic/Module/DieModule.h" -#include "GameLogic/Module/UpdateModule.h" - -//------------------------------------------------------------------------------------------------- -class ParkingPlaceBehaviorModuleData : public UpdateModuleData -{ -public: - //UnsignedInt m_framesForFullHeal; - Real m_healAmount; -// Real m_extraHealAmount4Helicopters; - Int m_numRows; - Int m_numCols; - Real m_approachHeight; - Real m_landingDeckHeightOffset; - Bool m_hasRunways; // if true, each col has a runway in front of it - Bool m_parkInHangars; // if true, park at the hangar production spot, not the "real" parking place - Real m_damageScalar; // Damage reduction for parked aircraft - Real m_damageScalarUpgraded; // Damage reduction for parked aircraft - AsciiString m_damageScalarUpgradeTrigger; // Upgrade template for damageScalar upgrade - - KindOfMaskType m_kindof; ///< the kind(s) of units that can land here - KindOfMaskType m_kindofnot; ///< the kind(s) of units that must not land here - - ParkingPlaceBehaviorModuleData() - { - m_damageScalarUpgradeTrigger.clear(); - //m_framesForFullHeal = 0; - m_healAmount = 0; -// m_extraHealAmount4Helicopters = 0; - m_numRows = 0; - m_numCols = 0; - m_approachHeight = 0.0f; - m_landingDeckHeightOffset = 0.0f; - m_hasRunways = false; - m_parkInHangars = false; - m_damageScalar = 1.0f; - m_damageScalarUpgraded = 1.0f; - } - - static void buildFieldParse(MultiIniFieldParse& p) - { - UpdateModuleData::buildFieldParse(p); - - static const FieldParse dataFieldParse[] = - { - { "NumRows", INI::parseInt, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_numRows ) }, - { "NumCols", INI::parseInt, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_numCols ) }, - { "ApproachHeight", INI::parseReal, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_approachHeight ) }, - { "LandingDeckHeightOffset", INI::parseReal, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_landingDeckHeightOffset ) }, - { "HasRunways", INI::parseBool, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_hasRunways ) }, - { "ParkInHangars", INI::parseBool, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_parkInHangars ) }, - { "HealAmountPerSecond", INI::parseReal, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_healAmount ) }, -// { "ExtraHealAmount4Helicopters", INI::parseReal, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_extraHealAmount4Helicopters ) }, - { "ParkedUnitsDamageScalar", INI::parseReal, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalar) }, - { "ParkedUnitsDamageScalarUpgraded", INI::parseReal, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalarUpgraded) }, - { "DamageScalarUpgradedTriggeredBy", INI::parseAsciiString, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalarUpgradeTrigger) }, - - { "RequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_kindof) }, - { "ForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_kindofnot) }, - - //{ "TimeForFullHeal", INI::parseDurationUnsignedInt, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_framesForFullHeal ) }, - { 0, 0, 0, 0 } - }; - p.add(dataFieldParse); - } - -private: - -}; - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -class ParkingPlaceBehavior : public UpdateModule, - public DieModuleInterface, - public ParkingPlaceBehaviorInterface, - public ExitInterface -{ - - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( ParkingPlaceBehavior, "ParkingPlaceBehavior" ) - MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( ParkingPlaceBehavior, ParkingPlaceBehaviorModuleData ) - -public: - - ParkingPlaceBehavior( Thing *thing, const ModuleData* moduleData ); - // virtual destructor prototype provided by memory pool declaration - - static Int getInterfaceMask() { return UpdateModule::getInterfaceMask() | (MODULEINTERFACE_DIE); } - - // BehaviorModule - virtual DieModuleInterface *getDie( void ) { return this; } - virtual ParkingPlaceBehaviorInterface* getParkingPlaceBehaviorInterface() { return this; } - virtual ExitInterface* getUpdateExitInterface() { return this; } - - // ExitInterface - virtual Bool isExitBusy() const {return FALSE;} ///< Contain style exiters are getting the ability to space out exits, so ask this before reserveDoor as a kind of no-commitment check. - virtual ExitDoorType reserveDoorForExit( const ThingTemplate* objType, Object *specificObject ); - virtual void exitObjectViaDoor( Object *newObj, ExitDoorType exitDoor ); - virtual void unreserveDoorForExit( ExitDoorType exitDoor ); - virtual void exitObjectByBudding( Object *newObj, Object *budHost ) { return; } - - virtual Bool getExitPosition( Coord3D& rallyPoint ) const; - virtual Bool getNaturalRallyPoint( Coord3D& rallyPoint, Bool offset = TRUE ) const; - virtual void setRallyPoint( const Coord3D *pos ); ///< define a "rally point" for units to move towards - virtual const Coord3D *getRallyPoint( void ) const; ///< define a "rally point" for units to move towards - - // UpdateModule - virtual UpdateSleepTime update(); - - // DieModule - virtual void onDie( const DamageInfo *damageInfo ); - - // ParkingPlaceBehaviorInterface - virtual Bool shouldReserveDoorWhenQueued(const ThingTemplate* thing) const; - virtual Bool hasAvailableSpaceFor(const ThingTemplate* thing) const; - virtual Bool hasReservedSpace(ObjectID id) const; - virtual Int getSpaceIndex( ObjectID id ) const; - virtual Bool reserveSpace(ObjectID id, Real parkingOffset, PPInfo* info); - virtual void releaseSpace(ObjectID id); - virtual Bool reserveRunway(ObjectID id, Bool forLanding); - virtual void releaseRunway(ObjectID id); - virtual void calcPPInfo( ObjectID id, PPInfo *info ); - virtual Int getRunwayCount() const { return m_runways.size(); } - virtual ObjectID getRunwayReservation( Int r, RunwayReservationType type ); - virtual void transferRunwayReservationToNextInLineForTakeoff(ObjectID id); - virtual Real getApproachHeight() const { return getParkingPlaceBehaviorModuleData()->m_approachHeight; } - virtual Real getLandingDeckHeightOffset() const { return getParkingPlaceBehaviorModuleData()->m_landingDeckHeightOffset; } - virtual void setHealee(Object* healee, Bool add); - virtual void killAllParkedUnits(); - virtual void defectAllParkedUnits(Team* newTeam, UnsignedInt detectionTime); - virtual Bool calcBestParkingAssignment( ObjectID id, Coord3D *pos, Int *oldIndex = NULL, Int *newIndex = NULL ) { return FALSE; } - virtual const std::vector* getTaxiLocations( ObjectID id ) const { return NULL; } - virtual const std::vector* getCreationLocations( ObjectID id ) const { return NULL; } - -private: - - struct ParkingPlaceInfo - { - Coord3D m_hangarStart; - Real m_hangarStartOrient; - Coord3D m_location; - Coord3D m_prep; - Real m_orientation; - Int m_runway; - ExitDoorType m_door; - ObjectID m_objectInSpace; - Bool m_reservedForExit; - - ParkingPlaceInfo() - { - m_hangarStart.zero(); - m_hangarStartOrient = 0; - m_location.zero(); - m_prep.zero(); - m_orientation = 0; - m_runway = 0; - m_door = DOOR_NONE_AVAILABLE; - m_objectInSpace = INVALID_ID; - m_reservedForExit = false; - } - }; - - struct RunwayInfo - { - Coord3D m_start; - Coord3D m_end; - ObjectID m_inUseBy; - ObjectID m_nextInLineForTakeoff; - Bool m_wasInLine; - }; - - struct HealingInfo - { - ObjectID m_gettingHealedID; - UnsignedInt m_healStartFrame; - }; - - std::vector m_spaces; - std::vector m_runways; - std::list m_healing; // note, this list can vary in size, and be larger than the parking space count - UnsignedInt m_nextHealFrame; - Bool m_gotInfo; - - void buildInfo(); - void purgeDead(); - void resetWakeFrame(); - - ParkingPlaceInfo* findPPI(ObjectID id); - ParkingPlaceInfo* findEmptyPPI(); - - void applyDamageScalar(Object* obj, Real scalarNew, Real scalarOld = 1.0f); - void removeDamageScalar(Object* obj, Real scalar); - Real getDamageScalar(); - void updateDamageScalars(); - - Coord3D m_heliRallyPoint; - Bool m_heliRallyPointExists; ///< Only move to the rally point if this is true - - Bool m_damageScalarUpgradeApplied; -}; - -#endif // __ParkingPlaceBehavior_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: ParkingPlaceBehavior.h ///////////////////////////////////////////////////////////////////////// +// Author: Steven Johnson, June 2002 +// Desc: +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __ParkingPlaceBehavior_H_ +#define __ParkingPlaceBehavior_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "GameLogic/Module/BehaviorModule.h" +#include "GameLogic/Module/DieModule.h" +#include "GameLogic/Module/UpdateModule.h" + +//------------------------------------------------------------------------------------------------- +class ParkingPlaceBehaviorModuleData : public UpdateModuleData +{ +public: + //UnsignedInt m_framesForFullHeal; + Real m_healAmount; +// Real m_extraHealAmount4Helicopters; + Int m_numRows; + Int m_numCols; + Real m_approachHeight; + Real m_landingDeckHeightOffset; + Bool m_hasRunways; // if true, each col has a runway in front of it + Bool m_parkInHangars; // if true, park at the hangar production spot, not the "real" parking place + Real m_damageScalar; // Damage reduction for parked aircraft + Real m_damageScalarUpgraded; // Damage reduction for parked aircraft + AsciiString m_damageScalarUpgradeTrigger; // Upgrade template for damageScalar upgrade + + KindOfMaskType m_kindof; ///< the kind(s) of units that can land here + KindOfMaskType m_kindofnot; ///< the kind(s) of units that must not land here + + ParkingPlaceBehaviorModuleData() + { + m_damageScalarUpgradeTrigger.clear(); + //m_framesForFullHeal = 0; + m_healAmount = 0; +// m_extraHealAmount4Helicopters = 0; + m_numRows = 0; + m_numCols = 0; + m_approachHeight = 0.0f; + m_landingDeckHeightOffset = 0.0f; + m_hasRunways = false; + m_parkInHangars = false; + m_damageScalar = 1.0f; + m_damageScalarUpgraded = 1.0f; + } + + static void buildFieldParse(MultiIniFieldParse& p) + { + UpdateModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + { "NumRows", INI::parseInt, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_numRows ) }, + { "NumCols", INI::parseInt, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_numCols ) }, + { "ApproachHeight", INI::parseReal, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_approachHeight ) }, + { "LandingDeckHeightOffset", INI::parseReal, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_landingDeckHeightOffset ) }, + { "HasRunways", INI::parseBool, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_hasRunways ) }, + { "ParkInHangars", INI::parseBool, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_parkInHangars ) }, + { "HealAmountPerSecond", INI::parseReal, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_healAmount ) }, +// { "ExtraHealAmount4Helicopters", INI::parseReal, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_extraHealAmount4Helicopters ) }, + { "ParkedUnitsDamageScalar", INI::parseReal, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalar) }, + { "ParkedUnitsDamageScalarUpgraded", INI::parseReal, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalarUpgraded) }, + { "DamageScalarUpgradedTriggeredBy", INI::parseAsciiString, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_damageScalarUpgradeTrigger) }, + + { "RequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_kindof) }, + { "ForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof(ParkingPlaceBehaviorModuleData, m_kindofnot) }, + + //{ "TimeForFullHeal", INI::parseDurationUnsignedInt, NULL, offsetof( ParkingPlaceBehaviorModuleData, m_framesForFullHeal ) }, + { 0, 0, 0, 0 } + }; + p.add(dataFieldParse); + } + +private: + +}; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class ParkingPlaceBehavior : public UpdateModule, + public DieModuleInterface, + public ParkingPlaceBehaviorInterface, + public ExitInterface +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( ParkingPlaceBehavior, "ParkingPlaceBehavior" ) + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( ParkingPlaceBehavior, ParkingPlaceBehaviorModuleData ) + +public: + + ParkingPlaceBehavior( Thing *thing, const ModuleData* moduleData ); + // virtual destructor prototype provided by memory pool declaration + + static Int getInterfaceMask() { return UpdateModule::getInterfaceMask() | (MODULEINTERFACE_DIE); } + + // BehaviorModule + virtual DieModuleInterface *getDie( void ) { return this; } + virtual ParkingPlaceBehaviorInterface* getParkingPlaceBehaviorInterface() { return this; } + virtual ExitInterface* getUpdateExitInterface() { return this; } + + // ExitInterface + virtual Bool isExitBusy() const {return FALSE;} ///< Contain style exiters are getting the ability to space out exits, so ask this before reserveDoor as a kind of no-commitment check. + virtual ExitDoorType reserveDoorForExit( const ThingTemplate* objType, Object *specificObject ); + virtual void exitObjectViaDoor( Object *newObj, ExitDoorType exitDoor ); + virtual void unreserveDoorForExit( ExitDoorType exitDoor ); + virtual void exitObjectByBudding( Object *newObj, Object *budHost ) { return; } + + virtual Bool getExitPosition( Coord3D& rallyPoint ) const; + virtual Bool getNaturalRallyPoint( Coord3D& rallyPoint, Bool offset = TRUE ) const; + virtual void setRallyPoint( const Coord3D *pos ); ///< define a "rally point" for units to move towards + virtual const Coord3D *getRallyPoint( void ) const; ///< define a "rally point" for units to move towards + + // UpdateModule + virtual UpdateSleepTime update(); + + // DieModule + virtual void onDie( const DamageInfo *damageInfo ); + + // ParkingPlaceBehaviorInterface + virtual Bool shouldReserveDoorWhenQueued(const ThingTemplate* thing) const; + virtual Bool hasAvailableSpaceFor(const ThingTemplate* thing) const; + virtual Bool hasReservedSpace(ObjectID id) const; + virtual Int getSpaceIndex( ObjectID id ) const; + virtual Bool reserveSpace(ObjectID id, Real parkingOffset, PPInfo* info); + virtual void releaseSpace(ObjectID id); + virtual Bool reserveRunway(ObjectID id, Bool forLanding); + virtual void releaseRunway(ObjectID id); + virtual void calcPPInfo( ObjectID id, PPInfo *info ); + virtual Int getRunwayCount() const { return m_runways.size(); } + virtual ObjectID getRunwayReservation( Int r, RunwayReservationType type ); + virtual void transferRunwayReservationToNextInLineForTakeoff(ObjectID id); + virtual Real getApproachHeight() const { return getParkingPlaceBehaviorModuleData()->m_approachHeight; } + virtual Real getLandingDeckHeightOffset() const { return getParkingPlaceBehaviorModuleData()->m_landingDeckHeightOffset; } + virtual void setHealee(Object* healee, Bool add); + virtual void killAllParkedUnits(); + virtual void defectAllParkedUnits(Team* newTeam, UnsignedInt detectionTime); + virtual Bool calcBestParkingAssignment( ObjectID id, Coord3D *pos, Int *oldIndex = NULL, Int *newIndex = NULL ) { return FALSE; } + virtual const std::vector* getTaxiLocations( ObjectID id ) const { return NULL; } + virtual const std::vector* getCreationLocations( ObjectID id ) const { return NULL; } + +private: + + struct ParkingPlaceInfo + { + Coord3D m_hangarStart; + Real m_hangarStartOrient; + Coord3D m_location; + Coord3D m_prep; + Real m_orientation; + Int m_runway; + ExitDoorType m_door; + ObjectID m_objectInSpace; + Bool m_reservedForExit; + + ParkingPlaceInfo() + { + m_hangarStart.zero(); + m_hangarStartOrient = 0; + m_location.zero(); + m_prep.zero(); + m_orientation = 0; + m_runway = 0; + m_door = DOOR_NONE_AVAILABLE; + m_objectInSpace = INVALID_ID; + m_reservedForExit = false; + } + }; + + struct RunwayInfo + { + Coord3D m_start; + Coord3D m_end; + ObjectID m_inUseBy; + ObjectID m_nextInLineForTakeoff; + Bool m_wasInLine; + }; + + struct HealingInfo + { + ObjectID m_gettingHealedID; + UnsignedInt m_healStartFrame; + }; + + std::vector m_spaces; + std::vector m_runways; + std::list m_healing; // note, this list can vary in size, and be larger than the parking space count + UnsignedInt m_nextHealFrame; + Bool m_gotInfo; + + void buildInfo(); + void purgeDead(); + void resetWakeFrame(); + + ParkingPlaceInfo* findPPI(ObjectID id); + ParkingPlaceInfo* findEmptyPPI(); + + void applyDamageScalar(Object* obj, Real scalarNew, Real scalarOld = 1.0f); + void removeDamageScalar(Object* obj, Real scalar); + Real getDamageScalar(); + void updateDamageScalars(); + + Coord3D m_heliRallyPoint; + Bool m_heliRallyPointExists; ///< Only move to the rally point if this is true + + Bool m_damageScalarUpgradeApplied; +}; + +#endif // __ParkingPlaceBehavior_H_ + diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp index 3fbabf06db2..739c7ff5fbf 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp @@ -1,816 +1,816 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: MemoryInit.cpp -//----------------------------------------------------------------------------- -// -// Westwood Studios Pacific. -// -// Confidential Information -// Copyright (C) 2001 - All Rights Reserved -// -//----------------------------------------------------------------------------- -// -// Project: RTS3 -// -// File name: MemoryInit.cpp -// -// Created: Steven Johnson, August 2001 -// -// Desc: Memory manager -// -// ---------------------------------------------------------------------------- -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -// SYSTEM INCLUDES - -// USER INCLUDES -#include "Lib/BaseType.h" -#include "Common/GameMemory.h" - -#ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -//----------------------------------------------------------------------------- -void userMemoryManagerGetDmaParms(Int *numSubPools, const PoolInitRec **pParms) -{ - static const PoolInitRec defaultDMA[7] = - { - // name, allocsize, initialcount, overflowcount - { "dmaPool_16", 16, 130000, 10000 }, - { "dmaPool_32", 32, 250000, 10000 }, - { "dmaPool_64", 64, 100000, 10000 }, - { "dmaPool_128", 128, 80000, 10000 }, - { "dmaPool_256", 256, 20000, 5000 }, - { "dmaPool_512", 512, 16000, 5000 }, - { "dmaPool_1024", 1024, 6000, 1024} - }; - - *numSubPools = 7; - *pParms = defaultDMA; -} - -//----------------------------------------------------------------------------- -struct PoolSizeRec -{ - const char* name; - Int initial; - Int overflow; -}; - -//----------------------------------------------------------------------------- -// And please be careful of duplicates. They are not rejected. -// not const -- we might override from INI -static PoolSizeRec sizes[] = -{ - { "PartitionContactListNode", 2048, 512 }, - { "BattleshipUpdate", 32, 32 }, - { "FlyToDestAndDestroyUpdate", 32, 32 }, - { "MusicTrack", 32, 32 }, - { "PositionalSoundPool", 32, 32 }, - { "GameMessage", 2048, 32 }, - { "NameKeyBucketPool", 9000, 1024 }, - { "ObjectSellInfo", 16, 16 }, - { "ProductionPrerequisitePool", 1024, 32 }, - { "RadarObject", 512, 32 }, - { "ResourceGatheringManager", 16, 16 }, - { "SightingInfo", 8192, 2048 },// Looks big, but all 3000 objects used to have 4 just built in. - { "SpecialPowerTemplate", 84, 32 }, - { "StateMachinePool", 32, 32 }, - { "TeamPool", 128, 32 }, // if you increase this, increase player/team relation map pools - { "PlayerRelationMapPool", 128, 32 }, - { "TeamRelationMapPool", 128, 32 }, - { "TeamPrototypePool", 256, 32 }, - { "TerrainType", 256, 32 }, - { "ThingTemplatePool", 2120, 32 }, - { "TunnelTracker", 16, 16 }, - { "Upgrade", 16, 16 }, - { "UpgradeTemplate", 128, 16 }, - { "Anim2D", 32, 32 }, - { "CommandButton", 1024, 256 }, - { "CommandSet", 820, 16 }, - { "DisplayString", 32, 32 }, - { "WebBrowserURL", 16, 16 }, - { "Drawable", 4096, 32 }, - { "Image", 2048, 32 }, - { "ParticlePool", 1400, 1024 }, - { "ParticleSystemTemplatePool", 1100, 32 }, - { "ParticleSystemPool", 1024, 32 }, - { "TerrainRoadType", 100, 32, }, - { "WindowLayoutPool", 32, 32 }, - { "AnimatedParticleSysBoneClientUpdate", 16, 16 }, - { "SwayClientUpdate", 32, 32 }, - { "BeaconClientUpdate", 64, 32 }, - { "AIGroupPool", 64, 32 }, - { "AIDockMachinePool", 256, 32 }, - { "AIGuardMachinePool", 32, 32 }, - { "AIGuardRetaliateMachinePool", 32, 32 }, - { "AITNGuardMachinePool", 32, 32 }, - { "PathNodePool", 8192, 1024 }, - { "PathPool", 256, 16 }, - { "WorkOrder", 32, 32 }, - { "TeamInQueue", 32, 32 }, - { "AIPlayer", 12, 4 }, - { "AISkirmishPlayer", 8, 8 }, - { "AIStateMachine", 600, 32 }, - { "JetAIStateMachine", 64, 32 }, - { "HeliAIStateMachine", 64, 32 }, - { "VtolAIStateMachine", 64, 32 }, - { "AIAttackMoveStateMachine", 2048, 32 }, - { "AIAttackThenIdleStateMachine", 512, 32 }, - { "AttackStateMachine", 512, 32 }, - { "CrateTemplate", 32, 32 }, - { "ExperienceTrackerPool", 2048, 512 }, - { "FiringTrackerPool", 4096, 256 }, - { "ObjectRepulsorHelper", 1024, 256 }, - { "ObjectSMCHelperPool", 2048, 256 }, - { "ObjectWeaponStatusHelperPool", 4096, 256 }, - { "ObjectDefectionHelperPool", 2048, 256 }, - { "StatusDamageHelper", 1500, 256 }, - { "SubdualDamageHelper", 1500, 256 }, - { "TempWeaponBonusHelper", 4096, 256 }, - { "Locomotor", 2048, 32 }, - { "LocomotorTemplate", 192, 32 }, - { "ObjectPool", 1500, 256 }, - { "SimpleObjectIteratorPool", 32, 32 }, - { "SimpleObjectIteratorClumpPool", 4096, 32 }, - { "PartitionDataPool", 2048, 512 }, - { "BuildEntry", 32, 32 }, - { "Weapon", 4096, 32 }, - { "WeaponTemplate", 360, 32 }, - { "AIUpdateInterface", 600, 32 }, - { "ActiveBody", 1024, 32 }, - { "ActiveShroudUpgrade", 32, 32 }, - { "AssistedTargetingUpdate", 32, 32 }, - { "AudioEventInfo", 4096, 64 }, - { "AudioRequest", 256, 8 }, - { "AutoHealBehavior", 1024, 256 }, - { "WeaponBonusUpdate", 16, 16 }, - { "GrantStealthBehavior", 4096, 32 }, - { "NeutronBlastBehavior", 4096, 32 }, - { "CountermeasuresBehavior", 256, 32 }, - { "BaseRegenerateUpdate", 128, 32 }, - { "BoneFXDamage", 64, 32 }, - { "BoneFXUpdate", 64, 32 }, - { "BridgeBehavior", 4, 4 }, - { "BridgeTowerBehavior", 32, 32 }, - { "BridgeScaffoldBehavior", 32, 32 }, - { "CaveContain", 16, 16 }, - { "HealContain", 32, 32 }, - { "CreateCrateDie", 256, 128 }, - { "CreateObjectDie", 1024, 32 }, - { "EjectPilotDie", 1024, 32 }, - { "CrushDie", 1024, 32 }, - { "DamDie", 8, 8 }, - { "DeliverPayloadStateMachine", 32, 32 }, - { "DeliverPayloadAIUpdate", 32, 32 }, - { "DeletionUpdate", 128, 32 }, - { "SmartBombTargetHomingUpdate", 8, 8 }, - { "DynamicAudioEventInfo", 16, 256 }, // Note: some levels have none, some have lots. Since all are allocated at level load time, we can set this low for the levels with none. - { "HackInternetStateMachine", 32, 32 }, - { "HackInternetAIUpdate", 32, 32 }, - { "MissileAIUpdate", 512, 32 }, - { "DumbProjectileBehavior", 64, 32 }, - { "FreeFallProjectileBehavior", 32, 32 }, - { "DestroyDie", 1024, 32 }, - { "UpgradeDie", 128, 32 }, - { "KeepObjectDie", 128, 32 }, - { "DozerAIUpdate", 32, 32 }, - { "DynamicGeometryInfoUpdate", 16, 16 }, - { "DynamicShroudClearingRangeUpdate", 128, 16 }, - { "FXListDie", 1024, 32 }, - { "FireSpreadUpdate", 2048, 128 }, - { "FirestormDynamicGeometryInfoUpdate", 16, 16 }, - { "FireWeaponCollide", 2048, 32 }, - { "FireWeaponUpdate", 32, 32 }, - { "FlammableUpdate", 512, 256 }, - { "FloatUpdate", 512, 128 }, - { "TensileFormationUpdate", 256, 32 }, - { "GarrisonContain", 256, 32 }, - { "HealCrateCollide", 32, 32 }, - { "HeightDieUpdate", 32, 32 }, - { "ScatterShotUpdate", 128, 64 }, - { "FireWeaponWhenDamagedBehavior", 32, 32 }, - { "FireWeaponWhenDeadBehavior", 128, 64 }, - { "DelayedUpgradeBehavior", 128, 64 }, - { "GenerateMinefieldBehavior", 32, 32 }, - { "HelicopterSlowDeathBehavior", 64, 32 }, - { "ParkingPlaceBehavior", 32, 32 }, - { "FlightDeckBehavior", 8, 8 }, -#ifdef ALLOW_SURRENDER - { "POWTruckAIUpdate", 32, 32, }, - { "POWTruckBehavior", 32, 32, }, - { "PrisonBehavior", 32, 32 }, - { "PrisonVisual", 32, 32 }, - { "PropagandaCenterBehavior", 16, 16 }, -#endif - { "PropagandaTowerBehavior", 16, 16 }, - { "BunkerBusterBehavior", 16, 16 }, - { "ObjectTracker", 128, 32 }, - { "OCLUpdate", 16, 16 }, - { "BodyParticleSystem", 196, 64 }, - { "HighlanderBody", 2048, 128 }, - { "UndeadBody", 32, 32 }, - { "HordeUpdate", 128, 32 }, - { "ImmortalBody", 128, 256 }, - { "InactiveBody", 2048, 32 }, - { "InstantDeathBehavior", 512, 32 }, - { "LaserUpdate", 32, 32 }, - { "PointDefenseLaserUpdate", 32, 32 }, - { "CleanupHazardUpdate", 32, 32 }, - { "AutoFindHealingUpdate", 256, 32 }, - { "CommandButtonHuntUpdate", 512, 8 }, - { "PilotFindVehicleUpdate", 256, 32 }, - { "DemoTrapUpdate", 32, 32 }, - { "ParticleUplinkCannonUpdate", 16, 16 }, - { "SpectreGunshipUpdate", 8, 8 }, - { "SpectreGunshipDeploymentUpdate", 8, 8 }, - { "BaikonurLaunchPower", 4, 4 }, - { "RadiusDecalUpdate", 16, 16 }, - { "RadiusDecalBehavior", 32, 32 }, - { "BattlePlanUpdate", 32, 32 }, - { "LifetimeUpdate", 32, 32 }, - { "LocomotorSetUpgrade", 512, 128 }, - { "LockWeaponCreate", 64, 128 }, - { "AutoDepositUpdate", 256, 32 }, - { "NeutronMissileUpdate", 512, 32 }, - { "MoneyCrateCollide", 48, 16 }, - { "NeutronMissileSlowDeathBehavior", 8, 8 }, - { "OpenContain", 128, 32 }, - { "OverchargeBehavior", 32, 32 }, - { "OverlordContain", 32, 32 }, - { "HelixContain", 32, 32 }, - { "ParachuteContain", 128, 32 }, - { "PhysicsBehavior", 600, 32 }, - { "PoisonedBehavior", 512, 64 }, - { "ProductionEntry", 32, 32 }, - { "ProductionUpdate", 256, 32 }, - { "ProjectileStreamUpdate", 32, 32 }, - { "ProneUpdate", 128, 32 }, - { "QueueProductionExitUpdate", 32, 32 }, - { "RadarUpdate", 16, 16 }, - { "RadarUpgrade", 16, 16 }, - { "AnimationSteeringUpdate", 1024, 32 }, - { "SupplyWarehouseCripplingBehavior", 16, 16 }, - { "CostModifierUpgrade", 32, 32 }, - { "ProductionTimeModifierUpgrade", 32, 32 }, - { "UnitProductionBonusUpgrade", 64, 32 }, - { "CashBountyPower", 32, 32 }, - { "CleanupAreaPower", 32, 32 }, - { "ObjectCreationUpgrade", 196, 32 }, - { "MinefieldBehavior", 256, 32 }, - { "JetSlowDeathBehavior", 64, 32 }, - { "BattleBusSlowDeathBehavior", 64, 32 }, - { "RebuildHoleBehavior", 64, 32 }, - { "RebuildHoleExposeDie", 64, 32 }, - { "RepairDockUpdate", 32, 32 }, -#ifdef ALLOW_SURRENDER - { "PrisonDockUpdate", 32, 32 }, -#endif - { "RailedTransportDockUpdate", 16, 16 }, - { "RailedTransportAIUpdate", 16, 16 }, - { "RailedTransportContain", 16, 16 }, - { "RailroadBehavior", 16, 16 }, - { "SalvageCrateCollide", 32, 32 }, - { "ShroudCrateCollide", 32, 32 }, - { "SlavedUpdate", 64, 32 }, - { "SlowDeathBehavior", 1400, 256 }, - { "SpyVisionUpdate", 16, 16 }, - { "DefaultProductionExitUpdate", 32, 32 }, - { "SpawnPointProductionExitUpdate", 32, 32 }, - { "SpawnBehavior", 32, 32 }, - { "SpecialPowerCompletionDie", 32, 32 }, - { "SpecialPowerCreate", 32, 32 }, - { "PreorderCreate", 32, 32 }, - { "SpecialAbility", 512, 32 }, - { "SpecialAbilityUpdate", 512, 32 }, - { "MissileLauncherBuildingUpdate", 32, 32 }, - { "SquishCollide", 512, 32 }, - { "StructureBody", 512, 64 }, - { "HiveStructureBody", 64, 32 }, //Stinger sites - { "StructureCollapseUpdate", 32, 32 }, - { "StructureToppleUpdate", 32, 32 }, - { "SupplyCenterCreate", 32, 32 }, - { "SupplyCenterDockUpdate", 32, 32 }, - { "SupplyCenterProductionExitUpdate", 32, 32 }, - { "SupplyTruckStateMachine", 256, 32 }, - { "SupplyTruckAIUpdate", 32, 32 }, - { "SupplyWarehouseCreate", 48, 16 }, - { "SupplyWarehouseDockUpdate", 48, 16 }, - { "EnemyNearUpdate", 1024, 32 }, - { "TechBuildingBehavior", 32, 32 }, - { "ToppleUpdate", 256, 128 }, - { "TransitionDamageFX", 384, 128 }, - { "TransportAIUpdate", 64, 32 }, - { "TransportContain", 128, 32 }, - { "RiderChangeContain", 128, 32 }, - { "InternetHackContain", 16, 16 }, - { "TunnelContain", 8, 8 }, - { "TunnelContainDie", 32, 32 }, - { "TunnelCreate", 32, 32 }, - { "TurretAI", 256, 32 }, - { "TurretStateMachine", 128, 32 }, - { "TurretSwapUpgrade", 512, 128 }, - { "UnitCrateCollide", 32, 32 }, - { "UnpauseSpecialPowerUpgrade", 32, 32 }, - { "VeterancyCrateCollide", 32, 32 }, - { "VeterancyGainCreate", 512, 128 }, - { "ConvertToCarBombCrateCollide", 256, 128 }, - { "ConvertToHijackedVehicleCrateCollide", 256, 128 }, - { "SabotageCommandCenterCrateCollide", 256, 128 }, - { "SabotageFakeBuildingCrateCollide", 256, 128 }, - { "SabotageInternetCenterCrateCollide", 256, 128 }, - { "SabotageMilitaryFactoryCrateCollide", 256, 128 }, - { "SabotagePowerPlantCrateCollide", 256, 128 }, - { "SabotageSuperweaponCrateCollide", 256, 128 }, - { "SabotageSupplyCenterCrateCollide", 256, 128 }, - { "SabotageSupplyDropzoneCrateCollide", 256, 128 }, - { "JetAIUpdate", 64, 32 }, - { "ChinookAIUpdate", 32, 32 }, - { "WanderAIUpdate", 32, 32 }, - { "TeleporterAIUpdate", 64, 32 }, - { "WaveGuideUpdate", 16, 16 }, - { "ArmorDamageScalarUpdate", 256, 32 }, - { "WeaponBonusUpgrade", 512, 128 }, - { "WeaponSetUpgrade", 512, 128 }, - { "ArmorUpgrade", 512, 128 }, - { "WorkerAIUpdate", 128, 128 }, - { "WorkerStateMachine", 128, 128 }, - { "ChinookAIStateMachine", 32, 32 }, - { "DeployStyleAIUpdate", 32, 32 }, - { "AssaultTransportAIUpdate", 64, 32 }, - { "StreamingArchiveFile", 8, 8 }, - - { "DozerActionStateMachine", 256, 32 }, - { "DozerPrimaryStateMachine", 256, 32 }, - { "W3DDisplayString", 1400, 128 }, - { "W3DDefaultDraw", 1024, 128 }, - { "W3DDebrisDraw", 128, 128 }, - { "W3DDependencyModelDraw", 64, 64 }, - { "W3DLaserDraw", 32, 32 }, - { "W3DModelDraw", 2048, 512 }, - { "W3DOverlordTankDraw", 64, 64 }, - { "W3DOverlordTruckDraw", 64, 64 }, - { "W3DOverlordAircraftDraw", 64, 64 }, - { "W3DPoliceCarDraw", 32, 32 }, - { "W3DProjectileStreamDraw", 32, 32 }, - { "W3DRopeDraw", 32, 32 }, - { "W3DScienceModelDraw", 32, 32 }, - { "W3DSupplyDraw", 40, 16 }, - { "W3DTankDraw", 256, 32 }, - { "W3DTreeDraw", 16, 16 }, - { "W3DPropDraw", 16, 16 }, - { "W3DTracerDraw", 64, 32 }, - { "W3DTruckDraw", 128, 32 }, - { "W3DTankTruckDraw", 32, 16 }, - { "W3DTreeTextureClass", 4, 4 }, - { "DefaultSpecialPower", 32, 32 }, - { "OCLSpecialPower", 96, 32 }, - { "FireWeaponPower", 32, 32 }, -#ifdef ALLOW_DEMORALIZE - { "DemoralizeSpecialPower", 16, 16, }, -#endif - { "CashHackSpecialPower", 32, 32 }, - { "CommandSetUpgrade", 32, 32 }, - { "PassengersFireUpgrade", 32, 32 }, - { "GrantUpgradeCreate", 256, 32 }, - { "GrantScienceUpgrade", 256, 32 }, - { "ReplaceObjectUpgrade", 32, 32 }, - { "ModelConditionUpgrade", 32, 32 }, - { "UpgradeSpecialPower", 64, 32 }, - { "SpyVisionSpecialPower", 256, 32 }, - { "StealthDetectorUpdate", 256, 32 }, - { "StealthUpdate", 512, 128 }, - { "StealthUpgrade", 256, 32 }, - { "StatusBitsUpgrade", 128, 128 }, - { "SubObjectsUpgrade", 128, 128 }, - { "ExperienceScalarUpgrade", 256, 128 }, - { "MaxHealthUpgrade", 128, 128 }, - { "WeaponBonusUpgrade", 128, 64 }, - { "StickyBombUpdate", 64, 32 }, - { "FireOCLAfterWeaponCooldownUpdate", 64, 32 }, - { "HijackerUpdate", 64, 32 }, - { "ChinaMinesUpgrade", 64, 32 }, - { "PowerPlantUpdate", 48, 16 }, - { "PowerPlantUpgrade", 48, 16 }, - { "DefectorSpecialPower", 16, 16 }, - { "CheckpointUpdate", 16, 16 }, - { "MobNexusContain", 128, 32 }, - { "MobMemberSlavedUpdate", 64, 32 }, - { "EMPUpdate", 64, 32 }, - { "LeafletDropBehavior", 64, 32 }, - { "Overridable", 32, 32 }, - - { "W3DGameWindow", 700, 256 }, - { "SuccessState", 32, 32 }, - { "FailureState", 32, 32 }, - { "ContinueState", 32, 32 }, - { "SleepState", 32, 32 }, - - { "AIDockWaitForClearanceState", 256, 32 }, - { "AIDockProcessDockState", 256, 32 }, - { "AIGuardInnerState", 32, 32 }, - { "AIGuardIdleState", 32, 32 }, - { "AIGuardOuterState", 32, 32 }, - { "AIGuardReturnState", 32, 32 }, - { "AIGuardPickUpCrateState", 32, 32 }, - { "AIGuardAttackAggressorState", 32, 32 }, - { "AIGuardRetaliateInnerState", 32, 32 }, - { "AIGuardRetaliateIdleState", 32, 32 }, - { "AIGuardRetaliateOuterState", 32, 32 }, - { "AIGuardRetaliateReturnState", 32, 32 }, - { "AIGuardRetaliatePickUpCrateState", 32, 32 }, - { "AIGuardRetaliateAttackAggressorState", 32, 32 }, - { "AITNGuardInnerState", 32, 32 }, - { "AITNGuardIdleState", 32, 32 }, - { "AITNGuardOuterState", 32, 32 }, - { "AITNGuardReturnState", 32, 32 }, - { "AITNGuardPickUpCrateState", 32, 32 }, - { "AITNGuardAttackAggressorState", 32, 32 }, - { "AIIdleState", 2400, 32 }, - { "AIRappelState", 600, 32 }, - { "AIBusyState", 600, 32 }, - { "AIWaitState", 600, 32 }, - { "AIAttackState", 4096, 32 }, - { "AIAttackSquadState", 600, 32 }, - { "AIDeadState", 600, 32 }, - { "AIDockState", 600, 32 }, - { "AIExitState", 600, 32 }, - { "AIExitInstantlyState", 600, 32 }, - { "AIGuardState", 600, 32 }, - { "AIGuardRetaliateState", 600, 32 }, - { "AITunnelNetworkGuardState", 600, 32 }, - { "AIHuntState", 600, 32 }, - { "AIAttackAreaState", 600, 32 }, - { "AIFaceState", 1200, 32 }, - { "ApproachState", 600, 32 }, - { "DeliveringState", 600, 32 }, - { "ConsiderNewApproachState", 600, 32 }, - { "RecoverFromOffMapState", 600, 32 }, - { "HeadOffMapState", 600, 32 }, - { "CleanUpState", 600, 32 }, - { "HackInternetState", 600, 32 }, - { "PackingState", 600, 32 }, - { "UnpackingState", 600, 32 }, - { "SupplyTruckWantsToPickUpOrDeliverBoxesState", 600, 32 }, - { "RegroupingState", 600, 32 }, - { "DockingState", 600, 32 }, - { "ChinookEvacuateState", 32, 32 }, - { "ChinookHeadOffMapState", 32, 32 }, - { "ChinookTakeoffOrLandingState", 32, 32 }, - { "ChinookCombatDropState", 32, 32 }, - { "DozerActionPickActionPosState", 256, 32 }, - { "DozerActionMoveToActionPosState", 256, 32 }, - { "DozerActionDoActionState", 256, 32 }, - { "DozerPrimaryIdleState", 256, 32 }, - { "DozerActionState", 256, 32 }, - { "DozerPrimaryGoingHomeState", 256, 32 }, - { "JetAwaitingRunwayState", 64, 32 }, - { "JetOrHeliCirclingDeadAirfieldState", 64, 32 }, - { "HeliTakeoffOrLandingState", 64, 32 }, - { "VtolTakeoffOrLandingState", 64, 32 }, - { "JetOrHeliParkOrientState", 64, 32 }, - { "VtolParkOrientState", 64, 32 }, - { "JetOrHeliReloadAmmoState", 64, 32 }, - { "SupplyTruckBusyState", 600, 32 }, - { "SupplyTruckIdleState", 600, 32 }, - { "ActAsDozerState", 600, 32 }, - { "ActAsSupplyTruckState", 600, 32 }, - { "AIDockApproachState", 256, 32 }, - { "AIDockAdvancePositionState", 256, 32 }, - { "AIDockMoveToEntryState", 256, 32 }, - { "AIDockMoveToDockState", 256, 32 }, - { "AIDockMoveToExitState", 256, 32 }, - { "AIDockMoveToRallyState", 256, 32 }, - { "AIMoveToState", 600, 32 }, - { "AIMoveOutOfTheWayState", 600, 32 }, - { "AIMoveAndTightenState", 600, 32 }, - { "AIMoveAwayFromRepulsorsState", 600, 32 }, - { "AIAttackApproachTargetState", 96, 32 }, - { "AIAttackPursueTargetState", 96, 32 }, - { "AIAttackAimAtTargetState", 96, 32 }, - { "AIAttackFireWeaponState", 256, 32 }, - { "AIPickUpCrateState", 4096, 32 }, - { "AIFollowWaypointPathState", 1200, 32 }, - { "AIFollowWaypointPathExactState", 1200, 32 }, - { "AIWanderInPlaceState", 600, 32 }, - { "AIFollowPathState", 1200, 32 }, - { "AIMoveAndEvacuateState", 1200, 32 }, - { "AIMoveAndDeleteState", 600, 32 }, - { "AIEnterState", 600, 32 }, - { "JetOrHeliReturningToDeadAirfieldState", 64, 32 }, - { "JetOrHeliReturnForLandingState", 64, 32 }, - { "TurretAIIdleState", 600, 32 }, - { "TurretAIIdleScanState", 600, 32 }, - { "TurretAIAimTurretState", 600, 32 }, - { "TurretAIRecenterTurretState", 600, 32 }, - { "TurretAIHoldTurretState", 600, 32 }, - { "JetOrHeliTaxiState", 64, 32 }, - { "JetTakeoffOrLandingState", 64, 32 }, - { "JetPauseBeforeTakeoffState", 64, 32 }, - { "AIAttackMoveToState", 600, 32 }, - { "AIAttackFollowWaypointPathState", 1200, 32 }, - { "AIWanderState", 600, 32 }, - { "AIPanicState", 600, 32 }, - { "ChinookMoveToBldgState", 32, 32 }, - { "ChinookRecordCreationState", 32, 32 }, - { "ScienceInfo", 96, 32 }, - { "RankInfo", 32, 32 }, - - { "FireWeaponNugget", 32, 32 }, - { "AttackNugget", 32, 32 }, - { "DeliverPayloadNugget", 48, 32 }, - { "ApplyRandomForceNugget", 32, 32 }, - { "GenericObjectCreationNugget", 632, 32 }, - { "SoundFXNugget", 320, 32 }, - { "TracerFXNugget", 32, 32 }, - { "RayEffectFXNugget", 32, 32 }, - { "LightPulseFXNugget", 68, 32 }, - { "ViewShakeFXNugget", 140, 32 }, - { "TerrainScorchFXNugget", 48, 32 }, - { "ParticleSystemFXNugget", 832, 32 }, - { "FXListAtBonePosFXNugget", 32, 32 }, - { "Squad", 256, 32 }, - { "BuildListInfo", 400, 64 }, - - { "ScriptGroup", 128, 32 }, - { "OrCondition", 1024, 256 }, - { "ScriptAction", 2600, 512 }, - { "Script", 1024, 256 }, - { "Parameter", 8192, 1024 }, - { "Condition", 2048, 256 }, - { "Template", 32, 32 }, - { "ScriptList", 32, 32 }, - { "AttackPriorityInfo", 32, 32 }, - { "SequentialScript", 32, 32 }, - { "Win32LocalFile", 1024, 256 }, - { "StdLocalFile", 1024, 256 }, - { "RAMFile", 32, 32 }, - { "BattlePlanBonuses", 32, 32 }, - { "KindOfPercentProductionChange", 32, 32 }, - { "UserParser", 4096, 256 }, - { "XferBlockData", 32, 32 }, - { "EvaCheckInfo", 52, 16 }, - { "SuperweaponInfo", 32, 32 }, - { "NamedTimerInfo", 32, 32 }, - { "PopupMessageData", 32, 32 }, - { "FloatingTextData", 32, 32 }, - { "MapObject", 5000, 1024 }, - { "Waypoint", 1024, 32 }, - { "PolygonTrigger", 64, 64 }, - { "Bridge", 32, 32 }, - { "Mapping", 384, 64 }, - { "OutputChunk", 32, 32 }, - { "InputChunk", 32, 32 }, - { "AnimateWindow", 32, 32 }, - { "GameFont", 32, 32 }, - { "NetCommandRef", 256, 32 }, - { "GameMessageArgument", 1024, 256 }, - { "GameMessageParserArgumentType", 32, 32 }, - { "GameMessageParser", 32, 32 }, - { "WeaponBonusSet", 96, 32 }, - { "Campaign", 32, 32 }, - { "Mission", 88, 32 }, - { "ModalWindow", 32, 32 }, - { "NetPacket", 32, 32 }, - { "AISideInfo", 32, 32 }, - { "AISideBuildList", 32, 32 }, - { "MetaMapRec", 256, 32 }, - { "TransportStatus", 32, 32 }, - { "Anim2DTemplate", 32, 32 }, - { "ObjectTypes", 32, 32 }, - { "NetCommandList", 512, 32 }, - { "TurretAIData", 256, 32 }, - { "NetCommandMsg", 32, 32 }, - { "NetGameCommandMsg", 64, 32 }, - { "NetAckBothCommandMsg", 32, 32 }, - { "NetAckStage1CommandMsg", 32, 32 }, - { "NetAckStage2CommandMsg", 32, 32 }, - { "NetFrameCommandMsg", 32, 32 }, - { "NetPlayerLeaveCommandMsg", 32, 32 }, - { "NetRunAheadMetricsCommandMsg", 32, 32 }, - { "NetRunAheadCommandMsg", 32, 32 }, - { "NetDestroyPlayerCommandMsg", 32, 32 }, - { "NetDisconnectFrameCommandMsg", 32, 32 }, - { "NetDisconnectScreenOffCommandMsg", 32, 32 }, - { "NetFrameResendRequestCommandMsg", 32, 32 }, - { "NetKeepAliveCommandMsg", 32, 32 }, - { "NetDisconnectKeepAliveCommandMsg", 32, 32 }, - { "NetDisconnectPlayerCommandMsg", 32, 32 }, - { "NetPacketRouterQueryCommandMsg", 32, 32 }, - { "NetPacketRouterAckCommandMsg", 32, 32 }, - { "NetDisconnectChatCommandMsg", 32, 32 }, - { "NetChatCommandMsg", 32, 32 }, - { "NetDisconnectVoteCommandMsg", 32, 32 }, - { "NetProgressCommandMsg", 32, 32 }, - { "NetWrapperCommandMsg", 32, 32 }, - { "NetFileCommandMsg", 32, 32 }, - { "NetFileAnnounceCommandMsg", 32, 32 }, - { "NetFileProgressCommandMsg", 32, 32 }, - { "NetCommandWrapperListNode", 32, 32 }, - { "NetCommandWrapperList", 32, 32 }, - { "Connection", 32, 32 }, - { "User", 32, 32 }, - { "FrameDataManager", 32, 32 }, - { "DrawableIconInfo", 32, 32 }, - { "TintEnvelope", 128, 32 }, - { "DynamicAudioEventRTS", 4000, 256 }, - { "DrawableLocoInfo", 128, 32 }, - { "W3DPrototypeClass", 512, 256 }, - { "EnumeratedIP", 32, 32 }, - { "WaterTransparencySetting", 4, 4 }, - { "WeatherSetting", 4, 4 }, - - // W3D pools! - { "BoxPrototypeClass", 128, 128 }, - { "SpherePrototypeClass", 32, 32 }, - { "SoundRenderObjPrototypeClass", 32, 32 }, - { "RingPrototypeClass", 32, 32 }, - { "PrimitivePrototypeClass", 8192, 32 }, - { "HModelPrototypeClass", 256, 32 }, - { "ParticleEmitterPrototypeClass", 32, 32 }, - { "NullPrototypeClass", 32, 32 }, - { "HLodPrototypeClass", 700, 128 }, - { "HLodDefClass", 700, 128 }, - { "DistLODPrototypeClass", 32, 32 }, - { "DazzlePrototypeClass", 32, 32 }, - { "CollectionPrototypeClass", 32, 32 }, - { "BoxPrototypeClass", 256, 32 }, - { "AggregatePrototypeClass", 32, 32 }, - { "OBBoxRenderObjClass", 512, 128 }, - { "AABoxRenderObjClass", 32, 32 }, - { "VertexMaterialClass", 6000, 2048 }, - { "TextureClass", 1200, 256 }, - { "CloudMapTerrainTextureClass", 4, 4 }, - { "ScorchTextureClass", 4, 4 }, - { "LightMapTerrainTextureClass", 4, 4 }, - { "AlphaEdgeTextureClass", 4, 4 }, - { "AlphaTerrainTextureClass", 4, 4 }, - { "TerrainTextureClass", 4, 4 }, - { "MeshClass", 14000, 2000 }, - { "HTreeClass", 2048, 512 }, - { "HLodClass", 2048, 512 }, - { "MeshModelClass", 8192, 32 }, - { "ShareBufferClass", 32768, 1024 }, - { "AABTreeClass", 300, 128 }, - { "MotionChannelClass", 16384, 32 }, - { "BitChannelClass", 84, 32 }, - { "TimeCodedMotionChannelClass", 116, 32 }, - { "AdaptiveDeltaMotionChannelClass", 32, 32 }, - { "TimeCodedBitChannelClass", 32, 32 }, - { "UVBufferClass", 8192, 32 }, - { "TexBufferClass", 384, 128 }, - { "MatBufferClass", 256, 128 }, - { "MatrixMapperClass", 32, 32 }, - { "ScaleTextureMapperClass", 32, 32 }, - { "LinearOffsetTextureMapperClass", 96, 32 }, - { "GridTextureMapperClass", 32, 32 }, - { "RotateTextureMapperClass", 32, 32 }, - { "SineLinearOffsetTextureMapperClass", 32, 32 }, - { "StepLinearOffsetTextureMapperClass", 32, 32 }, - { "ZigZagLinearOffsetTextureMapperClass", 32, 32 }, - { "ClassicEnvironmentMapperClass", 32, 32 }, - { "EnvironmentMapperClass", 256, 32 }, - { "EdgeMapperClass", 32, 32 }, - { "WSClassicEnvironmentMapperClass", 32, 32 }, - { "WSEnvironmentMapperClass", 32, 32 }, - { "GridClassicEnvironmentMapperClass", 32, 32 }, - { "GridEnvironmentMapperClass", 32, 32 }, - { "ScreenMapperClass", 32, 32 }, - { "RandomTextureMapperClass", 32, 32 }, - { "BumpEnvTextureMapperClass", 32, 32 }, - { "MeshLoadContextClass", 4, 4 }, - { "MaterialInfoClass", 8192, 32 }, - { "MeshMatDescClass", 8192, 32 }, - { "TextureLoadTaskClass", 256, 32 }, - { "SortingNodeStruct", 288, 32 }, - { "ProxyArrayClass", 32, 32 }, - { "Line3DClass", 8, 8 }, - { "Render2DClass", 64, 32 }, - { "SurfaceClass", 128, 32 }, - { "FontCharsClassCharDataStruct", 1024, 32 }, - { "FontCharsBuffer", 16, 4 }, - { "FVFInfoClass", 152, 64 }, - { "TerrainTracksRenderObjClass", 128, 32 }, - { "DynamicIBAccessClass", 32, 32 }, - { "DX8IndexBufferClass", 128, 32 }, - { "SortingIndexBufferClass", 32, 32 }, - { "DX8VertexBufferClass", 128, 32 }, - { "SortingVertexBufferClass", 32, 32 }, - { "DynD3DMATERIAL8", 8192, 32 }, - { "DynamicMatrix3D", 512, 32 }, - { "MeshGeometryClass", 32, 32 }, - { "DynamicMeshModel", 32, 32 }, - { "GapFillerClass", 32, 32 }, - { "FontCharsClass", 64, 32 }, - { "ThumbnailManagerClass", 32, 32}, - { "SmudgeSet", 32, 32}, - { "Smudge", 128, 32}, - { 0, 0, 0 } -}; - -//----------------------------------------------------------------------------- -void userMemoryAdjustPoolSize(const char *poolName, Int& initialAllocationCount, Int& overflowAllocationCount) -{ - if (initialAllocationCount > 0) - return; - - for (const PoolSizeRec* p = sizes; p->name != NULL; ++p) - { - if (strcmp(p->name, poolName) == 0) - { - initialAllocationCount = p->initial; - overflowAllocationCount = p->overflow; - return; - } - } - - DEBUG_CRASH(("Initial size for pool %s not found -- you should add it to MemoryInit.cpp\n",poolName)); -} - -//----------------------------------------------------------------------------- -static Int roundUpMemBound(Int i) -{ - const int MEM_BOUND_ALIGNMENT = 4; - - if (i < MEM_BOUND_ALIGNMENT) - return MEM_BOUND_ALIGNMENT; - else - return (i + (MEM_BOUND_ALIGNMENT-1)) & ~(MEM_BOUND_ALIGNMENT-1); -} - -//----------------------------------------------------------------------------- -void userMemoryManagerInitPools() -{ - // note that we MUST use stdio stuff here, and not the normal game file system - // (with bigfile support, etc), because that relies on memory pools, which - // aren't yet initialized properly! so rely ONLY on straight stdio stuff here. - // (not even AsciiString. thanks.) - - // since we're called prior to main, the cur dir might not be what - // we expect. so do it the hard way. - char buf[_MAX_PATH]; - ::GetModuleFileName(NULL, buf, sizeof(buf)); - char* pEnd = buf + strlen(buf); - while (pEnd != buf) - { - if (*pEnd == '\\') - { - *pEnd = 0; - break; - } - --pEnd; - } - strcat(buf, "\\Data\\INI\\MemoryPools.ini"); - - FILE* fp = fopen(buf, "r"); - if (fp) - { - char poolName[256]; - int initial, overflow; - while (fgets(buf, _MAX_PATH, fp)) - { - if (buf[0] == ';') - continue; - if (sscanf(buf, "%s %d %d", poolName, &initial, &overflow ) == 3) - { - for (PoolSizeRec* p = sizes; p->name != NULL; ++p) - { - if (stricmp(p->name, poolName) == 0) - { - // currently, these must be multiples of 4. so round up. - p->initial = roundUpMemBound(initial); - p->overflow = roundUpMemBound(overflow); - break; // from for-p - } - } - } - } - fclose(fp); - } -} - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: MemoryInit.cpp +//----------------------------------------------------------------------------- +// +// Westwood Studios Pacific. +// +// Confidential Information +// Copyright (C) 2001 - All Rights Reserved +// +//----------------------------------------------------------------------------- +// +// Project: RTS3 +// +// File name: MemoryInit.cpp +// +// Created: Steven Johnson, August 2001 +// +// Desc: Memory manager +// +// ---------------------------------------------------------------------------- +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +// SYSTEM INCLUDES + +// USER INCLUDES +#include "Lib/BaseType.h" +#include "Common/GameMemory.h" + +#ifdef RTS_INTERNAL +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +//----------------------------------------------------------------------------- +void userMemoryManagerGetDmaParms(Int *numSubPools, const PoolInitRec **pParms) +{ + static const PoolInitRec defaultDMA[7] = + { + // name, allocsize, initialcount, overflowcount + { "dmaPool_16", 16, 130000, 10000 }, + { "dmaPool_32", 32, 250000, 10000 }, + { "dmaPool_64", 64, 100000, 10000 }, + { "dmaPool_128", 128, 80000, 10000 }, + { "dmaPool_256", 256, 20000, 5000 }, + { "dmaPool_512", 512, 16000, 5000 }, + { "dmaPool_1024", 1024, 6000, 1024} + }; + + *numSubPools = 7; + *pParms = defaultDMA; +} + +//----------------------------------------------------------------------------- +struct PoolSizeRec +{ + const char* name; + Int initial; + Int overflow; +}; + +//----------------------------------------------------------------------------- +// And please be careful of duplicates. They are not rejected. +// not const -- we might override from INI +static PoolSizeRec sizes[] = +{ + { "PartitionContactListNode", 2048, 512 }, + { "BattleshipUpdate", 32, 32 }, + { "FlyToDestAndDestroyUpdate", 32, 32 }, + { "MusicTrack", 32, 32 }, + { "PositionalSoundPool", 32, 32 }, + { "GameMessage", 2048, 32 }, + { "NameKeyBucketPool", 9000, 1024 }, + { "ObjectSellInfo", 16, 16 }, + { "ProductionPrerequisitePool", 1024, 32 }, + { "RadarObject", 512, 32 }, + { "ResourceGatheringManager", 16, 16 }, + { "SightingInfo", 8192, 2048 },// Looks big, but all 3000 objects used to have 4 just built in. + { "SpecialPowerTemplate", 84, 32 }, + { "StateMachinePool", 32, 32 }, + { "TeamPool", 128, 32 }, // if you increase this, increase player/team relation map pools + { "PlayerRelationMapPool", 128, 32 }, + { "TeamRelationMapPool", 128, 32 }, + { "TeamPrototypePool", 256, 32 }, + { "TerrainType", 256, 32 }, + { "ThingTemplatePool", 2120, 32 }, + { "TunnelTracker", 16, 16 }, + { "Upgrade", 16, 16 }, + { "UpgradeTemplate", 128, 16 }, + { "Anim2D", 32, 32 }, + { "CommandButton", 1024, 256 }, + { "CommandSet", 820, 16 }, + { "DisplayString", 32, 32 }, + { "WebBrowserURL", 16, 16 }, + { "Drawable", 4096, 32 }, + { "Image", 2048, 32 }, + { "ParticlePool", 1400, 1024 }, + { "ParticleSystemTemplatePool", 1100, 32 }, + { "ParticleSystemPool", 1024, 32 }, + { "TerrainRoadType", 100, 32, }, + { "WindowLayoutPool", 32, 32 }, + { "AnimatedParticleSysBoneClientUpdate", 16, 16 }, + { "SwayClientUpdate", 32, 32 }, + { "BeaconClientUpdate", 64, 32 }, + { "AIGroupPool", 64, 32 }, + { "AIDockMachinePool", 256, 32 }, + { "AIGuardMachinePool", 32, 32 }, + { "AIGuardRetaliateMachinePool", 32, 32 }, + { "AITNGuardMachinePool", 32, 32 }, + { "PathNodePool", 8192, 1024 }, + { "PathPool", 256, 16 }, + { "WorkOrder", 32, 32 }, + { "TeamInQueue", 32, 32 }, + { "AIPlayer", 12, 4 }, + { "AISkirmishPlayer", 8, 8 }, + { "AIStateMachine", 600, 32 }, + { "JetAIStateMachine", 64, 32 }, + { "HeliAIStateMachine", 64, 32 }, + { "VtolAIStateMachine", 64, 32 }, + { "AIAttackMoveStateMachine", 2048, 32 }, + { "AIAttackThenIdleStateMachine", 512, 32 }, + { "AttackStateMachine", 512, 32 }, + { "CrateTemplate", 32, 32 }, + { "ExperienceTrackerPool", 2048, 512 }, + { "FiringTrackerPool", 4096, 256 }, + { "ObjectRepulsorHelper", 1024, 256 }, + { "ObjectSMCHelperPool", 2048, 256 }, + { "ObjectWeaponStatusHelperPool", 4096, 256 }, + { "ObjectDefectionHelperPool", 2048, 256 }, + { "StatusDamageHelper", 1500, 256 }, + { "SubdualDamageHelper", 1500, 256 }, + { "TempWeaponBonusHelper", 4096, 256 }, + { "Locomotor", 2048, 32 }, + { "LocomotorTemplate", 192, 32 }, + { "ObjectPool", 1500, 256 }, + { "SimpleObjectIteratorPool", 32, 32 }, + { "SimpleObjectIteratorClumpPool", 4096, 32 }, + { "PartitionDataPool", 2048, 512 }, + { "BuildEntry", 32, 32 }, + { "Weapon", 4096, 32 }, + { "WeaponTemplate", 360, 32 }, + { "AIUpdateInterface", 600, 32 }, + { "ActiveBody", 1024, 32 }, + { "ActiveShroudUpgrade", 32, 32 }, + { "AssistedTargetingUpdate", 32, 32 }, + { "AudioEventInfo", 4096, 64 }, + { "AudioRequest", 256, 8 }, + { "AutoHealBehavior", 1024, 256 }, + { "WeaponBonusUpdate", 16, 16 }, + { "GrantStealthBehavior", 4096, 32 }, + { "NeutronBlastBehavior", 4096, 32 }, + { "CountermeasuresBehavior", 256, 32 }, + { "BaseRegenerateUpdate", 128, 32 }, + { "BoneFXDamage", 64, 32 }, + { "BoneFXUpdate", 64, 32 }, + { "BridgeBehavior", 4, 4 }, + { "BridgeTowerBehavior", 32, 32 }, + { "BridgeScaffoldBehavior", 32, 32 }, + { "CaveContain", 16, 16 }, + { "HealContain", 32, 32 }, + { "CreateCrateDie", 256, 128 }, + { "CreateObjectDie", 1024, 32 }, + { "EjectPilotDie", 1024, 32 }, + { "CrushDie", 1024, 32 }, + { "DamDie", 8, 8 }, + { "DeliverPayloadStateMachine", 32, 32 }, + { "DeliverPayloadAIUpdate", 32, 32 }, + { "DeletionUpdate", 128, 32 }, + { "SmartBombTargetHomingUpdate", 8, 8 }, + { "DynamicAudioEventInfo", 16, 256 }, // Note: some levels have none, some have lots. Since all are allocated at level load time, we can set this low for the levels with none. + { "HackInternetStateMachine", 32, 32 }, + { "HackInternetAIUpdate", 32, 32 }, + { "MissileAIUpdate", 512, 32 }, + { "DumbProjectileBehavior", 64, 32 }, + { "FreeFallProjectileBehavior", 32, 32 }, + { "DestroyDie", 1024, 32 }, + { "UpgradeDie", 128, 32 }, + { "KeepObjectDie", 128, 32 }, + { "DozerAIUpdate", 32, 32 }, + { "DynamicGeometryInfoUpdate", 16, 16 }, + { "DynamicShroudClearingRangeUpdate", 128, 16 }, + { "FXListDie", 1024, 32 }, + { "FireSpreadUpdate", 2048, 128 }, + { "FirestormDynamicGeometryInfoUpdate", 16, 16 }, + { "FireWeaponCollide", 2048, 32 }, + { "FireWeaponUpdate", 32, 32 }, + { "FlammableUpdate", 512, 256 }, + { "FloatUpdate", 512, 128 }, + { "TensileFormationUpdate", 256, 32 }, + { "GarrisonContain", 256, 32 }, + { "HealCrateCollide", 32, 32 }, + { "HeightDieUpdate", 32, 32 }, + { "ScatterShotUpdate", 128, 64 }, + { "FireWeaponWhenDamagedBehavior", 32, 32 }, + { "FireWeaponWhenDeadBehavior", 128, 64 }, + { "DelayedUpgradeBehavior", 128, 64 }, + { "GenerateMinefieldBehavior", 32, 32 }, + { "HelicopterSlowDeathBehavior", 64, 32 }, + { "ParkingPlaceBehavior", 32, 32 }, + { "FlightDeckBehavior", 8, 8 }, +#ifdef ALLOW_SURRENDER + { "POWTruckAIUpdate", 32, 32, }, + { "POWTruckBehavior", 32, 32, }, + { "PrisonBehavior", 32, 32 }, + { "PrisonVisual", 32, 32 }, + { "PropagandaCenterBehavior", 16, 16 }, +#endif + { "PropagandaTowerBehavior", 16, 16 }, + { "BunkerBusterBehavior", 16, 16 }, + { "ObjectTracker", 128, 32 }, + { "OCLUpdate", 16, 16 }, + { "BodyParticleSystem", 196, 64 }, + { "HighlanderBody", 2048, 128 }, + { "UndeadBody", 32, 32 }, + { "HordeUpdate", 128, 32 }, + { "ImmortalBody", 128, 256 }, + { "InactiveBody", 2048, 32 }, + { "InstantDeathBehavior", 512, 32 }, + { "LaserUpdate", 32, 32 }, + { "PointDefenseLaserUpdate", 32, 32 }, + { "CleanupHazardUpdate", 32, 32 }, + { "AutoFindHealingUpdate", 256, 32 }, + { "CommandButtonHuntUpdate", 512, 8 }, + { "PilotFindVehicleUpdate", 256, 32 }, + { "DemoTrapUpdate", 32, 32 }, + { "ParticleUplinkCannonUpdate", 16, 16 }, + { "SpectreGunshipUpdate", 8, 8 }, + { "SpectreGunshipDeploymentUpdate", 8, 8 }, + { "BaikonurLaunchPower", 4, 4 }, + { "RadiusDecalUpdate", 16, 16 }, + { "RadiusDecalBehavior", 32, 32 }, + { "BattlePlanUpdate", 32, 32 }, + { "LifetimeUpdate", 32, 32 }, + { "LocomotorSetUpgrade", 512, 128 }, + { "LockWeaponCreate", 64, 128 }, + { "AutoDepositUpdate", 256, 32 }, + { "NeutronMissileUpdate", 512, 32 }, + { "MoneyCrateCollide", 48, 16 }, + { "NeutronMissileSlowDeathBehavior", 8, 8 }, + { "OpenContain", 128, 32 }, + { "OverchargeBehavior", 32, 32 }, + { "OverlordContain", 32, 32 }, + { "HelixContain", 32, 32 }, + { "ParachuteContain", 128, 32 }, + { "PhysicsBehavior", 600, 32 }, + { "PoisonedBehavior", 512, 64 }, + { "ProductionEntry", 32, 32 }, + { "ProductionUpdate", 256, 32 }, + { "ProjectileStreamUpdate", 32, 32 }, + { "ProneUpdate", 128, 32 }, + { "QueueProductionExitUpdate", 32, 32 }, + { "RadarUpdate", 16, 16 }, + { "RadarUpgrade", 16, 16 }, + { "AnimationSteeringUpdate", 1024, 32 }, + { "SupplyWarehouseCripplingBehavior", 16, 16 }, + { "CostModifierUpgrade", 32, 32 }, + { "ProductionTimeModifierUpgrade", 32, 32 }, + { "UnitProductionBonusUpgrade", 64, 32 }, + { "CashBountyPower", 32, 32 }, + { "CleanupAreaPower", 32, 32 }, + { "ObjectCreationUpgrade", 196, 32 }, + { "MinefieldBehavior", 256, 32 }, + { "JetSlowDeathBehavior", 64, 32 }, + { "BattleBusSlowDeathBehavior", 64, 32 }, + { "RebuildHoleBehavior", 64, 32 }, + { "RebuildHoleExposeDie", 64, 32 }, + { "RepairDockUpdate", 32, 32 }, +#ifdef ALLOW_SURRENDER + { "PrisonDockUpdate", 32, 32 }, +#endif + { "RailedTransportDockUpdate", 16, 16 }, + { "RailedTransportAIUpdate", 16, 16 }, + { "RailedTransportContain", 16, 16 }, + { "RailroadBehavior", 16, 16 }, + { "SalvageCrateCollide", 32, 32 }, + { "ShroudCrateCollide", 32, 32 }, + { "SlavedUpdate", 64, 32 }, + { "SlowDeathBehavior", 1400, 256 }, + { "SpyVisionUpdate", 16, 16 }, + { "DefaultProductionExitUpdate", 32, 32 }, + { "SpawnPointProductionExitUpdate", 32, 32 }, + { "SpawnBehavior", 32, 32 }, + { "SpecialPowerCompletionDie", 32, 32 }, + { "SpecialPowerCreate", 32, 32 }, + { "PreorderCreate", 32, 32 }, + { "SpecialAbility", 512, 32 }, + { "SpecialAbilityUpdate", 512, 32 }, + { "MissileLauncherBuildingUpdate", 32, 32 }, + { "SquishCollide", 512, 32 }, + { "StructureBody", 512, 64 }, + { "HiveStructureBody", 64, 32 }, //Stinger sites + { "StructureCollapseUpdate", 32, 32 }, + { "StructureToppleUpdate", 32, 32 }, + { "SupplyCenterCreate", 32, 32 }, + { "SupplyCenterDockUpdate", 32, 32 }, + { "SupplyCenterProductionExitUpdate", 32, 32 }, + { "SupplyTruckStateMachine", 256, 32 }, + { "SupplyTruckAIUpdate", 32, 32 }, + { "SupplyWarehouseCreate", 48, 16 }, + { "SupplyWarehouseDockUpdate", 48, 16 }, + { "EnemyNearUpdate", 1024, 32 }, + { "TechBuildingBehavior", 32, 32 }, + { "ToppleUpdate", 256, 128 }, + { "TransitionDamageFX", 384, 128 }, + { "TransportAIUpdate", 64, 32 }, + { "TransportContain", 128, 32 }, + { "RiderChangeContain", 128, 32 }, + { "InternetHackContain", 16, 16 }, + { "TunnelContain", 8, 8 }, + { "TunnelContainDie", 32, 32 }, + { "TunnelCreate", 32, 32 }, + { "TurretAI", 256, 32 }, + { "TurretStateMachine", 128, 32 }, + { "TurretSwapUpgrade", 512, 128 }, + { "UnitCrateCollide", 32, 32 }, + { "UnpauseSpecialPowerUpgrade", 32, 32 }, + { "VeterancyCrateCollide", 32, 32 }, + { "VeterancyGainCreate", 512, 128 }, + { "ConvertToCarBombCrateCollide", 256, 128 }, + { "ConvertToHijackedVehicleCrateCollide", 256, 128 }, + { "SabotageCommandCenterCrateCollide", 256, 128 }, + { "SabotageFakeBuildingCrateCollide", 256, 128 }, + { "SabotageInternetCenterCrateCollide", 256, 128 }, + { "SabotageMilitaryFactoryCrateCollide", 256, 128 }, + { "SabotagePowerPlantCrateCollide", 256, 128 }, + { "SabotageSuperweaponCrateCollide", 256, 128 }, + { "SabotageSupplyCenterCrateCollide", 256, 128 }, + { "SabotageSupplyDropzoneCrateCollide", 256, 128 }, + { "JetAIUpdate", 64, 32 }, + { "ChinookAIUpdate", 32, 32 }, + { "WanderAIUpdate", 32, 32 }, + { "TeleporterAIUpdate", 64, 32 }, + { "WaveGuideUpdate", 16, 16 }, + { "ArmorDamageScalarUpdate", 256, 32 }, + { "WeaponBonusUpgrade", 512, 128 }, + { "WeaponSetUpgrade", 512, 128 }, + { "ArmorUpgrade", 512, 128 }, + { "WorkerAIUpdate", 128, 128 }, + { "WorkerStateMachine", 128, 128 }, + { "ChinookAIStateMachine", 32, 32 }, + { "DeployStyleAIUpdate", 32, 32 }, + { "AssaultTransportAIUpdate", 64, 32 }, + { "StreamingArchiveFile", 8, 8 }, + + { "DozerActionStateMachine", 256, 32 }, + { "DozerPrimaryStateMachine", 256, 32 }, + { "W3DDisplayString", 1400, 128 }, + { "W3DDefaultDraw", 1024, 128 }, + { "W3DDebrisDraw", 128, 128 }, + { "W3DDependencyModelDraw", 64, 64 }, + { "W3DLaserDraw", 32, 32 }, + { "W3DModelDraw", 2048, 512 }, + { "W3DOverlordTankDraw", 64, 64 }, + { "W3DOverlordTruckDraw", 64, 64 }, + { "W3DOverlordAircraftDraw", 64, 64 }, + { "W3DPoliceCarDraw", 32, 32 }, + { "W3DProjectileStreamDraw", 32, 32 }, + { "W3DRopeDraw", 32, 32 }, + { "W3DScienceModelDraw", 32, 32 }, + { "W3DSupplyDraw", 40, 16 }, + { "W3DTankDraw", 256, 32 }, + { "W3DTreeDraw", 16, 16 }, + { "W3DPropDraw", 16, 16 }, + { "W3DTracerDraw", 64, 32 }, + { "W3DTruckDraw", 128, 32 }, + { "W3DTankTruckDraw", 32, 16 }, + { "W3DTreeTextureClass", 4, 4 }, + { "DefaultSpecialPower", 32, 32 }, + { "OCLSpecialPower", 96, 32 }, + { "FireWeaponPower", 32, 32 }, +#ifdef ALLOW_DEMORALIZE + { "DemoralizeSpecialPower", 16, 16, }, +#endif + { "CashHackSpecialPower", 32, 32 }, + { "CommandSetUpgrade", 32, 32 }, + { "PassengersFireUpgrade", 32, 32 }, + { "GrantUpgradeCreate", 256, 32 }, + { "GrantScienceUpgrade", 256, 32 }, + { "ReplaceObjectUpgrade", 32, 32 }, + { "ModelConditionUpgrade", 32, 32 }, + { "UpgradeSpecialPower", 64, 32 }, + { "SpyVisionSpecialPower", 256, 32 }, + { "StealthDetectorUpdate", 256, 32 }, + { "StealthUpdate", 512, 128 }, + { "StealthUpgrade", 256, 32 }, + { "StatusBitsUpgrade", 128, 128 }, + { "SubObjectsUpgrade", 128, 128 }, + { "ExperienceScalarUpgrade", 256, 128 }, + { "MaxHealthUpgrade", 128, 128 }, + { "WeaponBonusUpgrade", 128, 64 }, + { "StickyBombUpdate", 64, 32 }, + { "FireOCLAfterWeaponCooldownUpdate", 64, 32 }, + { "HijackerUpdate", 64, 32 }, + { "ChinaMinesUpgrade", 64, 32 }, + { "PowerPlantUpdate", 48, 16 }, + { "PowerPlantUpgrade", 48, 16 }, + { "DefectorSpecialPower", 16, 16 }, + { "CheckpointUpdate", 16, 16 }, + { "MobNexusContain", 128, 32 }, + { "MobMemberSlavedUpdate", 64, 32 }, + { "EMPUpdate", 64, 32 }, + { "LeafletDropBehavior", 64, 32 }, + { "Overridable", 32, 32 }, + + { "W3DGameWindow", 700, 256 }, + { "SuccessState", 32, 32 }, + { "FailureState", 32, 32 }, + { "ContinueState", 32, 32 }, + { "SleepState", 32, 32 }, + + { "AIDockWaitForClearanceState", 256, 32 }, + { "AIDockProcessDockState", 256, 32 }, + { "AIGuardInnerState", 32, 32 }, + { "AIGuardIdleState", 32, 32 }, + { "AIGuardOuterState", 32, 32 }, + { "AIGuardReturnState", 32, 32 }, + { "AIGuardPickUpCrateState", 32, 32 }, + { "AIGuardAttackAggressorState", 32, 32 }, + { "AIGuardRetaliateInnerState", 32, 32 }, + { "AIGuardRetaliateIdleState", 32, 32 }, + { "AIGuardRetaliateOuterState", 32, 32 }, + { "AIGuardRetaliateReturnState", 32, 32 }, + { "AIGuardRetaliatePickUpCrateState", 32, 32 }, + { "AIGuardRetaliateAttackAggressorState", 32, 32 }, + { "AITNGuardInnerState", 32, 32 }, + { "AITNGuardIdleState", 32, 32 }, + { "AITNGuardOuterState", 32, 32 }, + { "AITNGuardReturnState", 32, 32 }, + { "AITNGuardPickUpCrateState", 32, 32 }, + { "AITNGuardAttackAggressorState", 32, 32 }, + { "AIIdleState", 2400, 32 }, + { "AIRappelState", 600, 32 }, + { "AIBusyState", 600, 32 }, + { "AIWaitState", 600, 32 }, + { "AIAttackState", 4096, 32 }, + { "AIAttackSquadState", 600, 32 }, + { "AIDeadState", 600, 32 }, + { "AIDockState", 600, 32 }, + { "AIExitState", 600, 32 }, + { "AIExitInstantlyState", 600, 32 }, + { "AIGuardState", 600, 32 }, + { "AIGuardRetaliateState", 600, 32 }, + { "AITunnelNetworkGuardState", 600, 32 }, + { "AIHuntState", 600, 32 }, + { "AIAttackAreaState", 600, 32 }, + { "AIFaceState", 1200, 32 }, + { "ApproachState", 600, 32 }, + { "DeliveringState", 600, 32 }, + { "ConsiderNewApproachState", 600, 32 }, + { "RecoverFromOffMapState", 600, 32 }, + { "HeadOffMapState", 600, 32 }, + { "CleanUpState", 600, 32 }, + { "HackInternetState", 600, 32 }, + { "PackingState", 600, 32 }, + { "UnpackingState", 600, 32 }, + { "SupplyTruckWantsToPickUpOrDeliverBoxesState", 600, 32 }, + { "RegroupingState", 600, 32 }, + { "DockingState", 600, 32 }, + { "ChinookEvacuateState", 32, 32 }, + { "ChinookHeadOffMapState", 32, 32 }, + { "ChinookTakeoffOrLandingState", 32, 32 }, + { "ChinookCombatDropState", 32, 32 }, + { "DozerActionPickActionPosState", 256, 32 }, + { "DozerActionMoveToActionPosState", 256, 32 }, + { "DozerActionDoActionState", 256, 32 }, + { "DozerPrimaryIdleState", 256, 32 }, + { "DozerActionState", 256, 32 }, + { "DozerPrimaryGoingHomeState", 256, 32 }, + { "JetAwaitingRunwayState", 64, 32 }, + { "JetOrHeliCirclingDeadAirfieldState", 64, 32 }, + { "HeliTakeoffOrLandingState", 64, 32 }, + { "VtolTakeoffOrLandingState", 64, 32 }, + { "JetOrHeliParkOrientState", 64, 32 }, + { "VtolParkOrientState", 64, 32 }, + { "JetOrHeliReloadAmmoState", 64, 32 }, + { "SupplyTruckBusyState", 600, 32 }, + { "SupplyTruckIdleState", 600, 32 }, + { "ActAsDozerState", 600, 32 }, + { "ActAsSupplyTruckState", 600, 32 }, + { "AIDockApproachState", 256, 32 }, + { "AIDockAdvancePositionState", 256, 32 }, + { "AIDockMoveToEntryState", 256, 32 }, + { "AIDockMoveToDockState", 256, 32 }, + { "AIDockMoveToExitState", 256, 32 }, + { "AIDockMoveToRallyState", 256, 32 }, + { "AIMoveToState", 600, 32 }, + { "AIMoveOutOfTheWayState", 600, 32 }, + { "AIMoveAndTightenState", 600, 32 }, + { "AIMoveAwayFromRepulsorsState", 600, 32 }, + { "AIAttackApproachTargetState", 96, 32 }, + { "AIAttackPursueTargetState", 96, 32 }, + { "AIAttackAimAtTargetState", 96, 32 }, + { "AIAttackFireWeaponState", 256, 32 }, + { "AIPickUpCrateState", 4096, 32 }, + { "AIFollowWaypointPathState", 1200, 32 }, + { "AIFollowWaypointPathExactState", 1200, 32 }, + { "AIWanderInPlaceState", 600, 32 }, + { "AIFollowPathState", 1200, 32 }, + { "AIMoveAndEvacuateState", 1200, 32 }, + { "AIMoveAndDeleteState", 600, 32 }, + { "AIEnterState", 600, 32 }, + { "JetOrHeliReturningToDeadAirfieldState", 64, 32 }, + { "JetOrHeliReturnForLandingState", 64, 32 }, + { "TurretAIIdleState", 600, 32 }, + { "TurretAIIdleScanState", 600, 32 }, + { "TurretAIAimTurretState", 600, 32 }, + { "TurretAIRecenterTurretState", 600, 32 }, + { "TurretAIHoldTurretState", 600, 32 }, + { "JetOrHeliTaxiState", 64, 32 }, + { "JetTakeoffOrLandingState", 64, 32 }, + { "JetPauseBeforeTakeoffState", 64, 32 }, + { "AIAttackMoveToState", 600, 32 }, + { "AIAttackFollowWaypointPathState", 1200, 32 }, + { "AIWanderState", 600, 32 }, + { "AIPanicState", 600, 32 }, + { "ChinookMoveToBldgState", 32, 32 }, + { "ChinookRecordCreationState", 32, 32 }, + { "ScienceInfo", 96, 32 }, + { "RankInfo", 32, 32 }, + + { "FireWeaponNugget", 32, 32 }, + { "AttackNugget", 32, 32 }, + { "DeliverPayloadNugget", 48, 32 }, + { "ApplyRandomForceNugget", 32, 32 }, + { "GenericObjectCreationNugget", 632, 32 }, + { "SoundFXNugget", 320, 32 }, + { "TracerFXNugget", 32, 32 }, + { "RayEffectFXNugget", 32, 32 }, + { "LightPulseFXNugget", 68, 32 }, + { "ViewShakeFXNugget", 140, 32 }, + { "TerrainScorchFXNugget", 48, 32 }, + { "ParticleSystemFXNugget", 832, 32 }, + { "FXListAtBonePosFXNugget", 32, 32 }, + { "Squad", 256, 32 }, + { "BuildListInfo", 400, 64 }, + + { "ScriptGroup", 128, 32 }, + { "OrCondition", 1024, 256 }, + { "ScriptAction", 2600, 512 }, + { "Script", 1024, 256 }, + { "Parameter", 8192, 1024 }, + { "Condition", 2048, 256 }, + { "Template", 32, 32 }, + { "ScriptList", 32, 32 }, + { "AttackPriorityInfo", 32, 32 }, + { "SequentialScript", 32, 32 }, + { "Win32LocalFile", 1024, 256 }, + { "StdLocalFile", 1024, 256 }, + { "RAMFile", 32, 32 }, + { "BattlePlanBonuses", 32, 32 }, + { "KindOfPercentProductionChange", 32, 32 }, + { "UserParser", 4096, 256 }, + { "XferBlockData", 32, 32 }, + { "EvaCheckInfo", 52, 16 }, + { "SuperweaponInfo", 32, 32 }, + { "NamedTimerInfo", 32, 32 }, + { "PopupMessageData", 32, 32 }, + { "FloatingTextData", 32, 32 }, + { "MapObject", 5000, 1024 }, + { "Waypoint", 1024, 32 }, + { "PolygonTrigger", 64, 64 }, + { "Bridge", 32, 32 }, + { "Mapping", 384, 64 }, + { "OutputChunk", 32, 32 }, + { "InputChunk", 32, 32 }, + { "AnimateWindow", 32, 32 }, + { "GameFont", 32, 32 }, + { "NetCommandRef", 256, 32 }, + { "GameMessageArgument", 1024, 256 }, + { "GameMessageParserArgumentType", 32, 32 }, + { "GameMessageParser", 32, 32 }, + { "WeaponBonusSet", 96, 32 }, + { "Campaign", 32, 32 }, + { "Mission", 88, 32 }, + { "ModalWindow", 32, 32 }, + { "NetPacket", 32, 32 }, + { "AISideInfo", 32, 32 }, + { "AISideBuildList", 32, 32 }, + { "MetaMapRec", 256, 32 }, + { "TransportStatus", 32, 32 }, + { "Anim2DTemplate", 32, 32 }, + { "ObjectTypes", 32, 32 }, + { "NetCommandList", 512, 32 }, + { "TurretAIData", 256, 32 }, + { "NetCommandMsg", 32, 32 }, + { "NetGameCommandMsg", 64, 32 }, + { "NetAckBothCommandMsg", 32, 32 }, + { "NetAckStage1CommandMsg", 32, 32 }, + { "NetAckStage2CommandMsg", 32, 32 }, + { "NetFrameCommandMsg", 32, 32 }, + { "NetPlayerLeaveCommandMsg", 32, 32 }, + { "NetRunAheadMetricsCommandMsg", 32, 32 }, + { "NetRunAheadCommandMsg", 32, 32 }, + { "NetDestroyPlayerCommandMsg", 32, 32 }, + { "NetDisconnectFrameCommandMsg", 32, 32 }, + { "NetDisconnectScreenOffCommandMsg", 32, 32 }, + { "NetFrameResendRequestCommandMsg", 32, 32 }, + { "NetKeepAliveCommandMsg", 32, 32 }, + { "NetDisconnectKeepAliveCommandMsg", 32, 32 }, + { "NetDisconnectPlayerCommandMsg", 32, 32 }, + { "NetPacketRouterQueryCommandMsg", 32, 32 }, + { "NetPacketRouterAckCommandMsg", 32, 32 }, + { "NetDisconnectChatCommandMsg", 32, 32 }, + { "NetChatCommandMsg", 32, 32 }, + { "NetDisconnectVoteCommandMsg", 32, 32 }, + { "NetProgressCommandMsg", 32, 32 }, + { "NetWrapperCommandMsg", 32, 32 }, + { "NetFileCommandMsg", 32, 32 }, + { "NetFileAnnounceCommandMsg", 32, 32 }, + { "NetFileProgressCommandMsg", 32, 32 }, + { "NetCommandWrapperListNode", 32, 32 }, + { "NetCommandWrapperList", 32, 32 }, + { "Connection", 32, 32 }, + { "User", 32, 32 }, + { "FrameDataManager", 32, 32 }, + { "DrawableIconInfo", 32, 32 }, + { "TintEnvelope", 128, 32 }, + { "DynamicAudioEventRTS", 4000, 256 }, + { "DrawableLocoInfo", 128, 32 }, + { "W3DPrototypeClass", 512, 256 }, + { "EnumeratedIP", 32, 32 }, + { "WaterTransparencySetting", 4, 4 }, + { "WeatherSetting", 4, 4 }, + + // W3D pools! + { "BoxPrototypeClass", 128, 128 }, + { "SpherePrototypeClass", 32, 32 }, + { "SoundRenderObjPrototypeClass", 32, 32 }, + { "RingPrototypeClass", 32, 32 }, + { "PrimitivePrototypeClass", 8192, 32 }, + { "HModelPrototypeClass", 256, 32 }, + { "ParticleEmitterPrototypeClass", 32, 32 }, + { "NullPrototypeClass", 32, 32 }, + { "HLodPrototypeClass", 700, 128 }, + { "HLodDefClass", 700, 128 }, + { "DistLODPrototypeClass", 32, 32 }, + { "DazzlePrototypeClass", 32, 32 }, + { "CollectionPrototypeClass", 32, 32 }, + { "BoxPrototypeClass", 256, 32 }, + { "AggregatePrototypeClass", 32, 32 }, + { "OBBoxRenderObjClass", 512, 128 }, + { "AABoxRenderObjClass", 32, 32 }, + { "VertexMaterialClass", 6000, 2048 }, + { "TextureClass", 1200, 256 }, + { "CloudMapTerrainTextureClass", 4, 4 }, + { "ScorchTextureClass", 4, 4 }, + { "LightMapTerrainTextureClass", 4, 4 }, + { "AlphaEdgeTextureClass", 4, 4 }, + { "AlphaTerrainTextureClass", 4, 4 }, + { "TerrainTextureClass", 4, 4 }, + { "MeshClass", 14000, 2000 }, + { "HTreeClass", 2048, 512 }, + { "HLodClass", 2048, 512 }, + { "MeshModelClass", 8192, 32 }, + { "ShareBufferClass", 32768, 1024 }, + { "AABTreeClass", 300, 128 }, + { "MotionChannelClass", 16384, 32 }, + { "BitChannelClass", 84, 32 }, + { "TimeCodedMotionChannelClass", 116, 32 }, + { "AdaptiveDeltaMotionChannelClass", 32, 32 }, + { "TimeCodedBitChannelClass", 32, 32 }, + { "UVBufferClass", 8192, 32 }, + { "TexBufferClass", 384, 128 }, + { "MatBufferClass", 256, 128 }, + { "MatrixMapperClass", 32, 32 }, + { "ScaleTextureMapperClass", 32, 32 }, + { "LinearOffsetTextureMapperClass", 96, 32 }, + { "GridTextureMapperClass", 32, 32 }, + { "RotateTextureMapperClass", 32, 32 }, + { "SineLinearOffsetTextureMapperClass", 32, 32 }, + { "StepLinearOffsetTextureMapperClass", 32, 32 }, + { "ZigZagLinearOffsetTextureMapperClass", 32, 32 }, + { "ClassicEnvironmentMapperClass", 32, 32 }, + { "EnvironmentMapperClass", 256, 32 }, + { "EdgeMapperClass", 32, 32 }, + { "WSClassicEnvironmentMapperClass", 32, 32 }, + { "WSEnvironmentMapperClass", 32, 32 }, + { "GridClassicEnvironmentMapperClass", 32, 32 }, + { "GridEnvironmentMapperClass", 32, 32 }, + { "ScreenMapperClass", 32, 32 }, + { "RandomTextureMapperClass", 32, 32 }, + { "BumpEnvTextureMapperClass", 32, 32 }, + { "MeshLoadContextClass", 4, 4 }, + { "MaterialInfoClass", 8192, 32 }, + { "MeshMatDescClass", 8192, 32 }, + { "TextureLoadTaskClass", 256, 32 }, + { "SortingNodeStruct", 288, 32 }, + { "ProxyArrayClass", 32, 32 }, + { "Line3DClass", 8, 8 }, + { "Render2DClass", 64, 32 }, + { "SurfaceClass", 128, 32 }, + { "FontCharsClassCharDataStruct", 1024, 32 }, + { "FontCharsBuffer", 16, 4 }, + { "FVFInfoClass", 152, 64 }, + { "TerrainTracksRenderObjClass", 128, 32 }, + { "DynamicIBAccessClass", 32, 32 }, + { "DX8IndexBufferClass", 128, 32 }, + { "SortingIndexBufferClass", 32, 32 }, + { "DX8VertexBufferClass", 128, 32 }, + { "SortingVertexBufferClass", 32, 32 }, + { "DynD3DMATERIAL8", 8192, 32 }, + { "DynamicMatrix3D", 512, 32 }, + { "MeshGeometryClass", 32, 32 }, + { "DynamicMeshModel", 32, 32 }, + { "GapFillerClass", 32, 32 }, + { "FontCharsClass", 64, 32 }, + { "ThumbnailManagerClass", 32, 32}, + { "SmudgeSet", 32, 32}, + { "Smudge", 128, 32}, + { 0, 0, 0 } +}; + +//----------------------------------------------------------------------------- +void userMemoryAdjustPoolSize(const char *poolName, Int& initialAllocationCount, Int& overflowAllocationCount) +{ + if (initialAllocationCount > 0) + return; + + for (const PoolSizeRec* p = sizes; p->name != NULL; ++p) + { + if (strcmp(p->name, poolName) == 0) + { + initialAllocationCount = p->initial; + overflowAllocationCount = p->overflow; + return; + } + } + + DEBUG_CRASH(("Initial size for pool %s not found -- you should add it to MemoryInit.cpp\n",poolName)); +} + +//----------------------------------------------------------------------------- +static Int roundUpMemBound(Int i) +{ + const int MEM_BOUND_ALIGNMENT = 4; + + if (i < MEM_BOUND_ALIGNMENT) + return MEM_BOUND_ALIGNMENT; + else + return (i + (MEM_BOUND_ALIGNMENT-1)) & ~(MEM_BOUND_ALIGNMENT-1); +} + +//----------------------------------------------------------------------------- +void userMemoryManagerInitPools() +{ + // note that we MUST use stdio stuff here, and not the normal game file system + // (with bigfile support, etc), because that relies on memory pools, which + // aren't yet initialized properly! so rely ONLY on straight stdio stuff here. + // (not even AsciiString. thanks.) + + // since we're called prior to main, the cur dir might not be what + // we expect. so do it the hard way. + char buf[_MAX_PATH]; + ::GetModuleFileName(NULL, buf, sizeof(buf)); + char* pEnd = buf + strlen(buf); + while (pEnd != buf) + { + if (*pEnd == '\\') + { + *pEnd = 0; + break; + } + --pEnd; + } + strcat(buf, "\\Data\\INI\\MemoryPools.ini"); + + FILE* fp = fopen(buf, "r"); + if (fp) + { + char poolName[256]; + int initial, overflow; + while (fgets(buf, _MAX_PATH, fp)) + { + if (buf[0] == ';') + continue; + if (sscanf(buf, "%s %d %d", poolName, &initial, &overflow ) == 3) + { + for (PoolSizeRec* p = sizes; p->name != NULL; ++p) + { + if (stricmp(p->name, poolName) == 0) + { + // currently, these must be multiples of 4. so round up. + p->initial = roundUpMemBound(initial); + p->overflow = roundUpMemBound(overflow); + break; // from for-p + } + } + } + } + fclose(fp); + } +} + diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index e7dfe842d35..c80dc09cab9 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -1,749 +1,749 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: ModuleFactory.cpp //////////////////////////////////////////////////////////////////////// -// Author: Colin Day, September 2001 -// Desc: TheModuleFactory is where we actually instance modules for objects -// and drawbles. Those modules are things such as an UpdateModule -// or DamageModule or DrawModule etc. -// -// TheModuleFactory will contain a list of ModuleTemplates, when we -// request a new module, we will look for that template in our -// list and create it -// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -#include "Common/Module.h" -#include "Common/ModuleFactory.h" -#include "Common/NameKeyGenerator.h" - -// behavior includes -#include "GameLogic/Module/AutoHealBehavior.h" -#include "GameLogic/Module/GrantStealthBehavior.h" -#include "GameLogic/Module/NeutronBlastBehavior.h" -#include "GameLogic/Module/BehaviorModule.h" -#include "GameLogic/Module/BridgeBehavior.h" -#include "GameLogic/Module/BridgeScaffoldBehavior.h" -#include "GameLogic/Module/BridgeTowerBehavior.h" -#include "GameLogic/Module/CountermeasuresBehavior.h" -#include "GameLogic/Module/DumbProjectileBehavior.h" -#include "GameLogic/Module/FreeFallProjectileBehavior.h" -#include "GameLogic/Module/InstantDeathBehavior.h" -#include "GameLogic/Module/SlowDeathBehavior.h" -#include "GameLogic/Module/HelicopterSlowDeathUpdate.h" -#include "GameLogic/Module/NeutronMissileSlowDeathUpdate.h" -#include "GameLogic/Module/CaveContain.h" -#include "GameLogic/Module/OpenContain.h" -#include "GameLogic/Module/OverchargeBehavior.h" -#include "GameLogic/Module/HealContain.h" -#include "GameLogic/Module/GarrisonContain.h" -#include "GameLogic/Module/InternetHackContain.h" -#include "GameLogic/Module/RailedTransportContain.h" -#include "GameLogic/Module/RiderChangeContain.h" -#include "GameLogic/Module/TransportContain.h" -#include "GameLogic/Module/MobNexusContain.h" -#include "GameLogic/Module/TunnelContain.h" -#include "GameLogic/Module/OverlordContain.h" -#include "GameLogic/Module/HelixContain.h" -#include "GameLogic/Module/ParachuteContain.h" -#ifdef ALLOW_SURRENDER -#include "GameLogic/Module/POWTruckBehavior.h" -#include "GameLogic/Module/PrisonBehavior.h" -#include "GameLogic/Module/PropagandaCenterBehavior.h" -#endif -#include "GameLogic/Module/PropagandaTowerBehavior.h" -#include "GameLogic/Module/BunkerBusterBehavior.h" -#include "GameLogic/Module/FireWeaponWhenDamagedBehavior.h" -#include "GameLogic/Module/FireWeaponWhenDeadBehavior.h" -#include "GameLogic/Module/DelayedUpgradeBehavior.h" -#include "GameLogic/Module/GenerateMinefieldBehavior.h" -#include "GameLogic/Module/ParkingPlaceBehavior.h" -#include "GameLogic/Module/FlightDeckBehavior.h" -#include "GameLogic/Module/PoisonedBehavior.h" -#include "GameLogic/Module/RebuildHoleBehavior.h" -#include "GameLogic/Module/SupplyWarehouseCripplingBehavior.h" -#include "GameLogic/Module/TechBuildingBehavior.h" -#include "GameLogic/Module/MinefieldBehavior.h" -#include "GameLogic/Module/BattleBusSlowDeathBehavior.h" -#include "GameLogic/Module/JetSlowDeathBehavior.h" - -// die includes -#include "GameLogic/Module/CreateCrateDie.h" -#include "GameLogic/Module/CreateObjectDie.h" -#include "GameLogic/Module/CrushDie.h" -#include "GameLogic/Module/DamDie.h" -#include "GameLogic/Module/DestroyDie.h" -#include "GameLogic/Module/EjectPilotDie.h" -#include "GameLogic/Module/FXListDie.h" -#include "GameLogic/Module/RebuildHoleExposeDie.h" -#include "GameLogic/Module/SpecialPowerCompletionDie.h" -#include "GameLogic/Module/UpgradeDie.h" -#include "GameLogic/Module/KeepObjectDie.h" - -// logic update includes -#include "GameLogic/Module/AIUpdate.h" -#include "GameLogic/Module/AnimationSteeringUpdate.h" -#include "GameLogic/Module/AssistedTargetingUpdate.h" -#include "GameLogic/Module/BaseRegenerateUpdate.h" -#include "GameLogic/Module/BoneFXUpdate.h" -#include "GameLogic/Module/ChinookAIUpdate.h" -#include "GameLogic/Module/DefaultProductionExitUpdate.h" -#include "GameLogic/Module/DeletionUpdate.h" -#include "GameLogic/Module/SmartBombTargetHomingUpdate.h" -#include "GameLogic/Module/DeliverPayloadAIUpdate.h" -#include "GameLogic/Module/DozerAIUpdate.h" -#include "GameLogic/Module/DynamicGeometryInfoUpdate.h" -#include "GameLogic/Module/DynamicShroudClearingRangeUpdate.h" -#include "GameLogic/Module/EnemyNearUpdate.h" -#include "GameLogic/Module/FireSpreadUpdate.h" -#include "GameLogic/Module/FirestormDynamicGeometryInfoUpdate.h" -#include "GameLogic/Module/FireWeaponUpdate.h" -#include "GameLogic/Module/FlammableUpdate.h" -#include "GameLogic/Module/FloatUpdate.h" -#include "GameLogic/Module/TensileFormationUpdate.h" -#include "GameLogic/Module/HackInternetAIUpdate.h" -#include "GameLogic/Module/DeployStyleAIUpdate.h" -#include "GameLogic/Module/AssaultTransportAIUpdate.h" -#include "GameLogic/Module/HeightDieUpdate.h" -#include "GameLogic/Module/HordeUpdate.h" -#include "GameLogic/Module/ScatterShotUpdate.h" -#include "GameLogic/Module/JetAIUpdate.h" -#include "GameLogic/Module/LaserUpdate.h" -#include "GameLogic/Module/PointDefenseLaserUpdate.h" -#include "GameLogic/Module/CleanupHazardUpdate.h" -#include "GameLogic/Module/AutoFindHealingUpdate.h" -#include "GameLogic/Module/CommandButtonHuntUpdate.h" -#include "GameLogic/Module/PilotFindVehicleUpdate.h" -#include "GameLogic/Module/DemoTrapUpdate.h" -#include "GameLogic/Module/ParticleUplinkCannonUpdate.h" -#include "GameLogic/Module/SpectreGunshipUpdate.h" -#include "GameLogic/Module/SpectreGunshipDeploymentUpdate.h" -#include "GameLogic/Module/BaikonurLaunchPower.h" -#include "GameLogic/Module/BattlePlanUpdate.h" -#include "GameLogic/Module/LifetimeUpdate.h" -#include "GameLogic/Module/RadiusDecalUpdate.h" -#include "GameLogic/Module/RadiusDecalBehavior.h" -#include "GameLogic/Module/AutoDepositUpdate.h" -#include "GameLogic/Module/MissileAIUpdate.h" -#include "GameLogic/Module/NeutronMissileUpdate.h" -#include "GameLogic/Module/OCLUpdate.h" -#include "GameLogic/Module/PhysicsUpdate.h" -#ifdef ALLOW_SURRENDER -#include "GameLogic/Module/POWTruckAIUpdate.h" -#endif -#include "GameLogic/Module/ProductionUpdate.h" -#include "GameLogic/Module/ProjectileStreamUpdate.h" -#include "GameLogic/Module/ProneUpdate.h" -#include "GameLogic/Module/QueueProductionExitUpdate.h" -#include "GameLogic/Module/RadarUpdate.h" -#include "GameLogic/Module/RepairDockUpdate.h" -#ifdef ALLOW_SURRENDER -#include "GameLogic/Module/PrisonDockUpdate.h" -#endif -#include "GameLogic/Module/RailedTransportDockUpdate.h" -#include "GameLogic/Module/RailedTransportAIUpdate.h" -#include "GameLogic/Module/RailroadGuideAIUpdate.h" -#include "GameLogic/Module/SlavedUpdate.h" -#include "GameLogic/Module/MobMemberSlavedUpdate.h" -#include "GameLogic/Module/SpecialAbilityUpdate.h" -#include "GameLogic/Module/MissileLauncherBuildingUpdate.h" -#include "GameLogic/Module/StealthDetectorUpdate.h" -#include "GameLogic/Module/StealthUpdate.h" -#include "GameLogic/Module/SpawnPointProductionExitUpdate.h" -#include "GameLogic/Module/SpawnBehavior.h" -#include "GameLogic/Module/SpyVisionUpdate.h" -#include "GameLogic/Module/StickyBombUpdate.h" -#include "GameLogic/Module/FireOCLAfterWeaponCooldownUpdate.h" -#include "GameLogic/Module/HijackerUpdate.h" -#include "GameLogic/Module/StructureCollapseUpdate.h" -#include "GameLogic/Module/StructureToppleUpdate.h" -#include "GameLogic/Module/SupplyCenterDockUpdate.h" -#include "GameLogic/Module/SupplyCenterProductionExitUpdate.h" -#include "GameLogic/Module/SupplyTruckAIUpdate.h" -#include "GameLogic/Module/SupplyWarehouseDockUpdate.h" -#include "GameLogic/Module/ToppleUpdate.h" -#include "GameLogic/Module/TransportAIUpdate.h" -#include "GameLogic/Module/WanderAIUpdate.h" -#include "GameLogic/Module/TeleporterAIUpdate.h" -#include "GameLogic/Module/WaveGuideUpdate.h" -#include "GameLogic/Module/WeaponBonusUpdate.h" -#include "GameLogic/Module/ArmorDamageScalarUpdate.h" -#include "GameLogic/Module/WorkerAIUpdate.h" -#include "GameLogic/Module/PowerPlantUpdate.h" -#include "GameLogic/Module/CheckpointUpdate.h" -#include "GameLogic/Module/EMPUpdate.h" - -// upgrade includes -#include "GameLogic/Module/ActiveShroudUpgrade.h" -#include "GameLogic/Module/ArmorUpgrade.h" -#include "GameLogic/Module/CommandSetUpgrade.h" -#include "GameLogic/Module/GrantScienceUpgrade.h" -#include "GameLogic/Module/PassengersFireUpgrade.h" -#include "GameLogic/Module/LocomotorSetUpgrade.h" -#include "GameLogic/Module/ObjectCreationUpgrade.h" -#include "GameLogic/Module/RadarUpgrade.h" -#include "GameLogic/Module/PowerPlantUpgrade.h" -#include "GameLogic/Module/ReplaceObjectUpgrade.h" -#include "GameLogic/Module/ModelConditionUpgrade.h" -#include "GameLogic/Module/StatusBitsUpgrade.h" -#include "GameLogic/Module/SubObjectsUpgrade.h" -#include "GameLogic/Module/StealthUpgrade.h" -#include "GameLogic/Module/UnpauseSpecialPowerUpgrade.h" -#include "GameLogic/Module/WeaponBonusUpgrade.h" -#include "GameLogic/Module/WeaponSetUpgrade.h" -#include "GameLogic/Module/WeaponBonusUpgrade.h" -#include "GameLogic/Module/CostModifierUpgrade.h" -#include "GameLogic/Module/ProductionTimeModifierUpgrade.h" -#include "GameLogic/Module/UnitProductionBonusUpgrade.h" -#include "GameLogic/Module/ExperienceScalarUpgrade.h" -#include "GameLogic/Module/MaxHealthUpgrade.h" - -// create includes -#include "GameLogic/Module/LockWeaponCreate.h" -#include "GameLogic/Module/SupplyCenterCreate.h" -#include "GameLogic/Module/SupplyWarehouseCreate.h" -#include "GameLogic/Module/GrantUpgradeCreate.h" -#include "GameLogic/Module/PreorderCreate.h" -#include "GameLogic/Module/SpecialPowerCreate.h" -#include "GameLogic/Module/VeterancyGainCreate.h" - -// damage includes -#include "GameLogic/Module/BoneFXDamage.h" -#include "GameLogic/Module/TransitionDamageFX.h" - -// collide includes -#include "GameLogic/Module/FireWeaponCollide.h" -#include "GameLogic/Module/SquishCollide.h" - -#include "GameLogic/Module/ConvertToCarBombCrateCollide.h" -#include "GameLogic/Module/ConvertToHijackedVehicleCrateCollide.h" -#include "GameLogic/Module/HealCrateCollide.h" -#include "GameLogic/Module/MoneyCrateCollide.h" -#include "GameLogic/Module/SabotageCommandCenterCrateCollide.h" -#include "GameLogic/Module/SabotageFakeBuildingCrateCollide.h" -#include "GameLogic/Module/SabotageInternetCenterCrateCollide.h" -#include "GameLogic/Module/SabotageMilitaryFactoryCrateCollide.h" -#include "GameLogic/Module/SabotagePowerPlantCrateCollide.h" -#include "GameLogic/Module/SabotageSuperweaponCrateCollide.h" -#include "GameLogic/Module/SabotageSupplyCenterCrateCollide.h" -#include "GameLogic/Module/SabotageSupplyDropzoneCrateCollide.h" -#include "GameLogic/Module/SalvageCrateCollide.h" -#include "GameLogic/Module/ShroudCrateCollide.h" -#include "GameLogic/Module/UnitCrateCollide.h" -#include "GameLogic/Module/VeterancyCrateCollide.h" - -// body includes -#include "GameLogic/Module/InactiveBody.h" -#include "GameLogic/Module/ActiveBody.h" -#include "GameLogic/Module/HighlanderBody.h" -#include "GameLogic/Module/ImmortalBody.h" -#include "GameLogic/Module/StructureBody.h" -#include "GameLogic/Module/HiveStructureBody.h" -#include "GameLogic/Module/UndeadBody.h" - -// contain includes -// (none) - -// special power modules -#include "GameLogic/Module/CashHackSpecialPower.h" -#include "GameLogic/Module/DefectorSpecialPower.h" -#ifdef ALLOW_DEMORALIZE -#include "GameLogic/Module/DemoralizeSpecialPower.h" -#endif -#include "GameLogic/Module/OCLSpecialPower.h" -#include "GameLogic/Module/SpecialAbility.h" -#include "GameLogic/Module/SpyVisionSpecialPower.h" -#include "GameLogic/Module/UpgradeSpecialPower.h" -#include "GameLogic/Module/CashBountyPower.h" -#include "GameLogic/Module/CleanupAreaPower.h" -#include "GameLogic/Module/FireWeaponPower.h" - -// destroy includes -// (none) - -// client update includes -#include "GameClient/Module/AnimatedParticleSysBoneClientUpdate.h" -#include "GameClient/Module/SwayClientUpdate.h" -#include "GameClient/Module/BeaconClientUpdate.h" - -// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// -ModuleFactory *TheModuleFactory = NULL; ///< the module factory singleton - -// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// - -#ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -ModuleFactory::ModuleFactory( void ) -{ - m_moduleTemplateMap.clear(); - m_moduleDataList.clear(); - -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -ModuleFactory::~ModuleFactory( void ) -{ - m_moduleTemplateMap.clear(); - - for (ModuleDataList::iterator i = m_moduleDataList.begin(); i != m_moduleDataList.end(); ++i) - { - const ModuleData* data = *i; - delete data; - } - m_moduleDataList.clear(); - -} - -//------------------------------------------------------------------------------------------------- -/** Initialize the module factory. Any class that needs to be attached - * to objects or drawables as modules needs to add a template - * for that class here */ -//------------------------------------------------------------------------------------------------- -void ModuleFactory::init( void ) -{ - - // behavior modules - addModule( AutoHealBehavior ); - addModule( GrantStealthBehavior ); - addModule( NeutronBlastBehavior ); - addModule( BridgeBehavior ); - addModule( BridgeScaffoldBehavior ); - addModule( BridgeTowerBehavior ); - addModule( CountermeasuresBehavior ); - addModule( DumbProjectileBehavior ); - addModule( FreeFallProjectileBehavior ); - addModule( PhysicsBehavior ); - addModule( InstantDeathBehavior ); - addModule( SlowDeathBehavior ); - addModule( HelicopterSlowDeathBehavior ); - addModule( NeutronMissileSlowDeathBehavior ); - addModule( CaveContain ); - addModule( OpenContain ); - addModule( OverchargeBehavior ); - addModule( HealContain ); - addModule( GarrisonContain ); - addModule( InternetHackContain ); - addModule( TransportContain ); - addModule( RiderChangeContain ); - addModule( RailedTransportContain ); - addModule( MobNexusContain ); - addModule( TunnelContain ); - addModule( OverlordContain ); - addModule( HelixContain ); - addModule( ParachuteContain ); -#ifdef ALLOW_SURRENDER - addModule( POWTruckBehavior ); - addModule( PrisonBehavior ); - addModule( PropagandaCenterBehavior ); -#endif - addModule( PropagandaTowerBehavior ); - addModule( BunkerBusterBehavior ); - addModule( FireWeaponWhenDamagedBehavior ); - addModule( FireWeaponWhenDeadBehavior ); - addModule( DelayedUpgradeBehavior ); - addModule( GenerateMinefieldBehavior ); - addModule( ParkingPlaceBehavior ); - addModule( FlightDeckBehavior ); - addModule( PoisonedBehavior ); - addModule( RebuildHoleBehavior ); - addModule( SupplyWarehouseCripplingBehavior ); - addModule( TechBuildingBehavior ); - addModule( MinefieldBehavior ); - addModule( BattleBusSlowDeathBehavior ); - addModule( JetSlowDeathBehavior ); - addModule( RailroadBehavior ); - addModule( SpawnBehavior ); - - // die modules - addModule( DestroyDie ); - addModule( FXListDie ); - addModule( CrushDie ); - addModule( DamDie ); - addModule( CreateCrateDie ); - addModule( CreateObjectDie ); - addModule( EjectPilotDie ); - addModule( SpecialPowerCompletionDie ); - addModule( RebuildHoleExposeDie ); - addModule( UpgradeDie ); - addModule( KeepObjectDie ); - - // update modules - addModule( AssistedTargetingUpdate ); - addModule( AutoFindHealingUpdate ); - addModule( BaseRegenerateUpdate ); - addModule( StealthDetectorUpdate ); - addModule( StealthUpdate ); - addModule( DeletionUpdate ); - addModule( SmartBombTargetHomingUpdate ); - addModule( DynamicShroudClearingRangeUpdate ); - addModule( DeployStyleAIUpdate ); - addModule( AssaultTransportAIUpdate ); - addModule( HordeUpdate ); - addModule( ToppleUpdate ); - addModule( EnemyNearUpdate ); - addModule( LifetimeUpdate ); - addModule( RadiusDecalUpdate ); - addModule( RadiusDecalBehavior ); - addModule( EMPUpdate ); - addModule( LeafletDropBehavior ); - addModule( AutoDepositUpdate ); - addModule( WeaponBonusUpdate ); - addModule( ArmorDamageScalarUpdate ); - addModule( MissileAIUpdate ); - addModule( NeutronMissileUpdate ); - addModule( FireSpreadUpdate ); - addModule( FireWeaponUpdate ); - addModule( FlammableUpdate ); - addModule( FloatUpdate ); - addModule( TensileFormationUpdate ); - addModule( HeightDieUpdate ); - addModule( ScatterShotUpdate ); - addModule( ChinookAIUpdate ); - addModule( JetAIUpdate ); - addModule( AIUpdateInterface ); - addModule( SupplyTruckAIUpdate ); - addModule( DeliverPayloadAIUpdate ); - addModule( HackInternetAIUpdate ); - addModule( DynamicGeometryInfoUpdate ); - addModule( FirestormDynamicGeometryInfoUpdate ); - addModule( LaserUpdate ); - addModule( PointDefenseLaserUpdate ); - addModule( CleanupHazardUpdate ); - addModule( CommandButtonHuntUpdate ); - addModule( PilotFindVehicleUpdate ); - addModule( DemoTrapUpdate ); - addModule( ParticleUplinkCannonUpdate ); - addModule( SpectreGunshipUpdate ); - addModule( SpectreGunshipDeploymentUpdate ); - addModule( BaikonurLaunchPower ); - addModule( BattlePlanUpdate ); - addModule( ProjectileStreamUpdate ); - addModule( QueueProductionExitUpdate ); - addModule( RepairDockUpdate ); -#ifdef ALLOW_SURRENDER - addModule( PrisonDockUpdate ); -#endif - addModule( RailedTransportDockUpdate ); - addModule( DefaultProductionExitUpdate ); - addModule( SpawnPointProductionExitUpdate ); - addModule( SpyVisionUpdate ); - addModule( SlavedUpdate ); - addModule( MobMemberSlavedUpdate ); - addModule( OCLUpdate ); - addModule( SpecialAbilityUpdate ); - addModule( MissileLauncherBuildingUpdate ); - addModule( SupplyCenterProductionExitUpdate ); - addModule( SupplyCenterDockUpdate ); - addModule( SupplyWarehouseDockUpdate ); - addModule( DozerAIUpdate ); -#ifdef ALLOW_SURRENDER - addModule( POWTruckAIUpdate ); -#endif - addModule( RailedTransportAIUpdate ); - addModule( ProductionUpdate ); - addModule( ProneUpdate ); - addModule( StickyBombUpdate ); - addModule( FireOCLAfterWeaponCooldownUpdate ); - addModule( HijackerUpdate ); - addModule( StructureToppleUpdate ); - addModule( StructureCollapseUpdate ); - addModule( BoneFXUpdate ); - addModule( RadarUpdate ); - addModule( AnimationSteeringUpdate ); - addModule( TransportAIUpdate ); - addModule( WanderAIUpdate ); - addModule( TeleporterAIUpdate ); - addModule( WaveGuideUpdate ); - addModule( WorkerAIUpdate ); - addModule( PowerPlantUpdate ); - addModule( CheckpointUpdate ); - - // upgrade modules - addModule( CostModifierUpgrade ); - addModule( ProductionTimeModifierUpgrade ); - addModule( UnitProductionBonusUpgrade ); - addModule( ActiveShroudUpgrade ); - addModule( ArmorUpgrade ); - addModule( CommandSetUpgrade ); - addModule( GrantScienceUpgrade ); - addModule( PassengersFireUpgrade ); - addModule( StatusBitsUpgrade ); - addModule( SubObjectsUpgrade ); - addModule( StealthUpgrade ); - addModule( RadarUpgrade ); - addModule( PowerPlantUpgrade ); - addModule( LocomotorSetUpgrade ); - addModule( ObjectCreationUpgrade ); - addModule( ReplaceObjectUpgrade ); - addModule( ModelConditionUpgrade ); - addModule( UnpauseSpecialPowerUpgrade ); - addModule( WeaponBonusUpgrade ); - addModule( WeaponSetUpgrade ); - addModule( WeaponBonusUpgrade ); - addModule( ExperienceScalarUpgrade ); - addModule( MaxHealthUpgrade ); - - // create modules - addModule( LockWeaponCreate ); - addModule( PreorderCreate ); - addModule( SupplyCenterCreate ); - addModule( SupplyWarehouseCreate ); - addModule( SpecialPowerCreate ); - addModule( GrantUpgradeCreate ); - addModule( VeterancyGainCreate ); - - // damage modules - addModule( BoneFXDamage ); - addModule( TransitionDamageFX ); - - // collide modules - addModule( FireWeaponCollide ); - addModule( SquishCollide ); - - addModule( HealCrateCollide ); - addModule( MoneyCrateCollide ); - addModule( ShroudCrateCollide ); - addModule( UnitCrateCollide ); - addModule( VeterancyCrateCollide ); - addModule( ConvertToCarBombCrateCollide ); - addModule( ConvertToHijackedVehicleCrateCollide ); - addModule( SabotageCommandCenterCrateCollide ); - addModule( SabotageFakeBuildingCrateCollide ); - addModule( SabotageInternetCenterCrateCollide ); - addModule( SabotageMilitaryFactoryCrateCollide ); - addModule( SabotagePowerPlantCrateCollide ); - addModule( SabotageSuperweaponCrateCollide ); - addModule( SabotageSupplyCenterCrateCollide ); - addModule( SabotageSupplyDropzoneCrateCollide ); - addModule( SalvageCrateCollide ); - - // body modules - addModule( InactiveBody ); - addModule( ActiveBody ); - addModule( HighlanderBody ); - addModule( ImmortalBody ); - addModule( StructureBody ); - addModule( HiveStructureBody ); - addModule( UndeadBody ); - - // contain modules - // (none) - - // special power modules - addModule( CashHackSpecialPower ); - addModule( DefectorSpecialPower ); -#ifdef ALLOW_DEMORALIZE - addModule( DemoralizeSpecialPower ); -#endif - addModule( OCLSpecialPower ); - addModule( FireWeaponPower ); - addModule( SpecialAbility ); - addModule( SpyVisionSpecialPower ); - addModule( UpgradeSpecialPower ); - addModule( CashBountyPower ); - addModule( CleanupAreaPower ); - - // destroy modules - // (none) - - // client update modules - addModule( AnimatedParticleSysBoneClientUpdate ); - addModule( SwayClientUpdate ); - addModule( BeaconClientUpdate ); - -} // end init - -//------------------------------------------------------------------------------------------------- -Int ModuleFactory::findModuleInterfaceMask(const AsciiString& name, ModuleType type) -{ - if (name.isEmpty()) - return 0; - - const ModuleTemplate* moduleTemplate = findModuleTemplate(name, type); - if (moduleTemplate) - { - return moduleTemplate->m_whichInterfaces; - } - - return 0; -} - -//------------------------------------------------------------------------------------------------- -ModuleData* ModuleFactory::newModuleDataFromINI(INI* ini, const AsciiString& name, ModuleType type, - const AsciiString& moduleTag) -{ - if (name.isEmpty()) - return NULL; - - const ModuleTemplate* moduleTemplate = findModuleTemplate(name, type); - if (moduleTemplate) - { - ModuleData* md = (*moduleTemplate->m_createDataProc)(ini); - md->setModuleTagNameKey( NAMEKEY( moduleTag ) ); - m_moduleDataList.push_back(md); - return md; - } - - return NULL; -} - -// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------------- -/*static*/ NameKeyType ModuleFactory::makeDecoratedNameKey(const AsciiString& name, ModuleType type) -{ - char tmp[256]; - tmp[0] = '0' + (int)type; - strcpy(&tmp[1], name.str()); - return TheNameKeyGenerator->nameToKey(tmp); -} - -//------------------------------------------------------------------------------------------------- -const ModuleFactory::ModuleTemplate* ModuleFactory::findModuleTemplate(const AsciiString& name, ModuleType type) -{ - NameKeyType namekey = makeDecoratedNameKey(name, type); - - ModuleTemplateMap::const_iterator it = m_moduleTemplateMap.find(namekey); - if (it == m_moduleTemplateMap.end()) - { - DEBUG_CRASH(( "Module name '%s' not found\n", name.str() )); - return NULL; - } - else - { - return &(*it).second; - } -} - -//------------------------------------------------------------------------------------------------- -/** Allocate a new acton class istance given the name */ -//------------------------------------------------------------------------------------------------- -Module *ModuleFactory::newModule( Thing *thing, const AsciiString& name, const ModuleData* moduleData, ModuleType type ) -{ - // sanity - if( name.isEmpty() ) - { - DEBUG_CRASH(("attempting to create module with empty name\n")); - return NULL; - } - const ModuleTemplate* mt = findModuleTemplate(name, type); - if (mt) - { - Module* mod = (*mt->m_createProc)( thing, moduleData ); - -#ifdef DEBUG_CRASHING - if (type == MODULETYPE_BEHAVIOR) - { - BehaviorModule* bm = (BehaviorModule*)mod; - - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_BODY)) != 0) == (bm->getBody() != NULL), - ("getInterfaceMask bad for MODULE_BODY (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_COLLIDE)) != 0) == (bm->getCollide() != NULL), - ("getInterfaceMask bad for MODULE_COLLIDE (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_CONTAIN)) != 0) == (bm->getContain() != NULL), - ("getInterfaceMask bad for MODULE_CONTAIN (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_CREATE)) != 0) == (bm->getCreate() != NULL), - ("getInterfaceMask bad for MODULE_CREATE (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_DAMAGE)) != 0) == (bm->getDamage() != NULL), - ("getInterfaceMask bad for MODULE_DAMAGE (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_DESTROY)) != 0) == (bm->getDestroy() != NULL), - ("getInterfaceMask bad for MODULE_DESTROY (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_DIE)) != 0) == (bm->getDie() != NULL), - ("getInterfaceMask bad for MODULE_DIE (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_SPECIAL_POWER)) != 0) == (bm->getSpecialPower() != NULL), - ("getInterfaceMask bad for MODULE_SPECIAL_POWER (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_UPDATE)) != 0) == (bm->getUpdate() != NULL), - ("getInterfaceMask bad for MODULE_UPDATE (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_UPGRADE)) != 0) == (bm->getUpgrade() != NULL), - ("getInterfaceMask bad for MODULE_UPGRADE (%s)\n",name.str())); - } -#endif - - return mod; - } - - return NULL; - -} // end newModule - -//------------------------------------------------------------------------------------------------- -/** Add a module template to our list of templates */ -//------------------------------------------------------------------------------------------------- -void ModuleFactory::addModuleInternal( NewModuleProc proc, NewModuleDataProc dataproc, ModuleType type, const AsciiString& name, Int whichIntf ) -{ - NameKeyType namekey = makeDecoratedNameKey(name, type); - ModuleTemplate& mtm = m_moduleTemplateMap[namekey]; // this creates it if it does not exist already - mtm.m_createProc = proc; - mtm.m_createDataProc = dataproc; - mtm.m_whichInterfaces = whichIntf; -} - -//------------------------------------------------------------------------------------------------- -void ModuleFactory::crc( Xfer *xfer ) -{ - for (ModuleDataList::iterator mdIt = m_moduleDataList.begin(); mdIt != m_moduleDataList.end(); ++mdIt) - { - ((ModuleData *)(*mdIt))->crc(xfer); - } -} - -//------------------------------------------------------------------------------------------------- -void ModuleFactory::xfer( Xfer *xfer ) -{ - - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - for (ModuleDataList::iterator mdIt = m_moduleDataList.begin(); mdIt != m_moduleDataList.end(); ++mdIt) - { - ((ModuleData *)(*mdIt))->xfer(xfer); - } -} - -//------------------------------------------------------------------------------------------------- -void ModuleFactory::loadPostProcess( void ) -{ -} +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: ModuleFactory.cpp //////////////////////////////////////////////////////////////////////// +// Author: Colin Day, September 2001 +// Desc: TheModuleFactory is where we actually instance modules for objects +// and drawbles. Those modules are things such as an UpdateModule +// or DamageModule or DrawModule etc. +// +// TheModuleFactory will contain a list of ModuleTemplates, when we +// request a new module, we will look for that template in our +// list and create it +// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#include "Common/Module.h" +#include "Common/ModuleFactory.h" +#include "Common/NameKeyGenerator.h" + +// behavior includes +#include "GameLogic/Module/AutoHealBehavior.h" +#include "GameLogic/Module/GrantStealthBehavior.h" +#include "GameLogic/Module/NeutronBlastBehavior.h" +#include "GameLogic/Module/BehaviorModule.h" +#include "GameLogic/Module/BridgeBehavior.h" +#include "GameLogic/Module/BridgeScaffoldBehavior.h" +#include "GameLogic/Module/BridgeTowerBehavior.h" +#include "GameLogic/Module/CountermeasuresBehavior.h" +#include "GameLogic/Module/DumbProjectileBehavior.h" +#include "GameLogic/Module/FreeFallProjectileBehavior.h" +#include "GameLogic/Module/InstantDeathBehavior.h" +#include "GameLogic/Module/SlowDeathBehavior.h" +#include "GameLogic/Module/HelicopterSlowDeathUpdate.h" +#include "GameLogic/Module/NeutronMissileSlowDeathUpdate.h" +#include "GameLogic/Module/CaveContain.h" +#include "GameLogic/Module/OpenContain.h" +#include "GameLogic/Module/OverchargeBehavior.h" +#include "GameLogic/Module/HealContain.h" +#include "GameLogic/Module/GarrisonContain.h" +#include "GameLogic/Module/InternetHackContain.h" +#include "GameLogic/Module/RailedTransportContain.h" +#include "GameLogic/Module/RiderChangeContain.h" +#include "GameLogic/Module/TransportContain.h" +#include "GameLogic/Module/MobNexusContain.h" +#include "GameLogic/Module/TunnelContain.h" +#include "GameLogic/Module/OverlordContain.h" +#include "GameLogic/Module/HelixContain.h" +#include "GameLogic/Module/ParachuteContain.h" +#ifdef ALLOW_SURRENDER +#include "GameLogic/Module/POWTruckBehavior.h" +#include "GameLogic/Module/PrisonBehavior.h" +#include "GameLogic/Module/PropagandaCenterBehavior.h" +#endif +#include "GameLogic/Module/PropagandaTowerBehavior.h" +#include "GameLogic/Module/BunkerBusterBehavior.h" +#include "GameLogic/Module/FireWeaponWhenDamagedBehavior.h" +#include "GameLogic/Module/FireWeaponWhenDeadBehavior.h" +#include "GameLogic/Module/DelayedUpgradeBehavior.h" +#include "GameLogic/Module/GenerateMinefieldBehavior.h" +#include "GameLogic/Module/ParkingPlaceBehavior.h" +#include "GameLogic/Module/FlightDeckBehavior.h" +#include "GameLogic/Module/PoisonedBehavior.h" +#include "GameLogic/Module/RebuildHoleBehavior.h" +#include "GameLogic/Module/SupplyWarehouseCripplingBehavior.h" +#include "GameLogic/Module/TechBuildingBehavior.h" +#include "GameLogic/Module/MinefieldBehavior.h" +#include "GameLogic/Module/BattleBusSlowDeathBehavior.h" +#include "GameLogic/Module/JetSlowDeathBehavior.h" + +// die includes +#include "GameLogic/Module/CreateCrateDie.h" +#include "GameLogic/Module/CreateObjectDie.h" +#include "GameLogic/Module/CrushDie.h" +#include "GameLogic/Module/DamDie.h" +#include "GameLogic/Module/DestroyDie.h" +#include "GameLogic/Module/EjectPilotDie.h" +#include "GameLogic/Module/FXListDie.h" +#include "GameLogic/Module/RebuildHoleExposeDie.h" +#include "GameLogic/Module/SpecialPowerCompletionDie.h" +#include "GameLogic/Module/UpgradeDie.h" +#include "GameLogic/Module/KeepObjectDie.h" + +// logic update includes +#include "GameLogic/Module/AIUpdate.h" +#include "GameLogic/Module/AnimationSteeringUpdate.h" +#include "GameLogic/Module/AssistedTargetingUpdate.h" +#include "GameLogic/Module/BaseRegenerateUpdate.h" +#include "GameLogic/Module/BoneFXUpdate.h" +#include "GameLogic/Module/ChinookAIUpdate.h" +#include "GameLogic/Module/DefaultProductionExitUpdate.h" +#include "GameLogic/Module/DeletionUpdate.h" +#include "GameLogic/Module/SmartBombTargetHomingUpdate.h" +#include "GameLogic/Module/DeliverPayloadAIUpdate.h" +#include "GameLogic/Module/DozerAIUpdate.h" +#include "GameLogic/Module/DynamicGeometryInfoUpdate.h" +#include "GameLogic/Module/DynamicShroudClearingRangeUpdate.h" +#include "GameLogic/Module/EnemyNearUpdate.h" +#include "GameLogic/Module/FireSpreadUpdate.h" +#include "GameLogic/Module/FirestormDynamicGeometryInfoUpdate.h" +#include "GameLogic/Module/FireWeaponUpdate.h" +#include "GameLogic/Module/FlammableUpdate.h" +#include "GameLogic/Module/FloatUpdate.h" +#include "GameLogic/Module/TensileFormationUpdate.h" +#include "GameLogic/Module/HackInternetAIUpdate.h" +#include "GameLogic/Module/DeployStyleAIUpdate.h" +#include "GameLogic/Module/AssaultTransportAIUpdate.h" +#include "GameLogic/Module/HeightDieUpdate.h" +#include "GameLogic/Module/HordeUpdate.h" +#include "GameLogic/Module/ScatterShotUpdate.h" +#include "GameLogic/Module/JetAIUpdate.h" +#include "GameLogic/Module/LaserUpdate.h" +#include "GameLogic/Module/PointDefenseLaserUpdate.h" +#include "GameLogic/Module/CleanupHazardUpdate.h" +#include "GameLogic/Module/AutoFindHealingUpdate.h" +#include "GameLogic/Module/CommandButtonHuntUpdate.h" +#include "GameLogic/Module/PilotFindVehicleUpdate.h" +#include "GameLogic/Module/DemoTrapUpdate.h" +#include "GameLogic/Module/ParticleUplinkCannonUpdate.h" +#include "GameLogic/Module/SpectreGunshipUpdate.h" +#include "GameLogic/Module/SpectreGunshipDeploymentUpdate.h" +#include "GameLogic/Module/BaikonurLaunchPower.h" +#include "GameLogic/Module/BattlePlanUpdate.h" +#include "GameLogic/Module/LifetimeUpdate.h" +#include "GameLogic/Module/RadiusDecalUpdate.h" +#include "GameLogic/Module/RadiusDecalBehavior.h" +#include "GameLogic/Module/AutoDepositUpdate.h" +#include "GameLogic/Module/MissileAIUpdate.h" +#include "GameLogic/Module/NeutronMissileUpdate.h" +#include "GameLogic/Module/OCLUpdate.h" +#include "GameLogic/Module/PhysicsUpdate.h" +#ifdef ALLOW_SURRENDER +#include "GameLogic/Module/POWTruckAIUpdate.h" +#endif +#include "GameLogic/Module/ProductionUpdate.h" +#include "GameLogic/Module/ProjectileStreamUpdate.h" +#include "GameLogic/Module/ProneUpdate.h" +#include "GameLogic/Module/QueueProductionExitUpdate.h" +#include "GameLogic/Module/RadarUpdate.h" +#include "GameLogic/Module/RepairDockUpdate.h" +#ifdef ALLOW_SURRENDER +#include "GameLogic/Module/PrisonDockUpdate.h" +#endif +#include "GameLogic/Module/RailedTransportDockUpdate.h" +#include "GameLogic/Module/RailedTransportAIUpdate.h" +#include "GameLogic/Module/RailroadGuideAIUpdate.h" +#include "GameLogic/Module/SlavedUpdate.h" +#include "GameLogic/Module/MobMemberSlavedUpdate.h" +#include "GameLogic/Module/SpecialAbilityUpdate.h" +#include "GameLogic/Module/MissileLauncherBuildingUpdate.h" +#include "GameLogic/Module/StealthDetectorUpdate.h" +#include "GameLogic/Module/StealthUpdate.h" +#include "GameLogic/Module/SpawnPointProductionExitUpdate.h" +#include "GameLogic/Module/SpawnBehavior.h" +#include "GameLogic/Module/SpyVisionUpdate.h" +#include "GameLogic/Module/StickyBombUpdate.h" +#include "GameLogic/Module/FireOCLAfterWeaponCooldownUpdate.h" +#include "GameLogic/Module/HijackerUpdate.h" +#include "GameLogic/Module/StructureCollapseUpdate.h" +#include "GameLogic/Module/StructureToppleUpdate.h" +#include "GameLogic/Module/SupplyCenterDockUpdate.h" +#include "GameLogic/Module/SupplyCenterProductionExitUpdate.h" +#include "GameLogic/Module/SupplyTruckAIUpdate.h" +#include "GameLogic/Module/SupplyWarehouseDockUpdate.h" +#include "GameLogic/Module/ToppleUpdate.h" +#include "GameLogic/Module/TransportAIUpdate.h" +#include "GameLogic/Module/WanderAIUpdate.h" +#include "GameLogic/Module/TeleporterAIUpdate.h" +#include "GameLogic/Module/WaveGuideUpdate.h" +#include "GameLogic/Module/WeaponBonusUpdate.h" +#include "GameLogic/Module/ArmorDamageScalarUpdate.h" +#include "GameLogic/Module/WorkerAIUpdate.h" +#include "GameLogic/Module/PowerPlantUpdate.h" +#include "GameLogic/Module/CheckpointUpdate.h" +#include "GameLogic/Module/EMPUpdate.h" + +// upgrade includes +#include "GameLogic/Module/ActiveShroudUpgrade.h" +#include "GameLogic/Module/ArmorUpgrade.h" +#include "GameLogic/Module/CommandSetUpgrade.h" +#include "GameLogic/Module/GrantScienceUpgrade.h" +#include "GameLogic/Module/PassengersFireUpgrade.h" +#include "GameLogic/Module/LocomotorSetUpgrade.h" +#include "GameLogic/Module/ObjectCreationUpgrade.h" +#include "GameLogic/Module/RadarUpgrade.h" +#include "GameLogic/Module/PowerPlantUpgrade.h" +#include "GameLogic/Module/ReplaceObjectUpgrade.h" +#include "GameLogic/Module/ModelConditionUpgrade.h" +#include "GameLogic/Module/StatusBitsUpgrade.h" +#include "GameLogic/Module/SubObjectsUpgrade.h" +#include "GameLogic/Module/StealthUpgrade.h" +#include "GameLogic/Module/UnpauseSpecialPowerUpgrade.h" +#include "GameLogic/Module/WeaponBonusUpgrade.h" +#include "GameLogic/Module/WeaponSetUpgrade.h" +#include "GameLogic/Module/WeaponBonusUpgrade.h" +#include "GameLogic/Module/CostModifierUpgrade.h" +#include "GameLogic/Module/ProductionTimeModifierUpgrade.h" +#include "GameLogic/Module/UnitProductionBonusUpgrade.h" +#include "GameLogic/Module/ExperienceScalarUpgrade.h" +#include "GameLogic/Module/MaxHealthUpgrade.h" + +// create includes +#include "GameLogic/Module/LockWeaponCreate.h" +#include "GameLogic/Module/SupplyCenterCreate.h" +#include "GameLogic/Module/SupplyWarehouseCreate.h" +#include "GameLogic/Module/GrantUpgradeCreate.h" +#include "GameLogic/Module/PreorderCreate.h" +#include "GameLogic/Module/SpecialPowerCreate.h" +#include "GameLogic/Module/VeterancyGainCreate.h" + +// damage includes +#include "GameLogic/Module/BoneFXDamage.h" +#include "GameLogic/Module/TransitionDamageFX.h" + +// collide includes +#include "GameLogic/Module/FireWeaponCollide.h" +#include "GameLogic/Module/SquishCollide.h" + +#include "GameLogic/Module/ConvertToCarBombCrateCollide.h" +#include "GameLogic/Module/ConvertToHijackedVehicleCrateCollide.h" +#include "GameLogic/Module/HealCrateCollide.h" +#include "GameLogic/Module/MoneyCrateCollide.h" +#include "GameLogic/Module/SabotageCommandCenterCrateCollide.h" +#include "GameLogic/Module/SabotageFakeBuildingCrateCollide.h" +#include "GameLogic/Module/SabotageInternetCenterCrateCollide.h" +#include "GameLogic/Module/SabotageMilitaryFactoryCrateCollide.h" +#include "GameLogic/Module/SabotagePowerPlantCrateCollide.h" +#include "GameLogic/Module/SabotageSuperweaponCrateCollide.h" +#include "GameLogic/Module/SabotageSupplyCenterCrateCollide.h" +#include "GameLogic/Module/SabotageSupplyDropzoneCrateCollide.h" +#include "GameLogic/Module/SalvageCrateCollide.h" +#include "GameLogic/Module/ShroudCrateCollide.h" +#include "GameLogic/Module/UnitCrateCollide.h" +#include "GameLogic/Module/VeterancyCrateCollide.h" + +// body includes +#include "GameLogic/Module/InactiveBody.h" +#include "GameLogic/Module/ActiveBody.h" +#include "GameLogic/Module/HighlanderBody.h" +#include "GameLogic/Module/ImmortalBody.h" +#include "GameLogic/Module/StructureBody.h" +#include "GameLogic/Module/HiveStructureBody.h" +#include "GameLogic/Module/UndeadBody.h" + +// contain includes +// (none) + +// special power modules +#include "GameLogic/Module/CashHackSpecialPower.h" +#include "GameLogic/Module/DefectorSpecialPower.h" +#ifdef ALLOW_DEMORALIZE +#include "GameLogic/Module/DemoralizeSpecialPower.h" +#endif +#include "GameLogic/Module/OCLSpecialPower.h" +#include "GameLogic/Module/SpecialAbility.h" +#include "GameLogic/Module/SpyVisionSpecialPower.h" +#include "GameLogic/Module/UpgradeSpecialPower.h" +#include "GameLogic/Module/CashBountyPower.h" +#include "GameLogic/Module/CleanupAreaPower.h" +#include "GameLogic/Module/FireWeaponPower.h" + +// destroy includes +// (none) + +// client update includes +#include "GameClient/Module/AnimatedParticleSysBoneClientUpdate.h" +#include "GameClient/Module/SwayClientUpdate.h" +#include "GameClient/Module/BeaconClientUpdate.h" + +// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// +ModuleFactory *TheModuleFactory = NULL; ///< the module factory singleton + +// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// + +#ifdef RTS_INTERNAL +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ModuleFactory::ModuleFactory( void ) +{ + m_moduleTemplateMap.clear(); + m_moduleDataList.clear(); + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ModuleFactory::~ModuleFactory( void ) +{ + m_moduleTemplateMap.clear(); + + for (ModuleDataList::iterator i = m_moduleDataList.begin(); i != m_moduleDataList.end(); ++i) + { + const ModuleData* data = *i; + delete data; + } + m_moduleDataList.clear(); + +} + +//------------------------------------------------------------------------------------------------- +/** Initialize the module factory. Any class that needs to be attached + * to objects or drawables as modules needs to add a template + * for that class here */ +//------------------------------------------------------------------------------------------------- +void ModuleFactory::init( void ) +{ + + // behavior modules + addModule( AutoHealBehavior ); + addModule( GrantStealthBehavior ); + addModule( NeutronBlastBehavior ); + addModule( BridgeBehavior ); + addModule( BridgeScaffoldBehavior ); + addModule( BridgeTowerBehavior ); + addModule( CountermeasuresBehavior ); + addModule( DumbProjectileBehavior ); + addModule( FreeFallProjectileBehavior ); + addModule( PhysicsBehavior ); + addModule( InstantDeathBehavior ); + addModule( SlowDeathBehavior ); + addModule( HelicopterSlowDeathBehavior ); + addModule( NeutronMissileSlowDeathBehavior ); + addModule( CaveContain ); + addModule( OpenContain ); + addModule( OverchargeBehavior ); + addModule( HealContain ); + addModule( GarrisonContain ); + addModule( InternetHackContain ); + addModule( TransportContain ); + addModule( RiderChangeContain ); + addModule( RailedTransportContain ); + addModule( MobNexusContain ); + addModule( TunnelContain ); + addModule( OverlordContain ); + addModule( HelixContain ); + addModule( ParachuteContain ); +#ifdef ALLOW_SURRENDER + addModule( POWTruckBehavior ); + addModule( PrisonBehavior ); + addModule( PropagandaCenterBehavior ); +#endif + addModule( PropagandaTowerBehavior ); + addModule( BunkerBusterBehavior ); + addModule( FireWeaponWhenDamagedBehavior ); + addModule( FireWeaponWhenDeadBehavior ); + addModule( DelayedUpgradeBehavior ); + addModule( GenerateMinefieldBehavior ); + addModule( ParkingPlaceBehavior ); + addModule( FlightDeckBehavior ); + addModule( PoisonedBehavior ); + addModule( RebuildHoleBehavior ); + addModule( SupplyWarehouseCripplingBehavior ); + addModule( TechBuildingBehavior ); + addModule( MinefieldBehavior ); + addModule( BattleBusSlowDeathBehavior ); + addModule( JetSlowDeathBehavior ); + addModule( RailroadBehavior ); + addModule( SpawnBehavior ); + + // die modules + addModule( DestroyDie ); + addModule( FXListDie ); + addModule( CrushDie ); + addModule( DamDie ); + addModule( CreateCrateDie ); + addModule( CreateObjectDie ); + addModule( EjectPilotDie ); + addModule( SpecialPowerCompletionDie ); + addModule( RebuildHoleExposeDie ); + addModule( UpgradeDie ); + addModule( KeepObjectDie ); + + // update modules + addModule( AssistedTargetingUpdate ); + addModule( AutoFindHealingUpdate ); + addModule( BaseRegenerateUpdate ); + addModule( StealthDetectorUpdate ); + addModule( StealthUpdate ); + addModule( DeletionUpdate ); + addModule( SmartBombTargetHomingUpdate ); + addModule( DynamicShroudClearingRangeUpdate ); + addModule( DeployStyleAIUpdate ); + addModule( AssaultTransportAIUpdate ); + addModule( HordeUpdate ); + addModule( ToppleUpdate ); + addModule( EnemyNearUpdate ); + addModule( LifetimeUpdate ); + addModule( RadiusDecalUpdate ); + addModule( RadiusDecalBehavior ); + addModule( EMPUpdate ); + addModule( LeafletDropBehavior ); + addModule( AutoDepositUpdate ); + addModule( WeaponBonusUpdate ); + addModule( ArmorDamageScalarUpdate ); + addModule( MissileAIUpdate ); + addModule( NeutronMissileUpdate ); + addModule( FireSpreadUpdate ); + addModule( FireWeaponUpdate ); + addModule( FlammableUpdate ); + addModule( FloatUpdate ); + addModule( TensileFormationUpdate ); + addModule( HeightDieUpdate ); + addModule( ScatterShotUpdate ); + addModule( ChinookAIUpdate ); + addModule( JetAIUpdate ); + addModule( AIUpdateInterface ); + addModule( SupplyTruckAIUpdate ); + addModule( DeliverPayloadAIUpdate ); + addModule( HackInternetAIUpdate ); + addModule( DynamicGeometryInfoUpdate ); + addModule( FirestormDynamicGeometryInfoUpdate ); + addModule( LaserUpdate ); + addModule( PointDefenseLaserUpdate ); + addModule( CleanupHazardUpdate ); + addModule( CommandButtonHuntUpdate ); + addModule( PilotFindVehicleUpdate ); + addModule( DemoTrapUpdate ); + addModule( ParticleUplinkCannonUpdate ); + addModule( SpectreGunshipUpdate ); + addModule( SpectreGunshipDeploymentUpdate ); + addModule( BaikonurLaunchPower ); + addModule( BattlePlanUpdate ); + addModule( ProjectileStreamUpdate ); + addModule( QueueProductionExitUpdate ); + addModule( RepairDockUpdate ); +#ifdef ALLOW_SURRENDER + addModule( PrisonDockUpdate ); +#endif + addModule( RailedTransportDockUpdate ); + addModule( DefaultProductionExitUpdate ); + addModule( SpawnPointProductionExitUpdate ); + addModule( SpyVisionUpdate ); + addModule( SlavedUpdate ); + addModule( MobMemberSlavedUpdate ); + addModule( OCLUpdate ); + addModule( SpecialAbilityUpdate ); + addModule( MissileLauncherBuildingUpdate ); + addModule( SupplyCenterProductionExitUpdate ); + addModule( SupplyCenterDockUpdate ); + addModule( SupplyWarehouseDockUpdate ); + addModule( DozerAIUpdate ); +#ifdef ALLOW_SURRENDER + addModule( POWTruckAIUpdate ); +#endif + addModule( RailedTransportAIUpdate ); + addModule( ProductionUpdate ); + addModule( ProneUpdate ); + addModule( StickyBombUpdate ); + addModule( FireOCLAfterWeaponCooldownUpdate ); + addModule( HijackerUpdate ); + addModule( StructureToppleUpdate ); + addModule( StructureCollapseUpdate ); + addModule( BoneFXUpdate ); + addModule( RadarUpdate ); + addModule( AnimationSteeringUpdate ); + addModule( TransportAIUpdate ); + addModule( WanderAIUpdate ); + addModule( TeleporterAIUpdate ); + addModule( WaveGuideUpdate ); + addModule( WorkerAIUpdate ); + addModule( PowerPlantUpdate ); + addModule( CheckpointUpdate ); + + // upgrade modules + addModule( CostModifierUpgrade ); + addModule( ProductionTimeModifierUpgrade ); + addModule( UnitProductionBonusUpgrade ); + addModule( ActiveShroudUpgrade ); + addModule( ArmorUpgrade ); + addModule( CommandSetUpgrade ); + addModule( GrantScienceUpgrade ); + addModule( PassengersFireUpgrade ); + addModule( StatusBitsUpgrade ); + addModule( SubObjectsUpgrade ); + addModule( StealthUpgrade ); + addModule( RadarUpgrade ); + addModule( PowerPlantUpgrade ); + addModule( LocomotorSetUpgrade ); + addModule( ObjectCreationUpgrade ); + addModule( ReplaceObjectUpgrade ); + addModule( ModelConditionUpgrade ); + addModule( UnpauseSpecialPowerUpgrade ); + addModule( WeaponBonusUpgrade ); + addModule( WeaponSetUpgrade ); + addModule( WeaponBonusUpgrade ); + addModule( ExperienceScalarUpgrade ); + addModule( MaxHealthUpgrade ); + + // create modules + addModule( LockWeaponCreate ); + addModule( PreorderCreate ); + addModule( SupplyCenterCreate ); + addModule( SupplyWarehouseCreate ); + addModule( SpecialPowerCreate ); + addModule( GrantUpgradeCreate ); + addModule( VeterancyGainCreate ); + + // damage modules + addModule( BoneFXDamage ); + addModule( TransitionDamageFX ); + + // collide modules + addModule( FireWeaponCollide ); + addModule( SquishCollide ); + + addModule( HealCrateCollide ); + addModule( MoneyCrateCollide ); + addModule( ShroudCrateCollide ); + addModule( UnitCrateCollide ); + addModule( VeterancyCrateCollide ); + addModule( ConvertToCarBombCrateCollide ); + addModule( ConvertToHijackedVehicleCrateCollide ); + addModule( SabotageCommandCenterCrateCollide ); + addModule( SabotageFakeBuildingCrateCollide ); + addModule( SabotageInternetCenterCrateCollide ); + addModule( SabotageMilitaryFactoryCrateCollide ); + addModule( SabotagePowerPlantCrateCollide ); + addModule( SabotageSuperweaponCrateCollide ); + addModule( SabotageSupplyCenterCrateCollide ); + addModule( SabotageSupplyDropzoneCrateCollide ); + addModule( SalvageCrateCollide ); + + // body modules + addModule( InactiveBody ); + addModule( ActiveBody ); + addModule( HighlanderBody ); + addModule( ImmortalBody ); + addModule( StructureBody ); + addModule( HiveStructureBody ); + addModule( UndeadBody ); + + // contain modules + // (none) + + // special power modules + addModule( CashHackSpecialPower ); + addModule( DefectorSpecialPower ); +#ifdef ALLOW_DEMORALIZE + addModule( DemoralizeSpecialPower ); +#endif + addModule( OCLSpecialPower ); + addModule( FireWeaponPower ); + addModule( SpecialAbility ); + addModule( SpyVisionSpecialPower ); + addModule( UpgradeSpecialPower ); + addModule( CashBountyPower ); + addModule( CleanupAreaPower ); + + // destroy modules + // (none) + + // client update modules + addModule( AnimatedParticleSysBoneClientUpdate ); + addModule( SwayClientUpdate ); + addModule( BeaconClientUpdate ); + +} // end init + +//------------------------------------------------------------------------------------------------- +Int ModuleFactory::findModuleInterfaceMask(const AsciiString& name, ModuleType type) +{ + if (name.isEmpty()) + return 0; + + const ModuleTemplate* moduleTemplate = findModuleTemplate(name, type); + if (moduleTemplate) + { + return moduleTemplate->m_whichInterfaces; + } + + return 0; +} + +//------------------------------------------------------------------------------------------------- +ModuleData* ModuleFactory::newModuleDataFromINI(INI* ini, const AsciiString& name, ModuleType type, + const AsciiString& moduleTag) +{ + if (name.isEmpty()) + return NULL; + + const ModuleTemplate* moduleTemplate = findModuleTemplate(name, type); + if (moduleTemplate) + { + ModuleData* md = (*moduleTemplate->m_createDataProc)(ini); + md->setModuleTagNameKey( NAMEKEY( moduleTag ) ); + m_moduleDataList.push_back(md); + return md; + } + + return NULL; +} + +// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +/*static*/ NameKeyType ModuleFactory::makeDecoratedNameKey(const AsciiString& name, ModuleType type) +{ + char tmp[256]; + tmp[0] = '0' + (int)type; + strcpy(&tmp[1], name.str()); + return TheNameKeyGenerator->nameToKey(tmp); +} + +//------------------------------------------------------------------------------------------------- +const ModuleFactory::ModuleTemplate* ModuleFactory::findModuleTemplate(const AsciiString& name, ModuleType type) +{ + NameKeyType namekey = makeDecoratedNameKey(name, type); + + ModuleTemplateMap::const_iterator it = m_moduleTemplateMap.find(namekey); + if (it == m_moduleTemplateMap.end()) + { + DEBUG_CRASH(( "Module name '%s' not found\n", name.str() )); + return NULL; + } + else + { + return &(*it).second; + } +} + +//------------------------------------------------------------------------------------------------- +/** Allocate a new acton class istance given the name */ +//------------------------------------------------------------------------------------------------- +Module *ModuleFactory::newModule( Thing *thing, const AsciiString& name, const ModuleData* moduleData, ModuleType type ) +{ + // sanity + if( name.isEmpty() ) + { + DEBUG_CRASH(("attempting to create module with empty name\n")); + return NULL; + } + const ModuleTemplate* mt = findModuleTemplate(name, type); + if (mt) + { + Module* mod = (*mt->m_createProc)( thing, moduleData ); + +#ifdef DEBUG_CRASHING + if (type == MODULETYPE_BEHAVIOR) + { + BehaviorModule* bm = (BehaviorModule*)mod; + + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_BODY)) != 0) == (bm->getBody() != NULL), + ("getInterfaceMask bad for MODULE_BODY (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_COLLIDE)) != 0) == (bm->getCollide() != NULL), + ("getInterfaceMask bad for MODULE_COLLIDE (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_CONTAIN)) != 0) == (bm->getContain() != NULL), + ("getInterfaceMask bad for MODULE_CONTAIN (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_CREATE)) != 0) == (bm->getCreate() != NULL), + ("getInterfaceMask bad for MODULE_CREATE (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_DAMAGE)) != 0) == (bm->getDamage() != NULL), + ("getInterfaceMask bad for MODULE_DAMAGE (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_DESTROY)) != 0) == (bm->getDestroy() != NULL), + ("getInterfaceMask bad for MODULE_DESTROY (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_DIE)) != 0) == (bm->getDie() != NULL), + ("getInterfaceMask bad for MODULE_DIE (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_SPECIAL_POWER)) != 0) == (bm->getSpecialPower() != NULL), + ("getInterfaceMask bad for MODULE_SPECIAL_POWER (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_UPDATE)) != 0) == (bm->getUpdate() != NULL), + ("getInterfaceMask bad for MODULE_UPDATE (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_UPGRADE)) != 0) == (bm->getUpgrade() != NULL), + ("getInterfaceMask bad for MODULE_UPGRADE (%s)\n",name.str())); + } +#endif + + return mod; + } + + return NULL; + +} // end newModule + +//------------------------------------------------------------------------------------------------- +/** Add a module template to our list of templates */ +//------------------------------------------------------------------------------------------------- +void ModuleFactory::addModuleInternal( NewModuleProc proc, NewModuleDataProc dataproc, ModuleType type, const AsciiString& name, Int whichIntf ) +{ + NameKeyType namekey = makeDecoratedNameKey(name, type); + ModuleTemplate& mtm = m_moduleTemplateMap[namekey]; // this creates it if it does not exist already + mtm.m_createProc = proc; + mtm.m_createDataProc = dataproc; + mtm.m_whichInterfaces = whichIntf; +} + +//------------------------------------------------------------------------------------------------- +void ModuleFactory::crc( Xfer *xfer ) +{ + for (ModuleDataList::iterator mdIt = m_moduleDataList.begin(); mdIt != m_moduleDataList.end(); ++mdIt) + { + ((ModuleData *)(*mdIt))->crc(xfer); + } +} + +//------------------------------------------------------------------------------------------------- +void ModuleFactory::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + for (ModuleDataList::iterator mdIt = m_moduleDataList.begin(); mdIt != m_moduleDataList.end(); ++mdIt) + { + ((ModuleData *)(*mdIt))->xfer(xfer); + } +} + +//------------------------------------------------------------------------------------------------- +void ModuleFactory::loadPostProcess( void ) +{ +} diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp index b935559a4ed..5658e8b600e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp @@ -1,2844 +1,2844 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: Locomotor.cpp /////////////////////////////////////////////////////////////////////////////// -// Author: Steven Johnson, Feb 2002 -// Desc: Locomotor descriptions -/////////////////////////////////////////////////////////////////////////////////////////////////// - - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -#define DEFINE_SURFACECATEGORY_NAMES -#define DEFINE_LOCO_Z_NAMES -#define DEFINE_LOCO_APPEARANCE_NAMES - -#include "Common/INI.h" -#include "GameLogic/GameLogic.h" -#include "GameLogic/PartitionManager.h" -#include "GameLogic/Locomotor.h" -#include "GameLogic/Object.h" -#include "GameLogic/AI.h" -#include "GameLogic/AIPathfind.h" -#include "GameLogic/Module/PhysicsUpdate.h" -#include "GameLogic/Module/BodyModule.h" -#include "GameLogic/Module/AIUpdate.h" - -#ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -static const Real DONUT_TIME_DELAY_SECONDS=2.5f; -static const Real DONUT_DISTANCE=4.0*PATHFIND_CELL_SIZE_F; - - -#define MAX_BRAKING_FACTOR 5.0f -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -LocomotorStore *TheLocomotorStore = NULL; ///< the Locomotor store definition - -const Real BIGNUM = 99999.0f; - -static const char *TheLocomotorPriorityNames[] = -{ - "MOVES_BACK", - "MOVES_MIDDLE", - "MOVES_FRONT", - - NULL -}; - - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------------- -static Real calcSlowDownDist(Real curSpeed, Real desiredSpeed, Real maxBraking) -{ - Real delta = curSpeed - desiredSpeed; - if (delta <= 0) - return 0.0f; - - Real dist = (sqr(delta) / fabs(maxBraking)) * 0.5f; - - // use a little fudge so that things can stop "on a dime" more easily... - const Real FUDGE = 1.05f; - return dist * FUDGE; -} - -//----------------------------------------------------------------------------- -inline Bool isNearlyZero(Real a) -{ - const Real TINY_EPSILON = 0.001f; - return fabs(a) < TINY_EPSILON; -} - -//----------------------------------------------------------------------------- -inline Bool isNearly(Real a, Real val) -{ - const Real TINY_EPSILON = 0.001f; - return fabs(a - val) < TINY_EPSILON; -} - -//----------------------------------------------------------------------------- -// return the angle delta (in 3-space) we turned. -static Real tryToRotateVector3D( - Real maxAngle, // if negative, it's a percent (0...1) of the dist to rotate 'em - const Vector3& inCurDir, - const Vector3& inGoalDir, - Vector3& actualDir -) -{ - if (isNearlyZero(maxAngle)) - { - actualDir = inCurDir; - return 0.0f; - } - - Vector3 curDir = inCurDir; - curDir.Normalize(); - - Vector3 goalDir = inGoalDir; - goalDir.Normalize(); - - // dot of two unit vectors is cos of angle between them. - Real cosine = Vector3::Dot_Product(curDir, goalDir); - // bound it in case of numerical error - Real angleBetween = (Real)ACos(clamp(-1.0f, cosine, 1.0f)); - - if (maxAngle < 0) - { - maxAngle = -maxAngle * angleBetween; - if (isNearlyZero(maxAngle)) - { - actualDir = inCurDir; - return 0.0f; - } - } - - if (fabs(angleBetween) <= maxAngle) - { - // close enough - actualDir = goalDir; - } - else - { - // nah, try as much as we can in the right dir. - // we need to rotate around the axis perpendicular to these two vecs. - // but: cross of two vectors is the perpendicular axis! -#ifdef ALLOW_TEMPORARIES - Vector3 objCrossGoal = Vector3::Cross_Product(curDir, goalDir); - objCrossGoal.Normalize(); -#else - Vector3 objCrossGoal; - Vector3::Normalized_Cross_Product(curDir, goalDir, &objCrossGoal); -#endif - - angleBetween = maxAngle; - Matrix3D rotMtx(objCrossGoal, angleBetween); - actualDir = rotMtx.Rotate_Vector(curDir); - } - - return angleBetween; -} - -//------------------------------------------------------------------------------------------------- -static Real tryToOrientInThisDirection3D(Object* obj, Real maxTurnRate, const Vector3& desiredDir) -{ - Vector3 actualDir; - Real relAngle = tryToRotateVector3D(maxTurnRate, obj->getTransformMatrix()->Get_X_Vector(), desiredDir, actualDir); - if (relAngle != 0.0f) - { - Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z); - - Matrix3D newXform; - newXform.buildTransformMatrix( objPos, actualDir ); - - obj->setTransformMatrix( &newXform ); - } - return relAngle; -} - -//------------------------------------------------------------------------------------------------- -inline Real tryToOrientInThisDirection3D(Object* obj, Real maxTurnRate, const Coord3D* dir) -{ - return tryToOrientInThisDirection3D(obj, maxTurnRate, Vector3(dir->x, dir->y, dir->z)); -} - -//----------------------------------------------------------------------------- -static void calcDirectionToApplyThrust( - const Object* obj, - const PhysicsBehavior* physics, - const Coord3D& ingoalPos, - Real maxAccel, - Vector3& goalDir -) -{ - /* - our meta-goal here is to calculate the direction we should apply our motive force - in order to minimize the angle between (our velocity) and (direction towards goalpos). - - this is complicated by the fact that we generally have an intrinsic velocity already, - that must be accounted for, and by the fact that we can only apply force in our - forward-x-direction (with a thrust-angle-range), and (due to limited range) might not - be able to apply the force in the optimal direction! - */ - - // convert to Vector3, to use all its handy stuff - Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z); - Vector3 goalPos(ingoalPos.x, ingoalPos.y, ingoalPos.z); - - Vector3 vecToGoal = goalPos - objPos; - if (isNearlyZero(vecToGoal.Length2())) - { - // goal pos is essentially same as current pos, so just stay the same & return - goalDir = obj->getTransformMatrix()->Get_X_Vector(); - return; - } - - /* - get our cur vel into a useful Vector3 form - */ - Vector3 curVel(physics->getVelocity()->x, physics->getVelocity()->y, physics->getVelocity()->z); - - // add gravity to our vel so that we account for it in our calcs - curVel.Z += TheGlobalData->m_gravity; - - Bool foundSolution = false; - Real distToGoalSqr = vecToGoal.Length2(); - Real distToGoal = sqrt(distToGoalSqr); - Real curVelMagSqr = curVel.Length2(); - Real curVelMag = sqrt(curVelMagSqr); - Real maxAccelSqr = sqr(maxAccel); - - Real denom = curVelMagSqr - maxAccelSqr; - if (!isNearlyZero(denom)) - { - // solve the (greatly simplified) quadratic... - Real t = (distToGoal * (curVelMag + maxAccel)) / denom; - Real t2 = (distToGoal * (curVelMag - maxAccel)) / denom; - if (t >= 0 || t2 >= 0) - { - // choose the smallest positive t. - if (t < 0 || (t2 >= 0 && t2 < t)) - t = t2; - - // plug it in. - if (!isNearlyZero(t)) - { - goalDir.X = (vecToGoal.X / t) - curVel.X; - goalDir.Y = (vecToGoal.Y / t) - curVel.Y; - goalDir.Z = (vecToGoal.Z / t) - curVel.Z; - goalDir.Normalize(); - foundSolution = true; - } - } - } - if (!foundSolution) - { - // Doh... no (useful) solution. revert to dumb. - goalDir = vecToGoal; - goalDir.Normalize(); - } - -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------------- -LocomotorTemplate::LocomotorTemplate() -{ - // these values mean "make the same as undamaged if not explicitly specified" - m_maxSpeedDamaged = -1.0f; - m_maxTurnRateDamaged = -1.0f; - m_accelerationDamaged = -1.0f; - m_liftDamaged = -1.0f; - - m_surfaces = 0; - m_maxSpeed = 0.0f; - m_maxTurnRate = 0.0f; - m_acceleration = 0.0f; - m_lift = 0.0f; - m_braking = BIGNUM; - m_minSpeed = 0.0f; - m_minTurnSpeed = BIGNUM; - m_behaviorZ = Z_NO_Z_MOTIVE_FORCE; - m_appearance = LOCO_OTHER; - m_movePriority = LOCO_MOVES_MIDDLE; - m_preferredHeight = 0; - m_preferredHeightDamping = 1.0f; - m_circlingRadius = 0; - - m_maxThrustAngle = 0; - m_speedLimitZ = 999999.0f; - m_extra2DFriction = 0.0f; - - m_accelPitchLimit = 0; - m_decelPitchLimit = 0; - m_bounceKick = 0; - -// m_pitchStiffness = 0; -// m_rollStiffness = 0; -// m_pitchDamping = 0; -// m_rollDamping = 0; -// it's highly unlikely you want zero for the defaults for stiffness and damping... (srj) -// for stiffness: stiffness of the "springs" in the suspension 0 = no stiffness, 1 = totally stiff (huh huh, he said "stiff") -// for damping: 0=perfect spring, bounces forever. 1=glued to terrain. - m_pitchStiffness = 0.1f; - m_rollStiffness = 0.1f; - m_pitchDamping = 0.9f; - m_rollDamping = 0.9f; - m_forwardVelCoef = 0; - m_pitchByZVelCoef = 0; - m_thrustRoll = 0.0f; - m_wobbleRate = 0.0f; - m_minWobble = 0.0f; - m_maxWobble = 0.0f; - m_lateralVelCoef = 0; - m_forwardAccelCoef = 0; - m_lateralAccelCoef = 0; - m_uniformAxialDamping = 1.0f; - m_turnPivotOffset = 0; - m_apply2DFrictionWhenAirborne = false; - m_downhillOnly = false; - m_allowMotiveForceWhileAirborne = false; - m_locomotorWorksWhenDead = false; - m_airborneTargetingHeight = INT_MAX; - m_stickToGround = false; - m_canMoveBackward = false; - m_hasSuspension = false; - m_wheelTurnAngle = 0; - m_maximumWheelExtension = 0; - m_maximumWheelCompression = 0; - m_closeEnoughDist = 1.0f; - m_isCloseEnoughDist3D = FALSE; - m_ultraAccurateSlideIntoPlaceFactor = 0.0f; - - m_wanderWidthFactor = 0.0f; - m_wanderLengthFactor = 1.0f; - m_wanderAboutPointRadius = 0.0f; - - m_rudderCorrectionDegree = 0.0f; - m_rudderCorrectionRate = 0.0f; - m_elevatorCorrectionDegree = 0.0f; - m_elevatorCorrectionRate = 0.0f; - -} - -//------------------------------------------------------------------------------------------------- -LocomotorTemplate::~LocomotorTemplate() -{ - -} - -//------------------------------------------------------------------------------------------------- -void LocomotorTemplate::validate() -{ - // this is ok; parachutes need it! - //DEBUG_ASSERTCRASH(m_lift == 0.0f || m_lift > fabs(TheGlobalData->m_gravity), ("Lift is too low to counteract gravity!")); - //DEBUG_ASSERTCRASH(m_liftDamaged == 0.0f || m_liftDamaged > fabs(TheGlobalData->m_gravity), ("LiftDamaged is too low to counteract gravity!")); - //DEBUG_ASSERTCRASH(m_preferredHeight == 0.0f || (m_behaviorZ == Z_SURFACE_RELATIVE_HEIGHT || m_behaviorZ == Z_ABSOLUTE_HEIGHT || m_appearance == LOCO_THRUST), - // ("You must use Z_SURFACE_RELATIVE_HEIGHT or Z_ABSOLUTE_HEIGHT (or THRUST) to use preferredHeight")); - - // for 'damaged' stuff that was omitted, set 'em to be the same as 'undamaged'... - if (m_maxSpeedDamaged < 0.0f) - m_maxSpeedDamaged = m_maxSpeed; - - if (m_maxTurnRateDamaged < 0.0f) - m_maxTurnRateDamaged = m_maxTurnRate; - - if (m_accelerationDamaged < 0.0f) - m_accelerationDamaged = m_acceleration; - - if (m_liftDamaged < 0.0f) - m_liftDamaged = m_lift; - - if (m_appearance == LOCO_WINGS) - { - if (m_minSpeed <= 0.0f) - { - DEBUG_CRASH(("WINGS should always have positive minSpeeds (otherwise, they hover)")); - m_minSpeed = 0.01f; - } - if (m_minTurnSpeed <= 0.0f) - { - DEBUG_CRASH(("WINGS should always have positive minTurnSpeed")); - m_minTurnSpeed = 0.01f; - } - } - - if (m_appearance == LOCO_THRUST) - { - if (m_behaviorZ != Z_NO_Z_MOTIVE_FORCE || - m_lift != 0.0f || - m_liftDamaged != 0.0f) - { - DEBUG_CRASH(("THRUST locos may not use ZAxisBehavior or lift!\n")); - throw INI_INVALID_DATA; - } - if (m_maxSpeed <= 0.0f) - { - // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing - DEBUG_LOG(("THRUST locos may not have zero m_maxSpeed; healing...\n")); - m_maxSpeed = 0.01f; - } - if (m_maxSpeedDamaged <= 0.0f) - { - // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing - DEBUG_LOG(("THRUST locos may not have zero m_maxSpeedDamaged; healing...\n")); - m_maxSpeedDamaged = 0.01f; - } - if (m_minSpeed <= 0.0f) - { - // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing - DEBUG_LOG(("THRUST locos may not have zero m_minSpeed; healing...\n")); - m_minSpeed = 0.01f; - } - } -} - -//------------------------------------------------------------------------------------------------- -static void parseFrictionPerSec( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - Real fricPerSec = INI::scanReal(ini->getNextToken()); - Real fricPerFrame = fricPerSec * SECONDS_PER_LOGICFRAME_REAL; - *(Real *)store = fricPerFrame; -} - -//------------------------------------------------------------------------------------------------- -const FieldParse* LocomotorTemplate::getFieldParse() const -{ - static const FieldParse TheFieldParse[] = - { - { "Surfaces", INI::parseBitString32, TheLocomotorSurfaceTypeNames, offsetof(LocomotorTemplate, m_surfaces) }, - { "Speed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_maxSpeed) }, - { "SpeedDamaged", INI::parseVelocityReal, NULL, offsetof( LocomotorTemplate, m_maxSpeedDamaged ) }, - { "TurnRate", INI::parseAngularVelocityReal, NULL, offsetof(LocomotorTemplate, m_maxTurnRate) }, - { "TurnRateDamaged", INI::parseAngularVelocityReal, NULL, offsetof( LocomotorTemplate, m_maxTurnRateDamaged ) }, - { "Acceleration", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_acceleration) }, - { "AccelerationDamaged", INI::parseAccelerationReal, NULL, offsetof( LocomotorTemplate, m_accelerationDamaged ) }, - { "Lift", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_lift) }, - { "LiftDamaged", INI::parseAccelerationReal, NULL, offsetof( LocomotorTemplate, m_liftDamaged ) }, - { "Braking", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_braking) }, - { "MinSpeed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_minSpeed) }, - { "MinTurnSpeed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_minTurnSpeed) }, - { "PreferredHeight", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_preferredHeight) }, - { "PreferredHeightDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_preferredHeightDamping) }, - { "CirclingRadius", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_circlingRadius) }, - { "Extra2DFriction", parseFrictionPerSec, NULL, offsetof(LocomotorTemplate, m_extra2DFriction) }, - { "SpeedLimitZ", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_speedLimitZ) }, - { "MaxThrustAngle", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_maxThrustAngle) }, // yes, angle, not angular-vel - { "ZAxisBehavior", INI::parseIndexList, TheLocomotorBehaviorZNames, offsetof(LocomotorTemplate, m_behaviorZ) }, - { "Appearance", INI::parseIndexList, TheLocomotorAppearanceNames, offsetof(LocomotorTemplate, m_appearance) }, \ - { "GroupMovementPriority", INI::parseIndexList, TheLocomotorPriorityNames, offsetof(LocomotorTemplate, m_movePriority) }, \ - - { "AccelerationPitchLimit", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_accelPitchLimit) }, - { "DecelerationPitchLimit", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_decelPitchLimit) }, - { "BounceAmount", INI::parseAngularVelocityReal, NULL, offsetof(LocomotorTemplate, m_bounceKick) }, - { "PitchStiffness", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchStiffness) }, - { "RollStiffness", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rollStiffness) }, - { "PitchDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchDamping) }, - { "RollDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rollDamping) }, - { "ThrustRoll", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_thrustRoll) }, - { "ThrustWobbleRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wobbleRate) }, - { "ThrustMinWobble", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_minWobble) }, - { "ThrustMaxWobble", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maxWobble) }, - { "PitchInDirectionOfZVelFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchByZVelCoef) }, - { "ForwardVelocityPitchFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_forwardVelCoef) }, - { "LateralVelocityRollFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_lateralVelCoef) }, - { "ForwardAccelerationPitchFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_forwardAccelCoef) }, - { "LateralAccelerationRollFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_lateralAccelCoef) }, - { "UniformAxialDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_uniformAxialDamping) }, - { "TurnPivotOffset", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_turnPivotOffset) }, - { "Apply2DFrictionWhenAirborne", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_apply2DFrictionWhenAirborne) }, - { "DownhillOnly", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_downhillOnly) }, - { "AllowAirborneMotiveForce", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_allowMotiveForceWhileAirborne) }, - { "LocomotorWorksWhenDead", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_locomotorWorksWhenDead) }, - { "AirborneTargetingHeight", INI::parseInt, NULL, offsetof( LocomotorTemplate, m_airborneTargetingHeight ) }, - { "StickToGround", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_stickToGround) }, - { "CanMoveBackwards", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_canMoveBackward) }, - { "HasSuspension", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_hasSuspension) }, - { "FrontWheelTurnAngle", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_wheelTurnAngle) }, - { "MaximumWheelExtension", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maximumWheelExtension) }, - { "MaximumWheelCompression", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maximumWheelCompression) }, - { "CloseEnoughDist", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_closeEnoughDist) }, - { "CloseEnoughDist3D", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_isCloseEnoughDist3D) }, - { "SlideIntoPlaceTime", INI::parseDurationReal, NULL, offsetof(LocomotorTemplate, m_ultraAccurateSlideIntoPlaceFactor) }, - - { "WanderWidthFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderWidthFactor) }, - { "WanderLengthFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderLengthFactor) }, - { "WanderAboutPointRadius", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderAboutPointRadius) }, - - { "RudderCorrectionDegree", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rudderCorrectionDegree) }, - { "RudderCorrectionRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rudderCorrectionRate) }, - { "ElevatorCorrectionDegree", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_elevatorCorrectionDegree) }, - { "ElevatorCorrectionRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_elevatorCorrectionRate) }, - { NULL, NULL, NULL, 0 } // keep this last - - }; - return TheFieldParse; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -LocomotorStore::LocomotorStore() -{ -} - -//------------------------------------------------------------------------------------------------- -LocomotorStore::~LocomotorStore() -{ - // delete all the templates, then clear out the table. - LocomotorTemplateMap::iterator it; - for (it = m_locomotorTemplates.begin(); it != m_locomotorTemplates.end(); ++it) { - it->second->deleteInstance(); - } - - m_locomotorTemplates.clear(); -} - -//------------------------------------------------------------------------------------------------- -LocomotorTemplate* LocomotorStore::findLocomotorTemplate(NameKeyType namekey) -{ - if (namekey == NAMEKEY_INVALID) - return NULL; - - LocomotorTemplateMap::iterator it = m_locomotorTemplates.find(namekey); - if (it == m_locomotorTemplates.end()) - return NULL; - else - return (*it).second; -} - -//------------------------------------------------------------------------------------------------- -const LocomotorTemplate* LocomotorStore::findLocomotorTemplate(NameKeyType namekey) const -{ - if (namekey == NAMEKEY_INVALID) - return NULL; - - LocomotorTemplateMap::const_iterator it = m_locomotorTemplates.find(namekey); - if (it == m_locomotorTemplates.end()) - { - return NULL; - } - else - { - return (*it).second; - } -} - -//------------------------------------------------------------------------------------------------- -void LocomotorStore::update() -{ -} - -//------------------------------------------------------------------------------------------------- -void LocomotorStore::reset() -{ - // cleanup overrides. - LocomotorTemplateMap::iterator it; - for (it = m_locomotorTemplates.begin(); it != m_locomotorTemplates.end(); ) { - Overridable *locoTemp = it->second->deleteOverrides(); - if (!locoTemp) - { - m_locomotorTemplates.erase(it); - } - else - { - ++it; - } - } -} - -//------------------------------------------------------------------------------------------------- -LocomotorTemplate *LocomotorStore::newOverride( LocomotorTemplate *locoTemplate ) -{ - if (locoTemplate == NULL) - return NULL; - - // allocate new template - LocomotorTemplate *newTemplate = newInstance(LocomotorTemplate); - - // copy data from final override to 'newTemplate' as a set of initial default values - *newTemplate = *locoTemplate; - locoTemplate->setNextOverride(newTemplate); - - newTemplate->markAsOverride(); - - // return the newly created override for us to set values with etc - return newTemplate; - -} // end newOverride - -//------------------------------------------------------------------------------------------------- -/*static*/ void LocomotorStore::parseLocomotorTemplateDefinition(INI* ini) -{ - if (!TheLocomotorStore) - throw INI_INVALID_DATA; - - Bool isOverride = false; - // read the Locomotor name - const char* token = ini->getNextToken(); - NameKeyType namekey = NAMEKEY(token); - - LocomotorTemplate *loco = TheLocomotorStore->findLocomotorTemplate(namekey); - if (loco) { - if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES) { - loco = TheLocomotorStore->newOverride((LocomotorTemplate*) loco->friend_getFinalOverride()); - } - isOverride = true; - } else { - loco = newInstance(LocomotorTemplate); - if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES) { - loco->markAsOverride(); - } - } - - loco->friend_setName(token); - ini->initFromINI(loco, loco->getFieldParse()); - loco->validate(); - - // if this is an override, then we want the pointer on the existing named locomotor to point us - // to the override, so don't add it to the map. - if (!isOverride) - TheLocomotorStore->m_locomotorTemplates[namekey] = loco; -} - -//------------------------------------------------------------------------------------------------- -/*static*/ void INI::parseLocomotorTemplateDefinition( INI* ini ) -{ - LocomotorStore::parseLocomotorTemplateDefinition(ini); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- -Locomotor::Locomotor(const LocomotorTemplate* tmpl) -{ - m_template = tmpl; - m_brakingFactor = 1.0f; - m_maxLift = BIGNUM; - m_maxSpeed = BIGNUM; - m_maxAccel = BIGNUM; - m_maxBraking = BIGNUM; - m_maxTurnRate = BIGNUM; - m_flags = 0; - m_closeEnoughDist = m_template->m_closeEnoughDist; - setFlag(IS_CLOSE_ENOUGH_DIST_3D, m_template->m_isCloseEnoughDist3D); -#ifdef CIRCLE_FOR_LANDING - m_circleThresh = 0.0f; -#endif - m_preferredHeight = m_template->m_preferredHeight; - m_preferredHeightDamping = m_template->m_preferredHeightDamping; - - m_angleOffset = GameLogicRandomValueReal(-PI/6, PI/6); - m_offsetIncrement = (PI/40) * (GameLogicRandomValueReal(0.8f, 1.2f)/m_template->m_wanderLengthFactor); - setFlag(OFFSET_INCREASING, GameLogicRandomValue(0,1)); - m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; - - m_speedMultiplier = 1.0; -} - -//------------------------------------------------------------------------------------------------- -Locomotor::Locomotor(const Locomotor& that) -{ - //Added By Sadullah Nader - //Initializations - m_angleOffset = 0.0f; - m_maintainPos.zero(); - - // - - m_template = that.m_template; - m_brakingFactor = that.m_brakingFactor; - m_maxLift = that.m_maxLift; - m_maxSpeed = that.m_maxSpeed; - m_maxAccel = that.m_maxAccel; - m_maxBraking = that.m_maxBraking; - m_maxTurnRate = that.m_maxTurnRate; - m_flags = that.m_flags; - m_closeEnoughDist = that.m_closeEnoughDist; -#ifdef CIRCLE_FOR_LANDING - m_circleThresh = that.m_circleThresh; -#endif - m_preferredHeight = that.m_preferredHeight; - m_preferredHeightDamping = that.m_preferredHeightDamping; - m_angleOffset = that.m_angleOffset; - m_offsetIncrement = that.m_offsetIncrement; -} - -//------------------------------------------------------------------------------------------------- -Locomotor& Locomotor::operator=(const Locomotor& that) -{ - if (this != &that) - { - m_template = that.m_template; - m_brakingFactor = that.m_brakingFactor; - m_maxLift = that.m_maxLift; - m_maxSpeed = that.m_maxSpeed; - m_maxAccel = that.m_maxAccel; - m_maxBraking = that.m_maxBraking; - m_maxTurnRate = that.m_maxTurnRate; - m_flags = that.m_flags; - m_closeEnoughDist = that.m_closeEnoughDist; -#ifdef CIRCLE_FOR_LANDING - m_circleThresh = that.m_circleThresh; -#endif - m_preferredHeight = that.m_preferredHeight; - m_preferredHeightDamping = that.m_preferredHeightDamping; - } - return *this; -} - -//------------------------------------------------------------------------------------------------- -Locomotor::~Locomotor() -{ -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void Locomotor::crc( Xfer *xfer ) -{ - -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void Locomotor::xfer( Xfer *xfer ) -{ - // version - const XferVersion currentVersion = 2; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - if (version>=2) { - xfer->xferUnsignedInt(&m_donutTimer); - } - - xfer->xferCoord3D(&m_maintainPos); - xfer->xferReal(&m_brakingFactor); - xfer->xferReal(&m_maxLift); - xfer->xferReal(&m_maxSpeed); - xfer->xferReal(&m_maxAccel); - xfer->xferReal(&m_maxBraking); - xfer->xferReal(&m_maxTurnRate); - xfer->xferReal(&m_closeEnoughDist); -#ifdef CIRCLE_FOR_LANDING - DEBUG_CRASH(("not supported, must fix me")); -#endif - xfer->xferUnsignedInt(&m_flags); - xfer->xferReal(&m_preferredHeight); - xfer->xferReal(&m_preferredHeightDamping); - xfer->xferReal(&m_angleOffset); - xfer->xferReal(&m_offsetIncrement); - - xfer->xferReal(&m_speedMultiplier); - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void Locomotor::loadPostProcess( void ) -{ - -} // end loadPostProcess - -//------------------------------------------------------------------------------------------------- -void Locomotor::startMove(void) -{ - // Reset the donut timer. - m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::getMaxSpeedForCondition(BodyDamageType condition) const -{ - Real speed; - - if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) - speed = m_template->m_maxSpeed; - else - speed = m_template->m_maxSpeedDamaged; - - speed *= m_speedMultiplier; - - if (speed > m_maxSpeed) - speed = m_maxSpeed; - - return speed; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::getMaxTurnRate(BodyDamageType condition) const -{ - Real turn; - - if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) - turn = m_template->m_maxTurnRate; - else - turn = m_template->m_maxTurnRateDamaged; - - turn *= m_speedMultiplier; - - if (turn > m_maxTurnRate) - turn = m_maxTurnRate; - - const Real TURN_FACTOR = 2; - if (getFlag(ULTRA_ACCURATE)) - turn *= TURN_FACTOR; // monster turning ability - - return turn; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::getMaxAcceleration(BodyDamageType condition) const -{ - Real accel; - - if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) - accel = m_template->m_acceleration; - else - accel = m_template->m_accelerationDamaged; - - accel *= m_speedMultiplier; - - if (accel > m_maxAccel) - accel = m_maxAccel; - - return accel; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::getBraking() const -{ - Real braking = m_template->m_braking; - - braking *= m_speedMultiplier; - - if (braking > m_maxBraking) - braking = m_maxBraking; - - return braking; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::getMaxLift(BodyDamageType condition) const -{ - Real lift; - - if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) - lift = m_template->m_lift; - else - lift = m_template->m_liftDamaged; - - lift *= m_speedMultiplier; - - if (lift > m_maxLift) - lift = m_maxLift; - - return lift; -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::locoUpdate_moveTowardsAngle(Object* obj, Real goalAngle) -{ - setFlag(MAINTAIN_POS_IS_VALID, false); - - if (obj == NULL || m_template == NULL) - return; - - PhysicsBehavior *physics = obj->getPhysics(); - if (physics == NULL) - { - DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); - return; - } - - // Skip moveTowardsAngle if physics say you're stunned - if(physics->getIsStunned()) - { - return; - } - -#ifdef DEBUG_OBJECT_ID_EXISTS -// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_moveTowardsAngle %f (%f deg), spd %f (%f)\n",goalAngle,goalAngle*180/PI,physics->getSpeed(),physics->getForwardSpeed2D())); -#endif - - Real minSpeed = getMinSpeed(); - if (minSpeed > 0) - { - // can't stay in one place; move in the desired direction at min speed. - Coord3D desiredPos = *obj->getPosition(); - desiredPos.x += Cos(goalAngle) * minSpeed * 2; - desiredPos.y += Sin(goalAngle) * minSpeed * 2; - // pass a huge num for "dist to goal", so that we don't think we're nearing - // our destination and thus slow down... - const Real onPathDistToGoal = 99999.0f; - Bool blocked = false; - locoUpdate_moveTowardsPosition(obj, desiredPos, onPathDistToGoal, minSpeed, &blocked); - - // don't need to call handleBehaviorZ() here, since locoUpdate_moveTowardsPosition() will do so - return; - } - else - { - DEBUG_ASSERTCRASH(m_template->m_appearance != LOCO_THRUST, ("THRUST should always have minspeeds!\n")); - Coord3D desiredPos = *obj->getPosition(); - desiredPos.x += Cos(goalAngle) * 1000.0f; - desiredPos.y += Sin(goalAngle) * 1000.0f; - PhysicsTurningType rotating = rotateTowardsPosition(obj, desiredPos); - physics->setTurning(rotating); - handleBehaviorZ(obj, physics, *obj->getPosition()); - } - -} - -//------------------------------------------------------------------------------------------------- -PhysicsTurningType Locomotor::rotateTowardsPosition(Object* obj, const Coord3D& goalPos, Real *relAngle) -{ - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real turnRate = getMaxTurnRate(bdt); - - PhysicsTurningType rotating = rotateObjAroundLocoPivot(obj, goalPos, turnRate, relAngle); - return rotating; -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::setPhysicsOptions(Object* obj) -{ - PhysicsBehavior *physics = obj->getPhysics(); - if (physics == NULL) - { - DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); - return; - } - - // crank up the friction in ultra-accurate mode to increase movement precision. - const Real EXTRA_FRIC = 0.5f; - Real extraExtraFriction = getFlag(ULTRA_ACCURATE) ? EXTRA_FRIC : 0.0f; - physics->setExtraFriction(m_template->m_extra2DFriction + extraExtraFriction); - physics->setAllowAirborneFriction(getApply2DFrictionWhenAirborne()); // you'd think we wouldn't want friction in the air, but it's needed for realistic behavior. - physics->setStickToGround(getStickToGround()); // walking guys aren't allowed to catch huge (or even small) air. -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalPos, - Real onPathDistToGoal, Real desiredSpeed, Bool *blocked) -{ - setFlag(MAINTAIN_POS_IS_VALID, false); - - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxSpeed = getMaxSpeedForCondition(bdt); - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - Real distToStopAtMaxSpeed = (maxSpeed/getBraking()) * (maxSpeed)/2.0f; - if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > distToStopAtMaxSpeed) - { - setFlag(IS_BRAKING, false); - m_brakingFactor = 1.0f; - } - PhysicsBehavior *physics = obj->getPhysics(); - if (physics == NULL) - { - DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); - return; - } - - // Skip moveTowardsPosition if physics say you're stunned - if(physics->getIsStunned()) - { - return; - } - -#ifdef DEBUG_OBJECT_ID_EXISTS -// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_moveTowardsPosition %f %f %f (dtg %f, spd %f), speed %f (%f)\n",goalPos.x,goalPos.y,goalPos.z,onPathDistToGoal,desiredSpeed,physics->getSpeed(),physics->getForwardSpeed2D())); -#endif - - // - // do not allow for invalid positions that the pathfinder cannot handle ... for airborne - // objects we don't need the pathfinder so we'll ignore this - // - if( BitIsSet( m_template->m_surfaces, LOCOMOTORSURFACE_AIR ) == false && - !TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, obj->getPosition()) && - !getFlag(ALLOW_INVALID_POSITION)) - { - // Somehow, we have gotten to an invalid location. - if (fixInvalidPosition(obj, physics)) - { - // the we adjusted us toward a legal position, so just return. - return; - } - } - - // If the actual distance is farther, then use the actual distance so we get there. - Real dx = goalPos.x - obj->getPosition()->x; - Real dy = goalPos.y - obj->getPosition()->y; - Real dz = goalPos.z - obj->getPosition()->z; - Real dist = sqrt(dx*dx+dy*dy); - if (dist>onPathDistToGoal) - { - if (!obj->isKindOf(KINDOF_PROJECTILE) && dist>2*onPathDistToGoal) - { - setFlag(IS_BRAKING, true); - } - onPathDistToGoal = dist; - } - - Coord3D nullAccel; - - Bool treatAsAirborne = false; - Coord3D pos = *obj->getPosition(); - Real heightAboveSurface = pos.z - TheTerrainLogic->getLayerHeight(pos.x, pos.y, obj->getLayer()); - - if( obj->getStatusBits().test( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) ) - { - heightAboveSurface -= obj->getCarrierDeckHeight(); - } - - if (heightAboveSurface > -(3*3)*TheGlobalData->m_gravity) - { - // If we get high enough to stay up for 3 frames, then we left the ground. - treatAsAirborne = true; - } - // We apply a zero acceleration to all units, as the call to - // applyMotiveForce flags an object as being "driven" by a locomotor, rather - // than being pushed around by objects bumping it. - nullAccel.x = nullAccel.y = nullAccel.z = 0; - physics->applyMotiveForce(&nullAccel); - - if (*blocked) - { - if (desiredSpeed > physics->getVelocityMagnitude()) - { - *blocked = false; - } - if (treatAsAirborne && BitIsSet( m_template->m_surfaces, LOCOMOTORSURFACE_AIR ) ) - { - // Airborne flying objects don't collide for now. jba. - *blocked = false; - } - } - - if (*blocked) - { - physics->scrubVelocity2D(desiredSpeed); // stop if we are about to run into the blocking object. - Real turnRate = getMaxTurnRate(obj->getBodyModule()->getDamageState()); - if (m_template->m_wanderWidthFactor == 0.0f) - { - *blocked = (TURN_NONE != rotateObjAroundLocoPivot(obj, goalPos, turnRate)); - } - - // it is very important to be sure to call this in all situations, even if not moving in 2d space. - handleBehaviorZ(obj, physics, goalPos); - return; - } - - if ( -// srj sez: I don't know why we didn't want HOVERs to allow to "brake". -// we actually really want them to, because it allows much more precise destination positioning. -// m_template->m_appearance == LOCO_HOVER || - m_template->m_appearance == LOCO_WINGS) - { - setFlag(IS_BRAKING, false); - } - - Bool wasBraking = obj->getStatusBits().test( OBJECT_STATUS_BRAKING ); - - physics->setTurning(TURN_NONE); - if (getAllowMotiveForceWhileAirborne() || !treatAsAirborne) - { - switch (m_template->m_appearance) - { - case LOCO_LEGS_TWO: - moveTowardsPositionLegs(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_CLIMBER: - moveTowardsPositionClimb(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_WHEELS_FOUR: - case LOCO_MOTORCYCLE: - moveTowardsPositionWheels( obj, physics, goalPos, onPathDistToGoal, desiredSpeed ); - break; - case LOCO_TREADS: - moveTowardsPositionTreads(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_HOVER: - moveTowardsPositionHover(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_WINGS: - moveTowardsPositionWings(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_THRUST: - moveTowardsPositionThrust(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_OTHER: - default: - moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - } - } - - handleBehaviorZ(obj, physics, goalPos); - // Objects that are braking don't follow the normal physics, so they end up at their destination exactly. - obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ), getFlag(IS_BRAKING) ); - - if (wasBraking) - { - #define MIN_VEL (PATHFIND_CELL_SIZE_F/(LOGICFRAMES_PER_SECOND)) - - Coord3D pos = *obj->getPosition(); - if (obj->isKindOf(KINDOF_PROJECTILE)) - { - // Projectiles never stop braking once they start. jba. - obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ) ); - // Projectiles cheat in 3 dimensions. - dist = sqrt(dx*dx+dy*dy+dz*dz); - Real vel = physics->getVelocityMagnitude(); - if (vel < MIN_VEL) - vel = MIN_VEL; - if (vel > dist) - vel = dist; // do not overcompensate! - // Normalize. - if (dist > 0.001f) - { - dist = 1.0f / dist; - dx *= dist; - dy *= dist; - dz *= dist; - - // DEBUG_LOG((">>> Locomotor Braking - d(xyz) = %f / %f / %f\n", dx * vel, dy * vel, dz * vel)); - - pos.x += dx * vel; - pos.y += dy * vel; - pos.z += dz * vel; - } - } - else - { - // not projectiles only cheat in x & y. - // Normalize. - if (dist > 0.001f) - { - Real vel = fabs(physics->getForwardSpeed2D()); - if (vel < MIN_VEL) - vel = MIN_VEL; - if (vel > dist) - vel = dist; // do not overcompensate! - dist = 1.0f / dist; - dx *= dist; - dy *= dist; - pos.x += dx * vel; - pos.y += dy * vel; - } - } - obj->setPosition(&pos); - } - -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxSpeed = getMaxSpeedForCondition(bdt); - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - Real maxAcceleration = getMaxAcceleration(bdt); - - // Locomotion for treaded vehicles, ie tanks. - - // - // Orient toward goal position - // -// Real angle = obj->getOrientation(); -// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); -// Real desiredAngle = angle + relAngle; - Real relAngle ; - PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos, &relAngle); - physics->setTurning(rotating); - - // - // Modulate speed according to turning. The more we have to turn, the slower we go - // - const Real QUAETERPI = PI / 4.0f; - Real angleCoeff = (Real)fabs( relAngle ) / QUAETERPI; - if (angleCoeff > 1.0f) - angleCoeff = 1.0; - - - Real dx = obj->getPosition()->x - goalPos.x; - Real dy = obj->getPosition()->y - goalPos.y; - - - Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; - - -// if (speed < m_minTurnSpeed) -// speed = m_minTurnSpeed; - - Real actualSpeed = physics->getForwardSpeed2D(); - Real slowDownTime = actualSpeed / getBraking(); - Real slowDownDist = (actualSpeed/1.50f) * slowDownTime; - - if (sqr(dx)+sqr(dy) 0.05) { - goalSpeed = actualSpeed*0.6f; - } - - if (onPathDistToGoal < slowDownDist && !getFlag(IS_BRAKING) && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - { - setFlag(IS_BRAKING, true); - m_brakingFactor = 1.1f; - } - - if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > 2.0*slowDownDist) - { - setFlag(IS_BRAKING, false); - } - - if (getFlag(IS_BRAKING)) - { - m_brakingFactor = slowDownDist/onPathDistToGoal; - m_brakingFactor *= m_brakingFactor; - if (m_brakingFactor>MAX_BRAKING_FACTOR) { - m_brakingFactor = MAX_BRAKING_FACTOR; - } - if (slowDownDist>onPathDistToGoal) { - goalSpeed = actualSpeed-getBraking(); - if (goalSpeed<0.0f) goalSpeed= 0.0f; - } else if (slowDownDist>onPathDistToGoal*0.75f) { - goalSpeed = actualSpeed-getBraking()/2.0f; - if (goalSpeed<0.0f) goalSpeed = 0.0f; - } else { - goalSpeed = actualSpeed; - } - } - - - //DEBUG_LOG(("Actual speed %f, Braking factor %f, slowDownDist %f, Pathdist %f, goalSpeed %f\n", - // actualSpeed, m_brakingFactor, slowDownDist, onPathDistToGoal, goalSpeed)); - - // - // Maintain goal speed - // - Real speedDelta = goalSpeed - actualSpeed; - if (speedDelta != 0.0f) - { - Real mass = physics->getMass(); - Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -m_brakingFactor*getBraking(); - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - const Coord3D *dir = obj->getUnitDirectionVector2D(); - - Coord3D force; - force.x = accelForce * dir->x; - force.y = accelForce * dir->y; - force.z = 0.0f; - - // apply forces to object - physics->applyMotiveForce( &force ); - } -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxSpeed = getMaxSpeedForCondition(bdt); - Real maxTurnRate = getMaxTurnRate(bdt); - Real maxAcceleration = getMaxAcceleration(bdt); - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - // Locomotion for wheeled vehicles, ie trucks. - // - // See if we are turning. If so, use the min turn speed. - // - Real turnSpeed = m_template->m_minTurnSpeed; - Real angle = obj->getOrientation(); -// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); -// Real desiredAngle = angle + relAngle; - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); - Real relAngle = stdAngleDiff(desiredAngle, angle); - - Bool moveBackwards = false; - - // Wheeled vehicles can only turn while moving, so make sure the turn speed is reasonable. - if (turnSpeed < maxSpeed/4.0f) - { - turnSpeed = maxSpeed/4.0f; - } - - - Real actualSpeed = physics->getForwardSpeed2D(); - Bool do3pointTurn = false; -#if 1 - if (actualSpeed==0.0f) { - setFlag(MOVING_BACKWARDS, false); - if (m_template->m_canMoveBackward && fabs(relAngle) > PI/2) { - setFlag(MOVING_BACKWARDS, true ); - setFlag(DOING_THREE_POINT_TURN, onPathDistToGoal>5*obj->getGeometryInfo().getMajorRadius()); - } - - } - if (getFlag(MOVING_BACKWARDS)) { - if (fabs(relAngle) < PI/2) { - moveBackwards = false; - setFlag(MOVING_BACKWARDS, false); - } else { - moveBackwards = true; - setFlag(DOING_THREE_POINT_TURN, onPathDistToGoal>5*obj->getGeometryInfo().getMajorRadius()); - do3pointTurn = getFlag(DOING_THREE_POINT_TURN); - if (!do3pointTurn) { - desiredAngle = stdAngleDiff(desiredAngle, PI); - relAngle = stdAngleDiff(desiredAngle, angle); - } - } - } -#endif - - const Real SMALL_TURN = PI / 20.0f; - if ((Real)fabs( relAngle ) > SMALL_TURN) - { - if (desiredSpeed>turnSpeed) - { - desiredSpeed = turnSpeed; - } - } - - Real goalSpeed = desiredSpeed; - if (moveBackwards) { - actualSpeed = -actualSpeed; - } - - - - Real slowDownTime = actualSpeed / getBraking() + 1.0f; - Real slowDownDist = (actualSpeed/1.5f) * slowDownTime + actualSpeed; - Real effectiveSlowDownDist = slowDownDist; - if (effectiveSlowDownDist < 1*PATHFIND_CELL_SIZE) { - effectiveSlowDownDist = 1*PATHFIND_CELL_SIZE; - } - - - const Real FIFTEEN_DEGREES = PI / 12.0f; - const Real PROJECT_FRAMES = LOGICFRAMES_PER_SECOND/2; // Project out 1/2 second. - if (fabs( relAngle ) > FIFTEEN_DEGREES) - { - // If we're turning more than 10 degrees, check & see if we're moving into "impassable territory" - Real distance = PROJECT_FRAMES * (goalSpeed+actualSpeed)/2.0f; - Real targetAngle = obj->getOrientation(); - Real turnFactor = ((goalSpeed+actualSpeed)/2.0f)/turnSpeed; - if (turnFactor > 1.0f) - turnFactor = 1.0f; - Real turnAmount = PROJECT_FRAMES*turnFactor*maxTurnRate/4.0f; - if (relAngle < 0) - { - targetAngle -= turnAmount; - } - else - { - targetAngle += turnAmount; - } - Coord3D offset; - offset.x = Cos(targetAngle)*distance; - offset.y = Sin(targetAngle)*distance; - offset.z = 0; - - const Coord3D* pos = obj->getPosition(); - - Coord3D nextPos; - nextPos.x = pos->x+offset.x; - nextPos.y = pos->y+offset.y; - nextPos.z = pos->z; - - pos = obj->getPosition(); - - Coord3D halfPos; - halfPos.x = pos->x+offset.x/2; - halfPos.y = pos->y+offset.y/2; - halfPos.z = pos->z; - - if (!TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &halfPos) || - !TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &nextPos)) - { - PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos); - physics->setTurning(rotating); - - // apply a zero force to object so that it acts "driven" - Coord3D force; - force.zero(); - physics->applyMotiveForce( &force ); - return; - } - - } - - if (onPathDistToGoal < effectiveSlowDownDist && !getFlag(IS_BRAKING) && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - { - setFlag(IS_BRAKING, true); - m_brakingFactor = 1.1f; - } - - - if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > 2.0*slowDownDist) - { - setFlag(IS_BRAKING, false); - } - - if (onPathDistToGoal > DONUT_DISTANCE) { - m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; - } else { - if (m_donutTimer < TheGameLogic->getFrame()) { - setFlag(IS_BRAKING, true); - } - } - - if (getFlag(IS_BRAKING)) - { - m_brakingFactor = slowDownDist/onPathDistToGoal; - m_brakingFactor *= m_brakingFactor; - if (m_brakingFactor>MAX_BRAKING_FACTOR) { - m_brakingFactor = MAX_BRAKING_FACTOR; - } - m_brakingFactor = 1.0f; - if (slowDownDist>onPathDistToGoal) { - goalSpeed = actualSpeed-getBraking(); - if (goalSpeed<0.0f) goalSpeed= 0.0f; - } else if (slowDownDist>onPathDistToGoal*0.75f) { - goalSpeed = actualSpeed-getBraking()/2.0f; - if (goalSpeed<0.0f) goalSpeed = 0.0f; - } else { - goalSpeed = actualSpeed; - } - } - - - //DEBUG_LOG(("Actual speed %f, Braking factor %f, slowDownDist %f, Pathdist %f, goalSpeed %f\n", - // actualSpeed, m_brakingFactor, slowDownDist, onPathDistToGoal, goalSpeed)); - - - // Wheeled can only turn while moving. - Real turnFactor = actualSpeed/turnSpeed; - if (turnFactor<0) { - turnFactor = -turnFactor; // in case we're sliding backwards in a 3 pt turn. - } - if (turnFactor > 1.0f) - turnFactor = 1.0f; - Real turnAmount = turnFactor*maxTurnRate; - - PhysicsTurningType rotating; - if (moveBackwards && !do3pointTurn) { - Coord3D backwardPos = *obj->getPosition(); - backwardPos.x += -(goalPos.x - obj->getPosition()->x); - backwardPos.y += -(goalPos.y - obj->getPosition()->y); - rotating = rotateObjAroundLocoPivot(obj, backwardPos, turnAmount); - } else { - rotating = rotateObjAroundLocoPivot(obj, goalPos, turnAmount); - } - - physics->setTurning(rotating); - - // - // Maintain goal speed - // - Real speedDelta = goalSpeed - actualSpeed; - if (moveBackwards) { - speedDelta = -goalSpeed+actualSpeed; - } - if (speedDelta != 0.0f) - { - Real mass = physics->getMass(); - Real acceleration; - if (moveBackwards) { - acceleration = (speedDelta < 0.0f) ? -maxAcceleration : m_brakingFactor*getBraking(); - } else { - acceleration = (speedDelta > 0.0f) ? maxAcceleration : -m_brakingFactor*getBraking(); - } - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - //DEBUG_LOG(("Braking %d, actualSpeed %f, goalSpeed %f, delta %f, accel %f\n", getFlag(IS_BRAKING), - //actualSpeed, goalSpeed, speedDelta, accelForce)); - - const Coord3D *dir = obj->getUnitDirectionVector2D(); - - Coord3D force; - force.x = accelForce * dir->x; - force.y = accelForce * dir->y; - force.z = 0.0f; - - // apply forces to object - physics->applyMotiveForce( &force ); - } - -} -//------------------------------------------------------------------------------------------------- -Bool Locomotor::fixInvalidPosition(Object* obj, PhysicsBehavior *physics) -{ - if (obj->isKindOf(KINDOF_DOZER)) { - // don't fix him. - return false; - } -#define no_IGNORE_INVALID -#ifdef IGNORE_INVALID - // Right now we ignore invalid positions, so when units clip the edge of a building or cliff - // they don't get stuck. jba. 12SEPT02 - return false; -#else - Int dx = 0; - Int dy = 0; - Int i, j; - for (j=-1; j<2; j++) { - for (i=-1; i<2; i++) { - Coord3D thePos = *obj->getPosition(); - thePos.x += i*PATHFIND_CELL_SIZE_F; - thePos.y += j*PATHFIND_CELL_SIZE_F; - if (!TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &thePos)) { - if (i<0) dx += 1; - if (i>0) dx -= 1; - if (j<0) dy += 1; - if (j>0) dy -= 1; - } - } - } - if (dx || dy) { - - Coord3D correction; - correction.x = dx*physics->getMass()/5; - correction.y = dy*physics->getMass()/5; - correction.z = 0; - - Coord3D correctionNormalized = correction; - correctionNormalized.normalize(); - - Coord3D velocity; - // Kill current velocity in the direction of the correction. - velocity = *physics->getVelocity(); - Real dot = (velocity.x*correctionNormalized.x) + (velocity.y*correctionNormalized.y); - if (dot>.25f) { - // It was already leaving. - return false; - } - - - // Kill current accel - //physics->clearAcceleration(); - - if (dot<0) { - dot = sqrt(-dot); - correctionNormalized.x *= dot*physics->getMass(); - correctionNormalized.y *= dot*physics->getMass(); - physics->applyMotiveForce(&correctionNormalized); - } - - // apply correction. - physics->applyMotiveForce(&correction); - return true; - } - return false; -#endif -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::calcMinTurnRadius(BodyDamageType condition, Real* timeToTravelThatDist) const -{ - Real minSpeed = getMinSpeed(); // in dist/frame - Real maxTurnRate = getMaxTurnRate(condition); // in rads/frame - - /* - our minimum circumference will be like so: - - Real minTurnCircum = maxSpeed * (2*PI / maxTurnRate); - - so therefore our minimum turn radius is: - - Real minTurnRadius = minTurnCircum / 2*PI; - - so we just eliminate the middleman: - */ - // if we can't turn, return a huge-but-finite radius rather than NAN... - Real minTurnRadius = (maxTurnRate > 0.0f) ? minSpeed / maxTurnRate : BIGNUM; - - if (timeToTravelThatDist) - *timeToTravelThatDist = (minSpeed > 0.0f) ? (minTurnRadius / minSpeed) : 0.0f; - - return minTurnRadius; -} - - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - if (getIsDownhillOnly() && obj->getPosition()->z < goalPos.z) - { - return; - } - - Real maxAcceleration = getMaxAcceleration( obj->getBodyModule()->getDamageState() ); - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState() ); - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - // Locomotion for infantry. - // - // Orient toward goal position - // - Real actualSpeed = physics->getForwardSpeed2D(); - Real angle = obj->getOrientation(); -// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); -// Real desiredAngle = angle + relAngle; - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); - - if (m_template->m_wanderWidthFactor != 0.0f) { - Real angleLimit = PI/8 * m_template->m_wanderWidthFactor; - // This is the wander offline code - it forces the desired angle away from the goal, so we wander back & forth. jba. - if (getFlag(OFFSET_INCREASING)) { - m_angleOffset += m_offsetIncrement*actualSpeed; - if (m_angleOffset > angleLimit) { - setFlag(OFFSET_INCREASING, false); - } - } else { - m_angleOffset -= m_offsetIncrement*actualSpeed; - if (m_angleOffset<-angleLimit) { - setFlag(OFFSET_INCREASING, true); - } - } - desiredAngle = normalizeAngle(desiredAngle+m_angleOffset); - } - - Real relAngle = stdAngleDiff(desiredAngle, angle); - locoUpdate_moveTowardsAngle(obj, desiredAngle); - - // - // Modulate speed according to turning. The more we have to turn, the slower we go - // - const Real QUARTERPI = PI / 4.0f; - Real angleCoeff = (Real)fabs( relAngle ) / (QUARTERPI); - if (angleCoeff > 1.0f) - angleCoeff = 1.0; - - Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; - - //Real slowDownDist = (actualSpeed - m_template->m_minSpeed) / getBraking(); - Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); - if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - { - goalSpeed = m_template->m_minSpeed; - } - - - - // - // Maintain goal speed - // - Real speedDelta = goalSpeed - actualSpeed; - if (speedDelta != 0.0f) - { - Real mass = physics->getMass(); - Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - const Coord3D *dir = obj->getUnitDirectionVector2D(); - - Coord3D force; - force.x = accelForce * dir->x; - force.y = accelForce * dir->y; - force.z = 0.0f; - - - - // apply forces to object - physics->applyMotiveForce( &force ); - } -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - Real maxAcceleration = getMaxAcceleration( obj->getBodyModule()->getDamageState() ); - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState() ); - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - // Locomotion for climbing infantry. - - - Bool moveBackwards = false; - - Real dx, dy, dz; - - Coord3D pos = *obj->getPosition(); - - dx = pos.x - goalPos.x; - dy = pos.y - goalPos.y; - dz = pos.z - goalPos.z; - if (dz*dz > sqr(PATHFIND_CELL_SIZE_F)) { - setFlag(CLIMBING, true); - } - if (fabs(dz)<1) { - setFlag(CLIMBING, false); - } - - - //setFlag(CLIMBING, true); - - if (getFlag(CLIMBING)) { - Coord3D delta = goalPos; - delta.x -= pos.x; - delta.y -= pos.y; - delta.z = 0; - delta.normalize(); - delta.x += pos.x; - delta.y += pos.y; - delta.z = TheTerrainLogic->getGroundHeight(delta.x, delta.y); - if (delta.z < pos.z-0.1) { - moveBackwards = true; - } - - Real groundSlope = fabs(delta.z - pos.z); - if (groundSlope<1.0f) groundSlope = 1.0f; - - if (groundSlope>1.0f) { - desiredSpeed /= groundSlope*4; - } - } - setFlag(MOVING_BACKWARDS, moveBackwards); - - // - // Orient toward goal position - // - Real angle = obj->getOrientation(); -// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); -// Real desiredAngle = angle + relAngle; - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); - Real relAngle = stdAngleDiff(desiredAngle, angle); - - if (moveBackwards) { - desiredAngle = stdAngleDiff(desiredAngle, PI); - relAngle = stdAngleDiff(desiredAngle, angle); - } - - locoUpdate_moveTowardsAngle(obj, desiredAngle); - - // - // Modulate speed according to turning. The more we have to turn, the slower we go - // - const Real QUARTERPI = PI / 4.0f; - Real angleCoeff = (Real)fabs( relAngle ) / (QUARTERPI); - if (angleCoeff > 1.0f) - angleCoeff = 1.0; - - Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; - - Real actualSpeed = physics->getForwardSpeed2D(); - - if (moveBackwards) { - actualSpeed = -actualSpeed; - } - - //Real slowDownDist = (actualSpeed - m_template->m_minSpeed) / getBraking(); - Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); - if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - { - goalSpeed = m_template->m_minSpeed; - } - - // - // Maintain goal speed - // - Real speedDelta = goalSpeed - actualSpeed; - if (moveBackwards) { - speedDelta = -goalSpeed+actualSpeed; - } - if (speedDelta != 0.0f) - { - Real mass = physics->getMass(); - Real acceleration; - if (moveBackwards) { - acceleration = (speedDelta < 0.0f) ? -maxAcceleration : getBraking(); - } else { - acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); - } - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - const Coord3D *dir = obj->getUnitDirectionVector2D(); - - Coord3D force; - force.x = accelForce * dir->x; - force.y = accelForce * dir->y; - force.z = 0.0f; - - // apply forces to object - physics->applyMotiveForce( &force ); - } -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ -#ifdef CIRCLE_FOR_LANDING - if (m_circleThresh > 0.0f) - { - // if we are going a mostly-vertical maneuver, circle in order to - // gain/lose altitude, then resume course... - const Coord3D* pos = obj->getPosition(); - Real dx = goalPos.x - pos->x; - Real dy = goalPos.y - pos->y; - Real dz = goalPos.z - pos->z; - if (fabs(dz) > m_circleThresh) - { - // aim for the spot on the opposite side of the circle. - - // find the direction towards our goal pos - Real angleTowardPos = - (isNearlyZero(dx) && isNearlyZero(dy)) ? - obj->getOrientation() : - atan2(dy, dx); - - Real aimDir = (PI - PI/8); - angleTowardPos += aimDir; - - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real turnRadius = calcMinTurnRadius(bdt, NULL) * 4; - - // project a spot "radius" dist away from it, in that dir - Coord3D desiredPos = goalPos; - desiredPos.x += Cos(angleTowardPos) * turnRadius; - desiredPos.y += Sin(angleTowardPos) * turnRadius; - moveTowardsPositionOther(obj, physics, desiredPos, 0, desiredSpeed); - return; - } - } -#endif - - // handle the 2D component. - moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionHover(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - // handle the 2D component. - moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - - // Only hover locomotors care about their OverWater special effects. (OverWater also affects speed, so this is not a client thing) - Coord3D newPosition = *obj->getPosition(); - if( TheTerrainLogic->isUnderwater( newPosition.x, newPosition.y ) ) - { - if( ! getFlag( OVER_WATER ) ) - { - // Change my model condition because I used to not be over water, but now I am - setFlag( OVER_WATER, TRUE ); - obj->setModelConditionState( MODELCONDITION_OVER_WATER ); - } - } - else - { - if( getFlag( OVER_WATER ) ) - { - // Here, I was, but now I'm not - setFlag( OVER_WATER, FALSE ); - obj->clearModelConditionState( MODELCONDITION_OVER_WATER ); - } - } -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - - Real maxForwardSpeed = getMaxSpeedForCondition(bdt); - desiredSpeed = clamp(m_template->m_minSpeed, desiredSpeed, maxForwardSpeed); - Real actualForwardSpeed = physics->getForwardSpeed3D(); - - if (getBraking() > 0) - { - //Real slowDownDist = (actualForwardSpeed - m_template->m_minSpeed) / getBraking(); - Real slowDownDist = calcSlowDownDist(actualForwardSpeed, m_template->m_minSpeed, getBraking()); - if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - desiredSpeed = m_template->m_minSpeed; - } - - Coord3D localGoalPos = goalPos; -#ifdef USE_ZDIR_DAMPING - Real zDirDamping = 0.0f; -#endif - - //out of the handleBehaviorZ() function - Coord3D pos = *obj->getPosition(); - if( m_preferredHeight != 0.0f && !getFlag(PRECISE_Z_POS) ) - { - // If we have a preferred flight height, and we haven't been told explicitly to ignore it... - Real surfaceHt = getSurfaceHtAtPt(pos.x, pos.y); - localGoalPos.z = m_preferredHeight + surfaceHt; -// localGoalPos.z = goalPos.z; - Real delta = localGoalPos.z - pos.z; - delta *= getPreferredHeightDamping(); - localGoalPos.z = pos.z + delta; - -#ifdef USE_ZDIR_DAMPING - // closer we get to the preferred height, less we adjust z-thrust, - // so we tend to "level out" at that height. we don't use this till - // below, but go ahead and calc it now... - Real MAX_VERTICAL_DAMP_RANGE = m_preferredHeight * 0.5; - delta = fabs(delta); - if (delta > MAX_VERTICAL_DAMP_RANGE) - delta = MAX_VERTICAL_DAMP_RANGE; - zDirDamping = 1.0f - (delta / MAX_VERTICAL_DAMP_RANGE); -#endif - } - - Vector3 forwardDir = obj->getTransformMatrix()->Get_X_Vector(); - - // Maintain goal speed - Real forwardSpeedDelta = desiredSpeed - actualForwardSpeed; - Real maxAccel = (forwardSpeedDelta > 0.0f || getBraking() == 0) ? getMaxAcceleration(bdt) : -getBraking(); - Real maxTurnRate = getMaxTurnRate(bdt); - - // what direction do we need to thrust in, in order to reach the goalpos? - Vector3 desiredThrustDir; - calcDirectionToApplyThrust(obj, physics, localGoalPos, maxAccel, desiredThrustDir); - - // we might not be able to thrust in that dir, so thrust as closely as we can - Real maxThrustAngle = (maxTurnRate > 0) ? (m_template->m_maxThrustAngle) : 0; - Vector3 thrustDir; - Real thrustAngle = tryToRotateVector3D(maxThrustAngle, forwardDir, desiredThrustDir, thrustDir); - - // note that we are trying to orient in the direction of our vel, not the dir of our thrust. - if (!isNearlyZero(physics->getVelocityMagnitude())) - { - const Coord3D* veltmp = physics->getVelocity(); - Vector3 vel(veltmp->x, veltmp->y, veltmp->z); - Bool adjust = true; - if( obj->getStatusBits().test( OBJECT_STATUS_BRAKING ) ) - { - //Real closeInDist = 150.0f; // TODO: get/set this from missileAI? - //Real af = 1.0f - __min((onPathDistToGoal / closeInDist), 1.0); - - //if (af > 0.0f) { - - // vel.Set( - // vel.X * (1.0f - af) + (goalPos.x - pos.x) * af, - // vel.Y * (1.0f - af) + (goalPos.y - pos.y) * af, - // vel.Z * (1.0f - af) + (goalPos.z - pos.z) * af - // ); - // if (isNearlyZero(sqr(vel.X) + sqr(vel.Y) + sqr(vel.Z))) { - // // we are at target. - // adjust = false; - // } - // maxTurnRate = (1.0f + (af * 2.0f) ) * maxTurnRate; - //} - - // DEBUG_LOG((">>> moveTowardsPositionThrust - Braking - maxTurnRate = %f\n", maxTurnRate)); - - // align to target, cause that's where we're going anyway. - - vel.Set(goalPos.x - pos.x, goalPos.y-pos.y, goalPos.z-pos.z); - if (isNearlyZero(sqr(vel.X)+sqr(vel.Y)+sqr(vel.Z))) { - // we are at target. - adjust = false; - } - maxTurnRate = 3*maxTurnRate; - } -#ifdef USE_ZDIR_DAMPING - if (zDirDamping != 0.0f) - { - Vector3 vel2D(veltmp->x, veltmp->y, 0); - // no need to normalize -- this call does that internally - tryToRotateVector3D(-zDirDamping, vel, vel2D, vel); - } -#endif - if (adjust) { - /*Real orient =*/ tryToOrientInThisDirection3D(obj, maxTurnRate, vel); - } - } - - if (forwardSpeedDelta != 0.0f || thrustAngle != 0.0f) - { - if (maxForwardSpeed <= 0.0f) - { - maxForwardSpeed = 0.01f; // In some cases, this is 0, hack for now. jba. - } - Real damping = clamp(0.0f, maxAccel / maxForwardSpeed, 1.0f); - Vector3 curVel(physics->getVelocity()->x, physics->getVelocity()->y, physics->getVelocity()->z); - - Vector3 accelVec = thrustDir * maxAccel - curVel * damping; - //DEBUG_LOG(("accel %f (max %f) vel %f (max %f) damping %f\n",accelVec.Length(),maxAccel,curVel.Length(),maxForwardSpeed,damping)); - - Real mass = physics->getMass(); - - Coord3D force; - force.x = mass * accelVec.X; - force.y = mass * accelVec.Y; - force.z = mass * accelVec.Z; - - // apply forces to object - physics->applyMotiveForce( &force ); - } -} - -//------------------------------------------------------------------------------------------------- -/*static*/ Real Locomotor::getSurfaceHtAtPt(Real x, Real y) -{ - Real ht = 0; - - Real z,waterZ; - if (TheTerrainLogic->isUnderwater(x, y, &waterZ, &z)) { - ht += waterZ; - } else { - ht += z; - } - - return ht; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::calcLiftToUseAtPt(Object* obj, PhysicsBehavior *physics, Real curZ, Real surfaceAtPt, Real preferredHeight) -{ - /* - take the classic equation: - - x = x0 + v*t + 0.5*a*t^2 - - and solve for acceleration. - */ - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxGrossLift = getMaxLift(bdt); - Real maxNetLift = maxGrossLift + TheGlobalData->m_gravity; // note that gravity is always negative. - if (maxNetLift < 0) - maxNetLift = 0; - Real curVelZ = physics->getVelocity()->z; - // going down, braking is limited by net lift; going up, braking is limited by gravity - Real maxAccel; - if (getFlag(ULTRA_ACCURATE)) - maxAccel = (curVelZ < 0) ? 2*maxNetLift : -2*maxNetLift; - else - maxAccel = (curVelZ < 0) ? maxNetLift : TheGlobalData->m_gravity; - // see how far we need to slow to dead stop, given max braking - Real desiredAccel; - const Real TINY_ACCEL = 0.001f; - if (fabs(maxAccel) > TINY_ACCEL) - { - Real deltaZ = preferredHeight - curZ; - // calc how far it will take for us to go from cur speed to zero speed, at max accel. - // Real brakeDist = calcSlowDownDist(curVelZ, 0, maxAccel); - // in theory, the above is the correct calculation, but in practice, - // doesn't work in some situations (eg, opening of USA01 map). Why, I dunno. - // But for now I have gone back to the old, looks-incorrect-to-me-but-works calc. (srj) - Real brakeDist = (sqr(curVelZ) / fabs(maxAccel)); - if (fabs(brakeDist) > fabs(deltaZ)) - { - // if the dist-to-accel (or dist-to-brake) is further than the dist-to-go, - // use the max accel. - desiredAccel = maxAccel; - } - else if (fabs(curVelZ) > m_template->m_speedLimitZ) - { - // or, if we're going too fast, limit it here. - desiredAccel = m_template->m_speedLimitZ - curVelZ; - } - else - { - // ok, figure out the correct accel to use to get us there at zero. - // - // dz = v t + 0.5 a t^2 - // thus - // a = 2(dz - v t)/t^2 - // and - // t = (-v +- sqrt(v*v + 2*a*dz))/a - // - // but if we assume t=1, then - // a=2(dz-v) - // then, plug it back in and see if t is really 1... - desiredAccel = 2.0f * (deltaZ - curVelZ); - } - } - else - { - desiredAccel = 0.0f; - } - Real liftToUse = desiredAccel - TheGlobalData->m_gravity; - if (getFlag(ULTRA_ACCURATE)) - { - // in ultra-accurate mode, we allow cheating. - const Real UP_FACTOR = 3.0f; - if (liftToUse > UP_FACTOR*maxGrossLift) - liftToUse = UP_FACTOR*maxGrossLift; - // srj sez: we used to clip lift to zero here (not allowing neg lift). - // however, I now think that allowing neg lift in ultra-accurate mode is - // a good and desirable thing; in particular, it enables jets to complete - // "short" landings more accurately (previously they sometimes would "float" - // down, which sucked.) if you need to bump this back to zero, check it carefully... - else if (liftToUse < -maxGrossLift) - liftToUse = -maxGrossLift; - } - else - { - if (liftToUse > maxGrossLift) - liftToUse = maxGrossLift; - else if (liftToUse < 0.0f) - liftToUse = 0.0f; - } - - return liftToUse; -} - -//------------------------------------------------------------------------------------------------- -PhysicsTurningType Locomotor::rotateObjAroundLocoPivot(Object* obj, const Coord3D& goalPos, - Real maxTurnRate, Real *relAngle) -{ - Real angle = obj->getOrientation(); - Real offset = getTurnPivotOffset(); - - PhysicsTurningType turn = TURN_NONE; - - if (getFlag(IS_BRAKING)) offset = 0.0f; // When braking we do exact movement towards goal, instead of physics. - //Rotating about pivot moves the object, and can make us miss our goal, so it is disabled. jba. - if (offset != 0.0f) - { - Real radius = obj->getGeometryInfo().getBoundingCircleRadius(); - Real turnPointOffset = offset * radius; - - Coord3D turnPos = *obj->getPosition(); - const Coord3D* dir = obj->getUnitDirectionVector2D(); - turnPos.x += dir->x * turnPointOffset; - turnPos.y += dir->y * turnPointOffset; - Real dx =goalPos.x - turnPos.x; - Real dy = goalPos.y - turnPos.y; - // If we are very close to the goal, we twitch due to rounding error. So just return. jba. - if (fabs(dx)<0.1f && fabs(dy)<0.1f) return TURN_NONE; - Real desiredAngle = atan2(dy, dx); - Real amount = stdAngleDiff(desiredAngle, angle); - if (relAngle) *relAngle = amount; - if (amount>maxTurnRate) { - amount = maxTurnRate; - turn = TURN_POSITIVE; - } else if (amount < -maxTurnRate) { - amount = -maxTurnRate; - turn = TURN_NEGATIVE; - } else { - turn = TURN_NONE; - } - -#if 0 - Coord3D desiredPos = *obj->getPosition(); // well, desired Dir, anyway - desiredPos.x += Cos(angle + amount) * radius; - desiredPos.y += Sin(angle + amount) * radius; - - - // so, the thing is, we want to rotate ourselves so that our *center* is rotated - // by the given amount, but the rotation must be around turnPos. so do a little - // back-calculation. - Real angleDesiredForTurnPos = atan2(desiredPos.y - turnPos.y, desiredPos.x - turnPos.x); - amount = angleDesiredForTurnPos - angle; -#endif - /// @todo srj -- there's probably a more efficient & more direct way to do this. find it. - Matrix3D mtx; - Matrix3D tmp(1); - tmp.Translate(turnPos.x, turnPos.y, 0); - tmp.In_Place_Pre_Rotate_Z(amount); - tmp.Translate(-turnPos.x, -turnPos.y, 0); - - mtx.mul(tmp, *obj->getTransformMatrix()); - - obj->setTransformMatrix(&mtx); - } - else - { - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); - Real amount = stdAngleDiff(desiredAngle, angle); - if (relAngle) *relAngle = amount; - if (amount>maxTurnRate) { - amount = maxTurnRate; - turn = TURN_POSITIVE; - } else if (amount < -maxTurnRate) { - amount = -maxTurnRate; - turn = TURN_NEGATIVE; - } else { - turn = TURN_NONE; - } - obj->setOrientation( normalizeAngle(angle + amount) ); - } - return turn; -} - -//------------------------------------------------------------------------------------------------- -/* - return true if we can maintain the position without being called every frame (eg, we are - resting on the ground), false if not (eg, we are hovering or circling) -*/ -Bool Locomotor::handleBehaviorZ(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos) -{ - Bool requiresConstantCalling = TRUE; - - // keep the agent aligned on the terrain - switch(m_template->m_behaviorZ) - { - case Z_NO_Z_MOTIVE_FORCE: - // nothing to do. - requiresConstantCalling = FALSE; - break; - - case Z_SEA_LEVEL: - requiresConstantCalling = TRUE; - if( !obj->isDisabledByType( DISABLED_HELD ) ) - { - Coord3D pos = *obj->getPosition(); - Real waterZ; - if (TheTerrainLogic->isUnderwater(pos.x, pos.y, &waterZ)) { - pos.z = waterZ; - } else { - pos.z = TheTerrainLogic->getLayerHeight(pos.x, pos.y, obj->getLayer()); - } - obj->setPosition(&pos); - } - break; - - case Z_FIXED_SURFACE_RELATIVE_HEIGHT: - case Z_FIXED_ABSOLUTE_HEIGHT: - requiresConstantCalling = TRUE; - { - Coord3D pos = *obj->getPosition(); - Bool surfaceRel = (m_template->m_behaviorZ == Z_FIXED_SURFACE_RELATIVE_HEIGHT); - Real surfaceHt = surfaceRel ? getSurfaceHtAtPt(pos.x, pos.y) : 0.0f; - pos.z = m_preferredHeight + (surfaceRel ? surfaceHt : 0); - obj->setPosition(&pos); - } - break; - - case Z_RELATIVE_TO_GROUND_AND_BUILDINGS: - requiresConstantCalling = TRUE; - { - // srj sez: use getGroundOrStructureHeight(), because someday it will cache building heights... - Coord3D pos = *obj->getPosition(); - Real surfaceHt = ThePartitionManager->getGroundOrStructureHeight(pos.x, pos.y); - - pos.z = m_preferredHeight + surfaceHt; - - obj->setPosition(&pos); - - } - break; - case Z_SMOOTH_RELATIVE_TO_HIGHEST_LAYER: - requiresConstantCalling = TRUE; - { - if (m_preferredHeight != 0.0f || getFlag(PRECISE_Z_POS)) - { - Coord3D pos = *obj->getPosition(); - - // srj sez: if we aren't on the ground, never find the ground layer - PathfindLayerEnum layerAtDest = obj->getLayer(); - if (layerAtDest == LAYER_GROUND) - layerAtDest = TheTerrainLogic->getHighestLayerForDestination( &pos ); - - Real surfaceHt; - Coord3D normal; - const Bool clip = false; // return the height, even if off the edge of the bridge proper. - surfaceHt = TheTerrainLogic->getLayerHeight( pos.x, pos.y, layerAtDest, &normal, clip ); - - Real preferredHeight = m_preferredHeight + surfaceHt; - if (getFlag(PRECISE_Z_POS)) - preferredHeight = goalPos.z; - - Real delta = preferredHeight - pos.z; - delta *= getPreferredHeightDamping(); - preferredHeight = pos.z + delta; - - Real liftToUse = calcLiftToUseAtPt(obj, physics, pos.z, surfaceHt, preferredHeight); - - //DEBUG_LOG(("HandleBZ %d LiftToUse %f\n",TheGameLogic->getFrame(),liftToUse)); - if (liftToUse != 0.0f) - { - Coord3D force; - force.x = 0.0f; - force.y = 0.0f; - force.z = liftToUse * physics->getMass(); - physics->applyMotiveForce(&force); - } - } - } - break; - - case Z_SURFACE_RELATIVE_HEIGHT: - case Z_ABSOLUTE_HEIGHT: - requiresConstantCalling = TRUE; - { - if (m_preferredHeight != 0.0f || getFlag(PRECISE_Z_POS)) - { - Coord3D pos = *obj->getPosition(); - - Bool surfaceRel = (m_template->m_behaviorZ == Z_SURFACE_RELATIVE_HEIGHT); - Real surfaceHt = surfaceRel ? getSurfaceHtAtPt(pos.x, pos.y) : 0.0f; - Real preferredHeight = m_preferredHeight + (surfaceRel ? surfaceHt : 0); - if (getFlag(PRECISE_Z_POS)) - preferredHeight = goalPos.z; - - Real delta = preferredHeight - pos.z; - delta *= getPreferredHeightDamping(); - preferredHeight = pos.z + delta; - - Real liftToUse = calcLiftToUseAtPt(obj, physics, pos.z, surfaceHt, preferredHeight); - - //DEBUG_LOG(("HandleBZ %d LiftToUse %f\n",TheGameLogic->getFrame(),liftToUse)); - if (liftToUse != 0.0f) - { - Coord3D force; - force.x = 0.0f; - force.y = 0.0f; - force.z = liftToUse * physics->getMass(); - physics->applyMotiveForce(&force); - } - } - } - break; - } - - return requiresConstantCalling; -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxAcceleration = getMaxAcceleration(bdt); - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - Real maxSpeed = getMaxSpeedForCondition(bdt); - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - Real goalSpeed = desiredSpeed; - Real actualSpeed = physics->getForwardSpeed2D(); - - // Locomotion for other things, ie don't know what it is jba :) - // - // Orient toward goal position - // exception: if very close (ie, we could get there in 2 frames or less),\ - // and ULTRA_ACCURATE, just slide into place - // - const Coord3D* pos = obj->getPosition(); - Coord3D dirToApplyForce = *obj->getUnitDirectionVector2D(); - -//DEBUG_ASSERTLOG(!getFlag(ULTRA_ACCURATE),("thresh %f %f (%f %f)\n", -//fabs(goalPos.y - pos->y),fabs(goalPos.x - pos->x), -//fabs(goalPos.y - pos->y)/goalSpeed,fabs(goalPos.x - pos->x)/goalSpeed)); - if (getFlag(ULTRA_ACCURATE) && - fabs(goalPos.y - pos->y) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor && - fabs(goalPos.x - pos->x) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor) - { - // don't turn, just slide in the right direction - physics->setTurning(TURN_NONE); - dirToApplyForce.x = goalPos.x - pos->x; - dirToApplyForce.y = goalPos.y - pos->y; - dirToApplyForce.z = 0.0f; - dirToApplyForce.normalize(); - } - else - { - PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos); - physics->setTurning(rotating); - } - - if (!getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - { - Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); - if (onPathDistToGoal < slowDownDist) - { - goalSpeed = m_template->m_minSpeed; - } - } - - // - // Maintain goal speed - // - Real speedDelta = goalSpeed - actualSpeed; - if (speedDelta != 0.0f) - { - Real mass = physics->getMass(); - Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - Coord3D force; - force.x = accelForce * dirToApplyForce.x; - force.y = accelForce * dirToApplyForce.y; - force.z = 0.0f; - - // apply forces to object - physics->applyMotiveForce( &force ); - } - -} - - -//------------------------------------------------------------------------------------------------- -/* - return true if we can maintain the position without being called every frame (eg, we are - resting on the ground), false if not (eg, we are hovering or circling) -*/ -Bool Locomotor::locoUpdate_maintainCurrentPosition(Object* obj) -{ - if (!getFlag(MAINTAIN_POS_IS_VALID)) - { - m_maintainPos = *obj->getPosition(); - setFlag(MAINTAIN_POS_IS_VALID, true); - } - - m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; - setFlag(IS_BRAKING, false); - PhysicsBehavior *physics = obj->getPhysics(); - if (physics == NULL) - { - DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); - return TRUE; - } - -#ifdef DEBUG_OBJECT_ID_EXISTS -// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_maintainCurrentPosition %f %f %f, speed %f (%f)\n",m_maintainPos.x,m_maintainPos.y,m_maintainPos.z,physics->getSpeed(),physics->getForwardSpeed2D())); -#endif - - Bool requiresConstantCalling = TRUE; // assume the worst. - switch (m_template->m_appearance) - { - case LOCO_THRUST: - maintainCurrentPositionThrust(obj, physics); - requiresConstantCalling = TRUE; - break; - case LOCO_LEGS_TWO: - maintainCurrentPositionLegs(obj, physics); - requiresConstantCalling = FALSE; - break; - case LOCO_CLIMBER: - maintainCurrentPositionLegs(obj, physics); - requiresConstantCalling = FALSE; - break; - case LOCO_WHEELS_FOUR: - case LOCO_MOTORCYCLE: - maintainCurrentPositionWheels(obj, physics); - requiresConstantCalling = FALSE; - break; - case LOCO_TREADS: - maintainCurrentPositionTreads(obj, physics); - requiresConstantCalling = FALSE; - break; - case LOCO_HOVER: - maintainCurrentPositionHover(obj, physics); - requiresConstantCalling = TRUE; - break; - case LOCO_WINGS: - maintainCurrentPositionWings(obj, physics); - requiresConstantCalling = TRUE; - break; - case LOCO_OTHER: - default: - maintainCurrentPositionOther(obj, physics); - requiresConstantCalling = TRUE; - break; - } - - // but we do need to do this even if not moving, for hovering/Thrusting things. - if (handleBehaviorZ(obj, physics, m_maintainPos)) - requiresConstantCalling = TRUE; - - return requiresConstantCalling; -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::maintainCurrentPositionThrust(Object* obj, PhysicsBehavior *physics) -{ - DEBUG_ASSERTCRASH(getFlag(MAINTAIN_POS_IS_VALID), ("invalid maintain pos")); - /// @todo srj -- should these also use the "circling radius" stuff, like wings? - moveTowardsPositionThrust(obj, physics, m_maintainPos, 0, getMinSpeed()); -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physics) -{ - DEBUG_ASSERTCRASH(getFlag(MAINTAIN_POS_IS_VALID), ("invalid maintain pos")); - physics->setTurning(TURN_NONE); - if (physics->isMotive() && obj->isAboveTerrain()) // no need to stop something that isn't moving (or is just sitting on the ground) - { - - // aim for the spot on the opposite side of the circle. - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real turnRadius = m_template->m_circlingRadius; - if (turnRadius == 0.0f) - turnRadius = calcMinTurnRadius(bdt, NULL); - - // find the direction towards our "maintain pos" - const Coord3D* pos = obj->getPosition(); - Real dx = m_maintainPos.x - pos->x; - Real dy = m_maintainPos.y - pos->y; - Real angleTowardMaintainPos = - (isNearlyZero(dx) && isNearlyZero(dy)) ? - obj->getOrientation() : - atan2(dy, dx); - - Real aimDir = (PI - PI/8); - if (turnRadius < 0) - { - turnRadius = -turnRadius; - aimDir = -aimDir; - } - angleTowardMaintainPos += aimDir; - - // project a spot "radius" dist away from it, in that dir - Coord3D desiredPos = m_maintainPos; - desiredPos.x += Cos(angleTowardMaintainPos) * turnRadius; - desiredPos.y += Sin(angleTowardMaintainPos) * turnRadius; - moveTowardsPositionWings(obj, physics, desiredPos, 0, m_template->m_minSpeed); - } -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physics) -{ - physics->setTurning(TURN_NONE); - if (physics->isMotive()) // no need to stop something that isn't moving. - { - DEBUG_ASSERTCRASH(m_template->m_minSpeed == 0.0f, ("HOVER should always have zero minSpeeds (otherwise, they WING)")); - - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxAcceleration = getMaxAcceleration(bdt); - Real actualSpeed = physics->getForwardSpeed2D(); - // - // Stop - // - Real minSpeed = max( 1.0E-10f, m_template->m_minSpeed ); - Real speedDelta = minSpeed - actualSpeed; - if (fabs(speedDelta) > minSpeed) - { - Real mass = physics->getMass(); - Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - const Coord3D *dir = obj->getUnitDirectionVector2D(); - - Coord3D force; - force.x = accelForce * dir->x; - force.y = accelForce * dir->y; - force.z = 0.0f; - - - // Apply a random kick (if applicable) to dirty-up visually. - // The idea is that chopper pilots have to do course corrections all the time - // Because of changes in wind, pressure, etc. - // Those changes are added here, then the - - - - // apply forces to object - physics->applyMotiveForce( &force ); - } - } - -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::maintainCurrentPositionOther(Object* obj, PhysicsBehavior *physics) -{ - - physics->setTurning(TURN_NONE); - if (physics->isMotive()) // no need to stop something that isn't moving. - { - physics->scrubVelocity2D(0); // stop. - } - -} - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- -LocomotorSet::LocomotorSet() -{ - m_locomotors.clear(); - m_validLocomotorSurfaces = 0; - m_downhillOnly = FALSE; - -} - -//------------------------------------------------------------------------------------------------- -LocomotorSet::LocomotorSet(const LocomotorSet& that) -{ - DEBUG_CRASH(("unimplemented")); -} - -//------------------------------------------------------------------------------------------------- -LocomotorSet& LocomotorSet::operator=(const LocomotorSet& that) -{ - if (this != &that) - { - DEBUG_CRASH(("unimplemented")); - } - return *this; -} - -//------------------------------------------------------------------------------------------------- -LocomotorSet::~LocomotorSet() -{ - clear(); -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void LocomotorSet::crc( Xfer *xfer ) -{ - -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void LocomotorSet::xfer( Xfer *xfer ) -{ - // version - const XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // count of vector - UnsignedShort count = m_locomotors.size(); - xfer->xferUnsignedShort( &count ); - - // data - if (xfer->getXferMode() == XFER_SAVE) - { - for (LocomotorVector::iterator it = m_locomotors.begin(); it != m_locomotors.end(); ++it) - { - Locomotor* loco = *it; - AsciiString name = loco->getTemplateName(); - xfer->xferAsciiString(&name); - xfer->xferSnapshot(loco); - } - } - else if (xfer->getXferMode() == XFER_LOAD) - { - // vector should be empty at this point - if (m_locomotors.empty() == FALSE) - { - DEBUG_CRASH(( "LocomotorSet::xfer - vector is not empty, but should be\n" )); - throw XFER_LIST_NOT_EMPTY; - } - - for (UnsignedShort i = 0; i < count; ++i) - { - AsciiString name; - xfer->xferAsciiString(&name); - - const LocomotorTemplate* lt = TheLocomotorStore->findLocomotorTemplate(NAMEKEY(name)); - if (lt == NULL) - { - DEBUG_CRASH(( "LocomotorSet::xfer - template %s not found\n", name.str() )); - throw XFER_UNKNOWN_STRING; - } - - Locomotor* loco = TheLocomotorStore->newLocomotor(lt); - xfer->xferSnapshot(loco); - m_locomotors.push_back(loco); - } - } - - xfer->xferInt(&m_validLocomotorSurfaces); - xfer->xferBool(&m_downhillOnly); - -} - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void LocomotorSet::loadPostProcess( void ) -{ - -} // end loadPostProcess - -//------------------------------------------------------------------------------------------------- -void LocomotorSet::xferSelfAndCurLocoPtr(Xfer *xfer, Locomotor** loco) -{ - xfer->xferSnapshot(this); - - if (xfer->getXferMode() == XFER_SAVE) - { - AsciiString name; - if (*loco) - name = (*loco)->getTemplateName(); - xfer->xferAsciiString(&name); - } - else if (xfer->getXferMode() == XFER_LOAD) - { - AsciiString name; - xfer->xferAsciiString(&name); - - if (name.isEmpty()) - { - *loco = NULL; - } - else - { - for (int i = 0; i < m_locomotors.size(); ++i) - { - if (m_locomotors[i]->getTemplateName() == name) - { - *loco = m_locomotors[i]; - return; - } - } - - DEBUG_CRASH(( "LocomotorSet::xfer - template %s not found\n", name.str() )); - throw XFER_UNKNOWN_STRING; - } - } -} - -//------------------------------------------------------------------------------------------------- -void LocomotorSet::clear() -{ - for (int i = 0; i < m_locomotors.size(); ++i) - { - if (m_locomotors[i]) - m_locomotors[i]->deleteInstance(); - } - m_locomotors.clear(); - m_validLocomotorSurfaces = 0; - m_downhillOnly = FALSE; -} - -//------------------------------------------------------------------------------------------------- -void LocomotorSet::addLocomotor(const LocomotorTemplate* lt) -{ - Locomotor* loco = TheLocomotorStore->newLocomotor(lt); - if (loco) - { - m_locomotors.push_back(loco); - m_validLocomotorSurfaces |= loco->getLegalSurfaces(); - if (loco->getIsDownhillOnly()) - { - m_downhillOnly = TRUE; - } - else // Previous locos were gravity only, but this one isn't! - { - DEBUG_ASSERTCRASH(!m_downhillOnly,("LocomotorSet, YOU CAN NOT MIX DOWNHILL-ONLY LOCOMOTORS WITH NON-DOWNHILL-ONLY ONES.")); - } - - } -} - -//------------------------------------------------------------------------------------------------- -Locomotor* LocomotorSet::findLocomotor(LocomotorSurfaceTypeMask t) -{ - for (LocomotorVector::iterator it = m_locomotors.begin(); it != m_locomotors.end(); ++it) - { - Locomotor* curLocomotor = *it; - if (curLocomotor && (curLocomotor->getLegalSurfaces() & t)) - return curLocomotor; - } - return NULL; -} - - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: Locomotor.cpp /////////////////////////////////////////////////////////////////////////////// +// Author: Steven Johnson, Feb 2002 +// Desc: Locomotor descriptions +/////////////////////////////////////////////////////////////////////////////////////////////////// + + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#define DEFINE_SURFACECATEGORY_NAMES +#define DEFINE_LOCO_Z_NAMES +#define DEFINE_LOCO_APPEARANCE_NAMES + +#include "Common/INI.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/PartitionManager.h" +#include "GameLogic/Locomotor.h" +#include "GameLogic/Object.h" +#include "GameLogic/AI.h" +#include "GameLogic/AIPathfind.h" +#include "GameLogic/Module/PhysicsUpdate.h" +#include "GameLogic/Module/BodyModule.h" +#include "GameLogic/Module/AIUpdate.h" + +#ifdef RTS_INTERNAL +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +static const Real DONUT_TIME_DELAY_SECONDS=2.5f; +static const Real DONUT_DISTANCE=4.0*PATHFIND_CELL_SIZE_F; + + +#define MAX_BRAKING_FACTOR 5.0f +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +LocomotorStore *TheLocomotorStore = NULL; ///< the Locomotor store definition + +const Real BIGNUM = 99999.0f; + +static const char *TheLocomotorPriorityNames[] = +{ + "MOVES_BACK", + "MOVES_MIDDLE", + "MOVES_FRONT", + + NULL +}; + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +static Real calcSlowDownDist(Real curSpeed, Real desiredSpeed, Real maxBraking) +{ + Real delta = curSpeed - desiredSpeed; + if (delta <= 0) + return 0.0f; + + Real dist = (sqr(delta) / fabs(maxBraking)) * 0.5f; + + // use a little fudge so that things can stop "on a dime" more easily... + const Real FUDGE = 1.05f; + return dist * FUDGE; +} + +//----------------------------------------------------------------------------- +inline Bool isNearlyZero(Real a) +{ + const Real TINY_EPSILON = 0.001f; + return fabs(a) < TINY_EPSILON; +} + +//----------------------------------------------------------------------------- +inline Bool isNearly(Real a, Real val) +{ + const Real TINY_EPSILON = 0.001f; + return fabs(a - val) < TINY_EPSILON; +} + +//----------------------------------------------------------------------------- +// return the angle delta (in 3-space) we turned. +static Real tryToRotateVector3D( + Real maxAngle, // if negative, it's a percent (0...1) of the dist to rotate 'em + const Vector3& inCurDir, + const Vector3& inGoalDir, + Vector3& actualDir +) +{ + if (isNearlyZero(maxAngle)) + { + actualDir = inCurDir; + return 0.0f; + } + + Vector3 curDir = inCurDir; + curDir.Normalize(); + + Vector3 goalDir = inGoalDir; + goalDir.Normalize(); + + // dot of two unit vectors is cos of angle between them. + Real cosine = Vector3::Dot_Product(curDir, goalDir); + // bound it in case of numerical error + Real angleBetween = (Real)ACos(clamp(-1.0f, cosine, 1.0f)); + + if (maxAngle < 0) + { + maxAngle = -maxAngle * angleBetween; + if (isNearlyZero(maxAngle)) + { + actualDir = inCurDir; + return 0.0f; + } + } + + if (fabs(angleBetween) <= maxAngle) + { + // close enough + actualDir = goalDir; + } + else + { + // nah, try as much as we can in the right dir. + // we need to rotate around the axis perpendicular to these two vecs. + // but: cross of two vectors is the perpendicular axis! +#ifdef ALLOW_TEMPORARIES + Vector3 objCrossGoal = Vector3::Cross_Product(curDir, goalDir); + objCrossGoal.Normalize(); +#else + Vector3 objCrossGoal; + Vector3::Normalized_Cross_Product(curDir, goalDir, &objCrossGoal); +#endif + + angleBetween = maxAngle; + Matrix3D rotMtx(objCrossGoal, angleBetween); + actualDir = rotMtx.Rotate_Vector(curDir); + } + + return angleBetween; +} + +//------------------------------------------------------------------------------------------------- +static Real tryToOrientInThisDirection3D(Object* obj, Real maxTurnRate, const Vector3& desiredDir) +{ + Vector3 actualDir; + Real relAngle = tryToRotateVector3D(maxTurnRate, obj->getTransformMatrix()->Get_X_Vector(), desiredDir, actualDir); + if (relAngle != 0.0f) + { + Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z); + + Matrix3D newXform; + newXform.buildTransformMatrix( objPos, actualDir ); + + obj->setTransformMatrix( &newXform ); + } + return relAngle; +} + +//------------------------------------------------------------------------------------------------- +inline Real tryToOrientInThisDirection3D(Object* obj, Real maxTurnRate, const Coord3D* dir) +{ + return tryToOrientInThisDirection3D(obj, maxTurnRate, Vector3(dir->x, dir->y, dir->z)); +} + +//----------------------------------------------------------------------------- +static void calcDirectionToApplyThrust( + const Object* obj, + const PhysicsBehavior* physics, + const Coord3D& ingoalPos, + Real maxAccel, + Vector3& goalDir +) +{ + /* + our meta-goal here is to calculate the direction we should apply our motive force + in order to minimize the angle between (our velocity) and (direction towards goalpos). + + this is complicated by the fact that we generally have an intrinsic velocity already, + that must be accounted for, and by the fact that we can only apply force in our + forward-x-direction (with a thrust-angle-range), and (due to limited range) might not + be able to apply the force in the optimal direction! + */ + + // convert to Vector3, to use all its handy stuff + Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z); + Vector3 goalPos(ingoalPos.x, ingoalPos.y, ingoalPos.z); + + Vector3 vecToGoal = goalPos - objPos; + if (isNearlyZero(vecToGoal.Length2())) + { + // goal pos is essentially same as current pos, so just stay the same & return + goalDir = obj->getTransformMatrix()->Get_X_Vector(); + return; + } + + /* + get our cur vel into a useful Vector3 form + */ + Vector3 curVel(physics->getVelocity()->x, physics->getVelocity()->y, physics->getVelocity()->z); + + // add gravity to our vel so that we account for it in our calcs + curVel.Z += TheGlobalData->m_gravity; + + Bool foundSolution = false; + Real distToGoalSqr = vecToGoal.Length2(); + Real distToGoal = sqrt(distToGoalSqr); + Real curVelMagSqr = curVel.Length2(); + Real curVelMag = sqrt(curVelMagSqr); + Real maxAccelSqr = sqr(maxAccel); + + Real denom = curVelMagSqr - maxAccelSqr; + if (!isNearlyZero(denom)) + { + // solve the (greatly simplified) quadratic... + Real t = (distToGoal * (curVelMag + maxAccel)) / denom; + Real t2 = (distToGoal * (curVelMag - maxAccel)) / denom; + if (t >= 0 || t2 >= 0) + { + // choose the smallest positive t. + if (t < 0 || (t2 >= 0 && t2 < t)) + t = t2; + + // plug it in. + if (!isNearlyZero(t)) + { + goalDir.X = (vecToGoal.X / t) - curVel.X; + goalDir.Y = (vecToGoal.Y / t) - curVel.Y; + goalDir.Z = (vecToGoal.Z / t) - curVel.Z; + goalDir.Normalize(); + foundSolution = true; + } + } + } + if (!foundSolution) + { + // Doh... no (useful) solution. revert to dumb. + goalDir = vecToGoal; + goalDir.Normalize(); + } + +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +LocomotorTemplate::LocomotorTemplate() +{ + // these values mean "make the same as undamaged if not explicitly specified" + m_maxSpeedDamaged = -1.0f; + m_maxTurnRateDamaged = -1.0f; + m_accelerationDamaged = -1.0f; + m_liftDamaged = -1.0f; + + m_surfaces = 0; + m_maxSpeed = 0.0f; + m_maxTurnRate = 0.0f; + m_acceleration = 0.0f; + m_lift = 0.0f; + m_braking = BIGNUM; + m_minSpeed = 0.0f; + m_minTurnSpeed = BIGNUM; + m_behaviorZ = Z_NO_Z_MOTIVE_FORCE; + m_appearance = LOCO_OTHER; + m_movePriority = LOCO_MOVES_MIDDLE; + m_preferredHeight = 0; + m_preferredHeightDamping = 1.0f; + m_circlingRadius = 0; + + m_maxThrustAngle = 0; + m_speedLimitZ = 999999.0f; + m_extra2DFriction = 0.0f; + + m_accelPitchLimit = 0; + m_decelPitchLimit = 0; + m_bounceKick = 0; + +// m_pitchStiffness = 0; +// m_rollStiffness = 0; +// m_pitchDamping = 0; +// m_rollDamping = 0; +// it's highly unlikely you want zero for the defaults for stiffness and damping... (srj) +// for stiffness: stiffness of the "springs" in the suspension 0 = no stiffness, 1 = totally stiff (huh huh, he said "stiff") +// for damping: 0=perfect spring, bounces forever. 1=glued to terrain. + m_pitchStiffness = 0.1f; + m_rollStiffness = 0.1f; + m_pitchDamping = 0.9f; + m_rollDamping = 0.9f; + m_forwardVelCoef = 0; + m_pitchByZVelCoef = 0; + m_thrustRoll = 0.0f; + m_wobbleRate = 0.0f; + m_minWobble = 0.0f; + m_maxWobble = 0.0f; + m_lateralVelCoef = 0; + m_forwardAccelCoef = 0; + m_lateralAccelCoef = 0; + m_uniformAxialDamping = 1.0f; + m_turnPivotOffset = 0; + m_apply2DFrictionWhenAirborne = false; + m_downhillOnly = false; + m_allowMotiveForceWhileAirborne = false; + m_locomotorWorksWhenDead = false; + m_airborneTargetingHeight = INT_MAX; + m_stickToGround = false; + m_canMoveBackward = false; + m_hasSuspension = false; + m_wheelTurnAngle = 0; + m_maximumWheelExtension = 0; + m_maximumWheelCompression = 0; + m_closeEnoughDist = 1.0f; + m_isCloseEnoughDist3D = FALSE; + m_ultraAccurateSlideIntoPlaceFactor = 0.0f; + + m_wanderWidthFactor = 0.0f; + m_wanderLengthFactor = 1.0f; + m_wanderAboutPointRadius = 0.0f; + + m_rudderCorrectionDegree = 0.0f; + m_rudderCorrectionRate = 0.0f; + m_elevatorCorrectionDegree = 0.0f; + m_elevatorCorrectionRate = 0.0f; + +} + +//------------------------------------------------------------------------------------------------- +LocomotorTemplate::~LocomotorTemplate() +{ + +} + +//------------------------------------------------------------------------------------------------- +void LocomotorTemplate::validate() +{ + // this is ok; parachutes need it! + //DEBUG_ASSERTCRASH(m_lift == 0.0f || m_lift > fabs(TheGlobalData->m_gravity), ("Lift is too low to counteract gravity!")); + //DEBUG_ASSERTCRASH(m_liftDamaged == 0.0f || m_liftDamaged > fabs(TheGlobalData->m_gravity), ("LiftDamaged is too low to counteract gravity!")); + //DEBUG_ASSERTCRASH(m_preferredHeight == 0.0f || (m_behaviorZ == Z_SURFACE_RELATIVE_HEIGHT || m_behaviorZ == Z_ABSOLUTE_HEIGHT || m_appearance == LOCO_THRUST), + // ("You must use Z_SURFACE_RELATIVE_HEIGHT or Z_ABSOLUTE_HEIGHT (or THRUST) to use preferredHeight")); + + // for 'damaged' stuff that was omitted, set 'em to be the same as 'undamaged'... + if (m_maxSpeedDamaged < 0.0f) + m_maxSpeedDamaged = m_maxSpeed; + + if (m_maxTurnRateDamaged < 0.0f) + m_maxTurnRateDamaged = m_maxTurnRate; + + if (m_accelerationDamaged < 0.0f) + m_accelerationDamaged = m_acceleration; + + if (m_liftDamaged < 0.0f) + m_liftDamaged = m_lift; + + if (m_appearance == LOCO_WINGS) + { + if (m_minSpeed <= 0.0f) + { + DEBUG_CRASH(("WINGS should always have positive minSpeeds (otherwise, they hover)")); + m_minSpeed = 0.01f; + } + if (m_minTurnSpeed <= 0.0f) + { + DEBUG_CRASH(("WINGS should always have positive minTurnSpeed")); + m_minTurnSpeed = 0.01f; + } + } + + if (m_appearance == LOCO_THRUST) + { + if (m_behaviorZ != Z_NO_Z_MOTIVE_FORCE || + m_lift != 0.0f || + m_liftDamaged != 0.0f) + { + DEBUG_CRASH(("THRUST locos may not use ZAxisBehavior or lift!\n")); + throw INI_INVALID_DATA; + } + if (m_maxSpeed <= 0.0f) + { + // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing + DEBUG_LOG(("THRUST locos may not have zero m_maxSpeed; healing...\n")); + m_maxSpeed = 0.01f; + } + if (m_maxSpeedDamaged <= 0.0f) + { + // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing + DEBUG_LOG(("THRUST locos may not have zero m_maxSpeedDamaged; healing...\n")); + m_maxSpeedDamaged = 0.01f; + } + if (m_minSpeed <= 0.0f) + { + // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing + DEBUG_LOG(("THRUST locos may not have zero m_minSpeed; healing...\n")); + m_minSpeed = 0.01f; + } + } +} + +//------------------------------------------------------------------------------------------------- +static void parseFrictionPerSec( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + Real fricPerSec = INI::scanReal(ini->getNextToken()); + Real fricPerFrame = fricPerSec * SECONDS_PER_LOGICFRAME_REAL; + *(Real *)store = fricPerFrame; +} + +//------------------------------------------------------------------------------------------------- +const FieldParse* LocomotorTemplate::getFieldParse() const +{ + static const FieldParse TheFieldParse[] = + { + { "Surfaces", INI::parseBitString32, TheLocomotorSurfaceTypeNames, offsetof(LocomotorTemplate, m_surfaces) }, + { "Speed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_maxSpeed) }, + { "SpeedDamaged", INI::parseVelocityReal, NULL, offsetof( LocomotorTemplate, m_maxSpeedDamaged ) }, + { "TurnRate", INI::parseAngularVelocityReal, NULL, offsetof(LocomotorTemplate, m_maxTurnRate) }, + { "TurnRateDamaged", INI::parseAngularVelocityReal, NULL, offsetof( LocomotorTemplate, m_maxTurnRateDamaged ) }, + { "Acceleration", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_acceleration) }, + { "AccelerationDamaged", INI::parseAccelerationReal, NULL, offsetof( LocomotorTemplate, m_accelerationDamaged ) }, + { "Lift", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_lift) }, + { "LiftDamaged", INI::parseAccelerationReal, NULL, offsetof( LocomotorTemplate, m_liftDamaged ) }, + { "Braking", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_braking) }, + { "MinSpeed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_minSpeed) }, + { "MinTurnSpeed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_minTurnSpeed) }, + { "PreferredHeight", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_preferredHeight) }, + { "PreferredHeightDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_preferredHeightDamping) }, + { "CirclingRadius", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_circlingRadius) }, + { "Extra2DFriction", parseFrictionPerSec, NULL, offsetof(LocomotorTemplate, m_extra2DFriction) }, + { "SpeedLimitZ", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_speedLimitZ) }, + { "MaxThrustAngle", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_maxThrustAngle) }, // yes, angle, not angular-vel + { "ZAxisBehavior", INI::parseIndexList, TheLocomotorBehaviorZNames, offsetof(LocomotorTemplate, m_behaviorZ) }, + { "Appearance", INI::parseIndexList, TheLocomotorAppearanceNames, offsetof(LocomotorTemplate, m_appearance) }, \ + { "GroupMovementPriority", INI::parseIndexList, TheLocomotorPriorityNames, offsetof(LocomotorTemplate, m_movePriority) }, \ + + { "AccelerationPitchLimit", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_accelPitchLimit) }, + { "DecelerationPitchLimit", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_decelPitchLimit) }, + { "BounceAmount", INI::parseAngularVelocityReal, NULL, offsetof(LocomotorTemplate, m_bounceKick) }, + { "PitchStiffness", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchStiffness) }, + { "RollStiffness", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rollStiffness) }, + { "PitchDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchDamping) }, + { "RollDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rollDamping) }, + { "ThrustRoll", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_thrustRoll) }, + { "ThrustWobbleRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wobbleRate) }, + { "ThrustMinWobble", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_minWobble) }, + { "ThrustMaxWobble", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maxWobble) }, + { "PitchInDirectionOfZVelFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchByZVelCoef) }, + { "ForwardVelocityPitchFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_forwardVelCoef) }, + { "LateralVelocityRollFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_lateralVelCoef) }, + { "ForwardAccelerationPitchFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_forwardAccelCoef) }, + { "LateralAccelerationRollFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_lateralAccelCoef) }, + { "UniformAxialDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_uniformAxialDamping) }, + { "TurnPivotOffset", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_turnPivotOffset) }, + { "Apply2DFrictionWhenAirborne", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_apply2DFrictionWhenAirborne) }, + { "DownhillOnly", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_downhillOnly) }, + { "AllowAirborneMotiveForce", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_allowMotiveForceWhileAirborne) }, + { "LocomotorWorksWhenDead", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_locomotorWorksWhenDead) }, + { "AirborneTargetingHeight", INI::parseInt, NULL, offsetof( LocomotorTemplate, m_airborneTargetingHeight ) }, + { "StickToGround", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_stickToGround) }, + { "CanMoveBackwards", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_canMoveBackward) }, + { "HasSuspension", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_hasSuspension) }, + { "FrontWheelTurnAngle", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_wheelTurnAngle) }, + { "MaximumWheelExtension", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maximumWheelExtension) }, + { "MaximumWheelCompression", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maximumWheelCompression) }, + { "CloseEnoughDist", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_closeEnoughDist) }, + { "CloseEnoughDist3D", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_isCloseEnoughDist3D) }, + { "SlideIntoPlaceTime", INI::parseDurationReal, NULL, offsetof(LocomotorTemplate, m_ultraAccurateSlideIntoPlaceFactor) }, + + { "WanderWidthFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderWidthFactor) }, + { "WanderLengthFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderLengthFactor) }, + { "WanderAboutPointRadius", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderAboutPointRadius) }, + + { "RudderCorrectionDegree", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rudderCorrectionDegree) }, + { "RudderCorrectionRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rudderCorrectionRate) }, + { "ElevatorCorrectionDegree", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_elevatorCorrectionDegree) }, + { "ElevatorCorrectionRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_elevatorCorrectionRate) }, + { NULL, NULL, NULL, 0 } // keep this last + + }; + return TheFieldParse; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +LocomotorStore::LocomotorStore() +{ +} + +//------------------------------------------------------------------------------------------------- +LocomotorStore::~LocomotorStore() +{ + // delete all the templates, then clear out the table. + LocomotorTemplateMap::iterator it; + for (it = m_locomotorTemplates.begin(); it != m_locomotorTemplates.end(); ++it) { + it->second->deleteInstance(); + } + + m_locomotorTemplates.clear(); +} + +//------------------------------------------------------------------------------------------------- +LocomotorTemplate* LocomotorStore::findLocomotorTemplate(NameKeyType namekey) +{ + if (namekey == NAMEKEY_INVALID) + return NULL; + + LocomotorTemplateMap::iterator it = m_locomotorTemplates.find(namekey); + if (it == m_locomotorTemplates.end()) + return NULL; + else + return (*it).second; +} + +//------------------------------------------------------------------------------------------------- +const LocomotorTemplate* LocomotorStore::findLocomotorTemplate(NameKeyType namekey) const +{ + if (namekey == NAMEKEY_INVALID) + return NULL; + + LocomotorTemplateMap::const_iterator it = m_locomotorTemplates.find(namekey); + if (it == m_locomotorTemplates.end()) + { + return NULL; + } + else + { + return (*it).second; + } +} + +//------------------------------------------------------------------------------------------------- +void LocomotorStore::update() +{ +} + +//------------------------------------------------------------------------------------------------- +void LocomotorStore::reset() +{ + // cleanup overrides. + LocomotorTemplateMap::iterator it; + for (it = m_locomotorTemplates.begin(); it != m_locomotorTemplates.end(); ) { + Overridable *locoTemp = it->second->deleteOverrides(); + if (!locoTemp) + { + m_locomotorTemplates.erase(it); + } + else + { + ++it; + } + } +} + +//------------------------------------------------------------------------------------------------- +LocomotorTemplate *LocomotorStore::newOverride( LocomotorTemplate *locoTemplate ) +{ + if (locoTemplate == NULL) + return NULL; + + // allocate new template + LocomotorTemplate *newTemplate = newInstance(LocomotorTemplate); + + // copy data from final override to 'newTemplate' as a set of initial default values + *newTemplate = *locoTemplate; + locoTemplate->setNextOverride(newTemplate); + + newTemplate->markAsOverride(); + + // return the newly created override for us to set values with etc + return newTemplate; + +} // end newOverride + +//------------------------------------------------------------------------------------------------- +/*static*/ void LocomotorStore::parseLocomotorTemplateDefinition(INI* ini) +{ + if (!TheLocomotorStore) + throw INI_INVALID_DATA; + + Bool isOverride = false; + // read the Locomotor name + const char* token = ini->getNextToken(); + NameKeyType namekey = NAMEKEY(token); + + LocomotorTemplate *loco = TheLocomotorStore->findLocomotorTemplate(namekey); + if (loco) { + if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES) { + loco = TheLocomotorStore->newOverride((LocomotorTemplate*) loco->friend_getFinalOverride()); + } + isOverride = true; + } else { + loco = newInstance(LocomotorTemplate); + if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES) { + loco->markAsOverride(); + } + } + + loco->friend_setName(token); + ini->initFromINI(loco, loco->getFieldParse()); + loco->validate(); + + // if this is an override, then we want the pointer on the existing named locomotor to point us + // to the override, so don't add it to the map. + if (!isOverride) + TheLocomotorStore->m_locomotorTemplates[namekey] = loco; +} + +//------------------------------------------------------------------------------------------------- +/*static*/ void INI::parseLocomotorTemplateDefinition( INI* ini ) +{ + LocomotorStore::parseLocomotorTemplateDefinition(ini); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- +Locomotor::Locomotor(const LocomotorTemplate* tmpl) +{ + m_template = tmpl; + m_brakingFactor = 1.0f; + m_maxLift = BIGNUM; + m_maxSpeed = BIGNUM; + m_maxAccel = BIGNUM; + m_maxBraking = BIGNUM; + m_maxTurnRate = BIGNUM; + m_flags = 0; + m_closeEnoughDist = m_template->m_closeEnoughDist; + setFlag(IS_CLOSE_ENOUGH_DIST_3D, m_template->m_isCloseEnoughDist3D); +#ifdef CIRCLE_FOR_LANDING + m_circleThresh = 0.0f; +#endif + m_preferredHeight = m_template->m_preferredHeight; + m_preferredHeightDamping = m_template->m_preferredHeightDamping; + + m_angleOffset = GameLogicRandomValueReal(-PI/6, PI/6); + m_offsetIncrement = (PI/40) * (GameLogicRandomValueReal(0.8f, 1.2f)/m_template->m_wanderLengthFactor); + setFlag(OFFSET_INCREASING, GameLogicRandomValue(0,1)); + m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; + + m_speedMultiplier = 1.0; +} + +//------------------------------------------------------------------------------------------------- +Locomotor::Locomotor(const Locomotor& that) +{ + //Added By Sadullah Nader + //Initializations + m_angleOffset = 0.0f; + m_maintainPos.zero(); + + // + + m_template = that.m_template; + m_brakingFactor = that.m_brakingFactor; + m_maxLift = that.m_maxLift; + m_maxSpeed = that.m_maxSpeed; + m_maxAccel = that.m_maxAccel; + m_maxBraking = that.m_maxBraking; + m_maxTurnRate = that.m_maxTurnRate; + m_flags = that.m_flags; + m_closeEnoughDist = that.m_closeEnoughDist; +#ifdef CIRCLE_FOR_LANDING + m_circleThresh = that.m_circleThresh; +#endif + m_preferredHeight = that.m_preferredHeight; + m_preferredHeightDamping = that.m_preferredHeightDamping; + m_angleOffset = that.m_angleOffset; + m_offsetIncrement = that.m_offsetIncrement; +} + +//------------------------------------------------------------------------------------------------- +Locomotor& Locomotor::operator=(const Locomotor& that) +{ + if (this != &that) + { + m_template = that.m_template; + m_brakingFactor = that.m_brakingFactor; + m_maxLift = that.m_maxLift; + m_maxSpeed = that.m_maxSpeed; + m_maxAccel = that.m_maxAccel; + m_maxBraking = that.m_maxBraking; + m_maxTurnRate = that.m_maxTurnRate; + m_flags = that.m_flags; + m_closeEnoughDist = that.m_closeEnoughDist; +#ifdef CIRCLE_FOR_LANDING + m_circleThresh = that.m_circleThresh; +#endif + m_preferredHeight = that.m_preferredHeight; + m_preferredHeightDamping = that.m_preferredHeightDamping; + } + return *this; +} + +//------------------------------------------------------------------------------------------------- +Locomotor::~Locomotor() +{ +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void Locomotor::crc( Xfer *xfer ) +{ + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void Locomotor::xfer( Xfer *xfer ) +{ + // version + const XferVersion currentVersion = 2; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + if (version>=2) { + xfer->xferUnsignedInt(&m_donutTimer); + } + + xfer->xferCoord3D(&m_maintainPos); + xfer->xferReal(&m_brakingFactor); + xfer->xferReal(&m_maxLift); + xfer->xferReal(&m_maxSpeed); + xfer->xferReal(&m_maxAccel); + xfer->xferReal(&m_maxBraking); + xfer->xferReal(&m_maxTurnRate); + xfer->xferReal(&m_closeEnoughDist); +#ifdef CIRCLE_FOR_LANDING + DEBUG_CRASH(("not supported, must fix me")); +#endif + xfer->xferUnsignedInt(&m_flags); + xfer->xferReal(&m_preferredHeight); + xfer->xferReal(&m_preferredHeightDamping); + xfer->xferReal(&m_angleOffset); + xfer->xferReal(&m_offsetIncrement); + + xfer->xferReal(&m_speedMultiplier); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void Locomotor::loadPostProcess( void ) +{ + +} // end loadPostProcess + +//------------------------------------------------------------------------------------------------- +void Locomotor::startMove(void) +{ + // Reset the donut timer. + m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getMaxSpeedForCondition(BodyDamageType condition) const +{ + Real speed; + + if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) + speed = m_template->m_maxSpeed; + else + speed = m_template->m_maxSpeedDamaged; + + speed *= m_speedMultiplier; + + if (speed > m_maxSpeed) + speed = m_maxSpeed; + + return speed; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getMaxTurnRate(BodyDamageType condition) const +{ + Real turn; + + if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) + turn = m_template->m_maxTurnRate; + else + turn = m_template->m_maxTurnRateDamaged; + + turn *= m_speedMultiplier; + + if (turn > m_maxTurnRate) + turn = m_maxTurnRate; + + const Real TURN_FACTOR = 2; + if (getFlag(ULTRA_ACCURATE)) + turn *= TURN_FACTOR; // monster turning ability + + return turn; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getMaxAcceleration(BodyDamageType condition) const +{ + Real accel; + + if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) + accel = m_template->m_acceleration; + else + accel = m_template->m_accelerationDamaged; + + accel *= m_speedMultiplier; + + if (accel > m_maxAccel) + accel = m_maxAccel; + + return accel; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getBraking() const +{ + Real braking = m_template->m_braking; + + braking *= m_speedMultiplier; + + if (braking > m_maxBraking) + braking = m_maxBraking; + + return braking; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getMaxLift(BodyDamageType condition) const +{ + Real lift; + + if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) + lift = m_template->m_lift; + else + lift = m_template->m_liftDamaged; + + lift *= m_speedMultiplier; + + if (lift > m_maxLift) + lift = m_maxLift; + + return lift; +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::locoUpdate_moveTowardsAngle(Object* obj, Real goalAngle) +{ + setFlag(MAINTAIN_POS_IS_VALID, false); + + if (obj == NULL || m_template == NULL) + return; + + PhysicsBehavior *physics = obj->getPhysics(); + if (physics == NULL) + { + DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); + return; + } + + // Skip moveTowardsAngle if physics say you're stunned + if(physics->getIsStunned()) + { + return; + } + +#ifdef DEBUG_OBJECT_ID_EXISTS +// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_moveTowardsAngle %f (%f deg), spd %f (%f)\n",goalAngle,goalAngle*180/PI,physics->getSpeed(),physics->getForwardSpeed2D())); +#endif + + Real minSpeed = getMinSpeed(); + if (minSpeed > 0) + { + // can't stay in one place; move in the desired direction at min speed. + Coord3D desiredPos = *obj->getPosition(); + desiredPos.x += Cos(goalAngle) * minSpeed * 2; + desiredPos.y += Sin(goalAngle) * minSpeed * 2; + // pass a huge num for "dist to goal", so that we don't think we're nearing + // our destination and thus slow down... + const Real onPathDistToGoal = 99999.0f; + Bool blocked = false; + locoUpdate_moveTowardsPosition(obj, desiredPos, onPathDistToGoal, minSpeed, &blocked); + + // don't need to call handleBehaviorZ() here, since locoUpdate_moveTowardsPosition() will do so + return; + } + else + { + DEBUG_ASSERTCRASH(m_template->m_appearance != LOCO_THRUST, ("THRUST should always have minspeeds!\n")); + Coord3D desiredPos = *obj->getPosition(); + desiredPos.x += Cos(goalAngle) * 1000.0f; + desiredPos.y += Sin(goalAngle) * 1000.0f; + PhysicsTurningType rotating = rotateTowardsPosition(obj, desiredPos); + physics->setTurning(rotating); + handleBehaviorZ(obj, physics, *obj->getPosition()); + } + +} + +//------------------------------------------------------------------------------------------------- +PhysicsTurningType Locomotor::rotateTowardsPosition(Object* obj, const Coord3D& goalPos, Real *relAngle) +{ + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real turnRate = getMaxTurnRate(bdt); + + PhysicsTurningType rotating = rotateObjAroundLocoPivot(obj, goalPos, turnRate, relAngle); + return rotating; +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::setPhysicsOptions(Object* obj) +{ + PhysicsBehavior *physics = obj->getPhysics(); + if (physics == NULL) + { + DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); + return; + } + + // crank up the friction in ultra-accurate mode to increase movement precision. + const Real EXTRA_FRIC = 0.5f; + Real extraExtraFriction = getFlag(ULTRA_ACCURATE) ? EXTRA_FRIC : 0.0f; + physics->setExtraFriction(m_template->m_extra2DFriction + extraExtraFriction); + physics->setAllowAirborneFriction(getApply2DFrictionWhenAirborne()); // you'd think we wouldn't want friction in the air, but it's needed for realistic behavior. + physics->setStickToGround(getStickToGround()); // walking guys aren't allowed to catch huge (or even small) air. +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalPos, + Real onPathDistToGoal, Real desiredSpeed, Bool *blocked) +{ + setFlag(MAINTAIN_POS_IS_VALID, false); + + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxSpeed = getMaxSpeedForCondition(bdt); + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + Real distToStopAtMaxSpeed = (maxSpeed/getBraking()) * (maxSpeed)/2.0f; + if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > distToStopAtMaxSpeed) + { + setFlag(IS_BRAKING, false); + m_brakingFactor = 1.0f; + } + PhysicsBehavior *physics = obj->getPhysics(); + if (physics == NULL) + { + DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); + return; + } + + // Skip moveTowardsPosition if physics say you're stunned + if(physics->getIsStunned()) + { + return; + } + +#ifdef DEBUG_OBJECT_ID_EXISTS +// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_moveTowardsPosition %f %f %f (dtg %f, spd %f), speed %f (%f)\n",goalPos.x,goalPos.y,goalPos.z,onPathDistToGoal,desiredSpeed,physics->getSpeed(),physics->getForwardSpeed2D())); +#endif + + // + // do not allow for invalid positions that the pathfinder cannot handle ... for airborne + // objects we don't need the pathfinder so we'll ignore this + // + if( BitIsSet( m_template->m_surfaces, LOCOMOTORSURFACE_AIR ) == false && + !TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, obj->getPosition()) && + !getFlag(ALLOW_INVALID_POSITION)) + { + // Somehow, we have gotten to an invalid location. + if (fixInvalidPosition(obj, physics)) + { + // the we adjusted us toward a legal position, so just return. + return; + } + } + + // If the actual distance is farther, then use the actual distance so we get there. + Real dx = goalPos.x - obj->getPosition()->x; + Real dy = goalPos.y - obj->getPosition()->y; + Real dz = goalPos.z - obj->getPosition()->z; + Real dist = sqrt(dx*dx+dy*dy); + if (dist>onPathDistToGoal) + { + if (!obj->isKindOf(KINDOF_PROJECTILE) && dist>2*onPathDistToGoal) + { + setFlag(IS_BRAKING, true); + } + onPathDistToGoal = dist; + } + + Coord3D nullAccel; + + Bool treatAsAirborne = false; + Coord3D pos = *obj->getPosition(); + Real heightAboveSurface = pos.z - TheTerrainLogic->getLayerHeight(pos.x, pos.y, obj->getLayer()); + + if( obj->getStatusBits().test( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) ) + { + heightAboveSurface -= obj->getCarrierDeckHeight(); + } + + if (heightAboveSurface > -(3*3)*TheGlobalData->m_gravity) + { + // If we get high enough to stay up for 3 frames, then we left the ground. + treatAsAirborne = true; + } + // We apply a zero acceleration to all units, as the call to + // applyMotiveForce flags an object as being "driven" by a locomotor, rather + // than being pushed around by objects bumping it. + nullAccel.x = nullAccel.y = nullAccel.z = 0; + physics->applyMotiveForce(&nullAccel); + + if (*blocked) + { + if (desiredSpeed > physics->getVelocityMagnitude()) + { + *blocked = false; + } + if (treatAsAirborne && BitIsSet( m_template->m_surfaces, LOCOMOTORSURFACE_AIR ) ) + { + // Airborne flying objects don't collide for now. jba. + *blocked = false; + } + } + + if (*blocked) + { + physics->scrubVelocity2D(desiredSpeed); // stop if we are about to run into the blocking object. + Real turnRate = getMaxTurnRate(obj->getBodyModule()->getDamageState()); + if (m_template->m_wanderWidthFactor == 0.0f) + { + *blocked = (TURN_NONE != rotateObjAroundLocoPivot(obj, goalPos, turnRate)); + } + + // it is very important to be sure to call this in all situations, even if not moving in 2d space. + handleBehaviorZ(obj, physics, goalPos); + return; + } + + if ( +// srj sez: I don't know why we didn't want HOVERs to allow to "brake". +// we actually really want them to, because it allows much more precise destination positioning. +// m_template->m_appearance == LOCO_HOVER || + m_template->m_appearance == LOCO_WINGS) + { + setFlag(IS_BRAKING, false); + } + + Bool wasBraking = obj->getStatusBits().test( OBJECT_STATUS_BRAKING ); + + physics->setTurning(TURN_NONE); + if (getAllowMotiveForceWhileAirborne() || !treatAsAirborne) + { + switch (m_template->m_appearance) + { + case LOCO_LEGS_TWO: + moveTowardsPositionLegs(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_CLIMBER: + moveTowardsPositionClimb(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_WHEELS_FOUR: + case LOCO_MOTORCYCLE: + moveTowardsPositionWheels( obj, physics, goalPos, onPathDistToGoal, desiredSpeed ); + break; + case LOCO_TREADS: + moveTowardsPositionTreads(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_HOVER: + moveTowardsPositionHover(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_WINGS: + moveTowardsPositionWings(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_THRUST: + moveTowardsPositionThrust(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_OTHER: + default: + moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + } + } + + handleBehaviorZ(obj, physics, goalPos); + // Objects that are braking don't follow the normal physics, so they end up at their destination exactly. + obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ), getFlag(IS_BRAKING) ); + + if (wasBraking) + { + #define MIN_VEL (PATHFIND_CELL_SIZE_F/(LOGICFRAMES_PER_SECOND)) + + Coord3D pos = *obj->getPosition(); + if (obj->isKindOf(KINDOF_PROJECTILE)) + { + // Projectiles never stop braking once they start. jba. + obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ) ); + // Projectiles cheat in 3 dimensions. + dist = sqrt(dx*dx+dy*dy+dz*dz); + Real vel = physics->getVelocityMagnitude(); + if (vel < MIN_VEL) + vel = MIN_VEL; + if (vel > dist) + vel = dist; // do not overcompensate! + // Normalize. + if (dist > 0.001f) + { + dist = 1.0f / dist; + dx *= dist; + dy *= dist; + dz *= dist; + + // DEBUG_LOG((">>> Locomotor Braking - d(xyz) = %f / %f / %f\n", dx * vel, dy * vel, dz * vel)); + + pos.x += dx * vel; + pos.y += dy * vel; + pos.z += dz * vel; + } + } + else + { + // not projectiles only cheat in x & y. + // Normalize. + if (dist > 0.001f) + { + Real vel = fabs(physics->getForwardSpeed2D()); + if (vel < MIN_VEL) + vel = MIN_VEL; + if (vel > dist) + vel = dist; // do not overcompensate! + dist = 1.0f / dist; + dx *= dist; + dy *= dist; + pos.x += dx * vel; + pos.y += dy * vel; + } + } + obj->setPosition(&pos); + } + +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxSpeed = getMaxSpeedForCondition(bdt); + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + Real maxAcceleration = getMaxAcceleration(bdt); + + // Locomotion for treaded vehicles, ie tanks. + + // + // Orient toward goal position + // +// Real angle = obj->getOrientation(); +// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); +// Real desiredAngle = angle + relAngle; + Real relAngle ; + PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos, &relAngle); + physics->setTurning(rotating); + + // + // Modulate speed according to turning. The more we have to turn, the slower we go + // + const Real QUAETERPI = PI / 4.0f; + Real angleCoeff = (Real)fabs( relAngle ) / QUAETERPI; + if (angleCoeff > 1.0f) + angleCoeff = 1.0; + + + Real dx = obj->getPosition()->x - goalPos.x; + Real dy = obj->getPosition()->y - goalPos.y; + + + Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; + + +// if (speed < m_minTurnSpeed) +// speed = m_minTurnSpeed; + + Real actualSpeed = physics->getForwardSpeed2D(); + Real slowDownTime = actualSpeed / getBraking(); + Real slowDownDist = (actualSpeed/1.50f) * slowDownTime; + + if (sqr(dx)+sqr(dy) 0.05) { + goalSpeed = actualSpeed*0.6f; + } + + if (onPathDistToGoal < slowDownDist && !getFlag(IS_BRAKING) && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + { + setFlag(IS_BRAKING, true); + m_brakingFactor = 1.1f; + } + + if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > 2.0*slowDownDist) + { + setFlag(IS_BRAKING, false); + } + + if (getFlag(IS_BRAKING)) + { + m_brakingFactor = slowDownDist/onPathDistToGoal; + m_brakingFactor *= m_brakingFactor; + if (m_brakingFactor>MAX_BRAKING_FACTOR) { + m_brakingFactor = MAX_BRAKING_FACTOR; + } + if (slowDownDist>onPathDistToGoal) { + goalSpeed = actualSpeed-getBraking(); + if (goalSpeed<0.0f) goalSpeed= 0.0f; + } else if (slowDownDist>onPathDistToGoal*0.75f) { + goalSpeed = actualSpeed-getBraking()/2.0f; + if (goalSpeed<0.0f) goalSpeed = 0.0f; + } else { + goalSpeed = actualSpeed; + } + } + + + //DEBUG_LOG(("Actual speed %f, Braking factor %f, slowDownDist %f, Pathdist %f, goalSpeed %f\n", + // actualSpeed, m_brakingFactor, slowDownDist, onPathDistToGoal, goalSpeed)); + + // + // Maintain goal speed + // + Real speedDelta = goalSpeed - actualSpeed; + if (speedDelta != 0.0f) + { + Real mass = physics->getMass(); + Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -m_brakingFactor*getBraking(); + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + const Coord3D *dir = obj->getUnitDirectionVector2D(); + + Coord3D force; + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; + force.z = 0.0f; + + // apply forces to object + physics->applyMotiveForce( &force ); + } +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxSpeed = getMaxSpeedForCondition(bdt); + Real maxTurnRate = getMaxTurnRate(bdt); + Real maxAcceleration = getMaxAcceleration(bdt); + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + // Locomotion for wheeled vehicles, ie trucks. + // + // See if we are turning. If so, use the min turn speed. + // + Real turnSpeed = m_template->m_minTurnSpeed; + Real angle = obj->getOrientation(); +// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); +// Real desiredAngle = angle + relAngle; + Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + Real relAngle = stdAngleDiff(desiredAngle, angle); + + Bool moveBackwards = false; + + // Wheeled vehicles can only turn while moving, so make sure the turn speed is reasonable. + if (turnSpeed < maxSpeed/4.0f) + { + turnSpeed = maxSpeed/4.0f; + } + + + Real actualSpeed = physics->getForwardSpeed2D(); + Bool do3pointTurn = false; +#if 1 + if (actualSpeed==0.0f) { + setFlag(MOVING_BACKWARDS, false); + if (m_template->m_canMoveBackward && fabs(relAngle) > PI/2) { + setFlag(MOVING_BACKWARDS, true ); + setFlag(DOING_THREE_POINT_TURN, onPathDistToGoal>5*obj->getGeometryInfo().getMajorRadius()); + } + + } + if (getFlag(MOVING_BACKWARDS)) { + if (fabs(relAngle) < PI/2) { + moveBackwards = false; + setFlag(MOVING_BACKWARDS, false); + } else { + moveBackwards = true; + setFlag(DOING_THREE_POINT_TURN, onPathDistToGoal>5*obj->getGeometryInfo().getMajorRadius()); + do3pointTurn = getFlag(DOING_THREE_POINT_TURN); + if (!do3pointTurn) { + desiredAngle = stdAngleDiff(desiredAngle, PI); + relAngle = stdAngleDiff(desiredAngle, angle); + } + } + } +#endif + + const Real SMALL_TURN = PI / 20.0f; + if ((Real)fabs( relAngle ) > SMALL_TURN) + { + if (desiredSpeed>turnSpeed) + { + desiredSpeed = turnSpeed; + } + } + + Real goalSpeed = desiredSpeed; + if (moveBackwards) { + actualSpeed = -actualSpeed; + } + + + + Real slowDownTime = actualSpeed / getBraking() + 1.0f; + Real slowDownDist = (actualSpeed/1.5f) * slowDownTime + actualSpeed; + Real effectiveSlowDownDist = slowDownDist; + if (effectiveSlowDownDist < 1*PATHFIND_CELL_SIZE) { + effectiveSlowDownDist = 1*PATHFIND_CELL_SIZE; + } + + + const Real FIFTEEN_DEGREES = PI / 12.0f; + const Real PROJECT_FRAMES = LOGICFRAMES_PER_SECOND/2; // Project out 1/2 second. + if (fabs( relAngle ) > FIFTEEN_DEGREES) + { + // If we're turning more than 10 degrees, check & see if we're moving into "impassable territory" + Real distance = PROJECT_FRAMES * (goalSpeed+actualSpeed)/2.0f; + Real targetAngle = obj->getOrientation(); + Real turnFactor = ((goalSpeed+actualSpeed)/2.0f)/turnSpeed; + if (turnFactor > 1.0f) + turnFactor = 1.0f; + Real turnAmount = PROJECT_FRAMES*turnFactor*maxTurnRate/4.0f; + if (relAngle < 0) + { + targetAngle -= turnAmount; + } + else + { + targetAngle += turnAmount; + } + Coord3D offset; + offset.x = Cos(targetAngle)*distance; + offset.y = Sin(targetAngle)*distance; + offset.z = 0; + + const Coord3D* pos = obj->getPosition(); + + Coord3D nextPos; + nextPos.x = pos->x+offset.x; + nextPos.y = pos->y+offset.y; + nextPos.z = pos->z; + + pos = obj->getPosition(); + + Coord3D halfPos; + halfPos.x = pos->x+offset.x/2; + halfPos.y = pos->y+offset.y/2; + halfPos.z = pos->z; + + if (!TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &halfPos) || + !TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &nextPos)) + { + PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos); + physics->setTurning(rotating); + + // apply a zero force to object so that it acts "driven" + Coord3D force; + force.zero(); + physics->applyMotiveForce( &force ); + return; + } + + } + + if (onPathDistToGoal < effectiveSlowDownDist && !getFlag(IS_BRAKING) && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + { + setFlag(IS_BRAKING, true); + m_brakingFactor = 1.1f; + } + + + if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > 2.0*slowDownDist) + { + setFlag(IS_BRAKING, false); + } + + if (onPathDistToGoal > DONUT_DISTANCE) { + m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; + } else { + if (m_donutTimer < TheGameLogic->getFrame()) { + setFlag(IS_BRAKING, true); + } + } + + if (getFlag(IS_BRAKING)) + { + m_brakingFactor = slowDownDist/onPathDistToGoal; + m_brakingFactor *= m_brakingFactor; + if (m_brakingFactor>MAX_BRAKING_FACTOR) { + m_brakingFactor = MAX_BRAKING_FACTOR; + } + m_brakingFactor = 1.0f; + if (slowDownDist>onPathDistToGoal) { + goalSpeed = actualSpeed-getBraking(); + if (goalSpeed<0.0f) goalSpeed= 0.0f; + } else if (slowDownDist>onPathDistToGoal*0.75f) { + goalSpeed = actualSpeed-getBraking()/2.0f; + if (goalSpeed<0.0f) goalSpeed = 0.0f; + } else { + goalSpeed = actualSpeed; + } + } + + + //DEBUG_LOG(("Actual speed %f, Braking factor %f, slowDownDist %f, Pathdist %f, goalSpeed %f\n", + // actualSpeed, m_brakingFactor, slowDownDist, onPathDistToGoal, goalSpeed)); + + + // Wheeled can only turn while moving. + Real turnFactor = actualSpeed/turnSpeed; + if (turnFactor<0) { + turnFactor = -turnFactor; // in case we're sliding backwards in a 3 pt turn. + } + if (turnFactor > 1.0f) + turnFactor = 1.0f; + Real turnAmount = turnFactor*maxTurnRate; + + PhysicsTurningType rotating; + if (moveBackwards && !do3pointTurn) { + Coord3D backwardPos = *obj->getPosition(); + backwardPos.x += -(goalPos.x - obj->getPosition()->x); + backwardPos.y += -(goalPos.y - obj->getPosition()->y); + rotating = rotateObjAroundLocoPivot(obj, backwardPos, turnAmount); + } else { + rotating = rotateObjAroundLocoPivot(obj, goalPos, turnAmount); + } + + physics->setTurning(rotating); + + // + // Maintain goal speed + // + Real speedDelta = goalSpeed - actualSpeed; + if (moveBackwards) { + speedDelta = -goalSpeed+actualSpeed; + } + if (speedDelta != 0.0f) + { + Real mass = physics->getMass(); + Real acceleration; + if (moveBackwards) { + acceleration = (speedDelta < 0.0f) ? -maxAcceleration : m_brakingFactor*getBraking(); + } else { + acceleration = (speedDelta > 0.0f) ? maxAcceleration : -m_brakingFactor*getBraking(); + } + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + //DEBUG_LOG(("Braking %d, actualSpeed %f, goalSpeed %f, delta %f, accel %f\n", getFlag(IS_BRAKING), + //actualSpeed, goalSpeed, speedDelta, accelForce)); + + const Coord3D *dir = obj->getUnitDirectionVector2D(); + + Coord3D force; + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; + force.z = 0.0f; + + // apply forces to object + physics->applyMotiveForce( &force ); + } + +} +//------------------------------------------------------------------------------------------------- +Bool Locomotor::fixInvalidPosition(Object* obj, PhysicsBehavior *physics) +{ + if (obj->isKindOf(KINDOF_DOZER)) { + // don't fix him. + return false; + } +#define no_IGNORE_INVALID +#ifdef IGNORE_INVALID + // Right now we ignore invalid positions, so when units clip the edge of a building or cliff + // they don't get stuck. jba. 12SEPT02 + return false; +#else + Int dx = 0; + Int dy = 0; + Int i, j; + for (j=-1; j<2; j++) { + for (i=-1; i<2; i++) { + Coord3D thePos = *obj->getPosition(); + thePos.x += i*PATHFIND_CELL_SIZE_F; + thePos.y += j*PATHFIND_CELL_SIZE_F; + if (!TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &thePos)) { + if (i<0) dx += 1; + if (i>0) dx -= 1; + if (j<0) dy += 1; + if (j>0) dy -= 1; + } + } + } + if (dx || dy) { + + Coord3D correction; + correction.x = dx*physics->getMass()/5; + correction.y = dy*physics->getMass()/5; + correction.z = 0; + + Coord3D correctionNormalized = correction; + correctionNormalized.normalize(); + + Coord3D velocity; + // Kill current velocity in the direction of the correction. + velocity = *physics->getVelocity(); + Real dot = (velocity.x*correctionNormalized.x) + (velocity.y*correctionNormalized.y); + if (dot>.25f) { + // It was already leaving. + return false; + } + + + // Kill current accel + //physics->clearAcceleration(); + + if (dot<0) { + dot = sqrt(-dot); + correctionNormalized.x *= dot*physics->getMass(); + correctionNormalized.y *= dot*physics->getMass(); + physics->applyMotiveForce(&correctionNormalized); + } + + // apply correction. + physics->applyMotiveForce(&correction); + return true; + } + return false; +#endif +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::calcMinTurnRadius(BodyDamageType condition, Real* timeToTravelThatDist) const +{ + Real minSpeed = getMinSpeed(); // in dist/frame + Real maxTurnRate = getMaxTurnRate(condition); // in rads/frame + + /* + our minimum circumference will be like so: + + Real minTurnCircum = maxSpeed * (2*PI / maxTurnRate); + + so therefore our minimum turn radius is: + + Real minTurnRadius = minTurnCircum / 2*PI; + + so we just eliminate the middleman: + */ + // if we can't turn, return a huge-but-finite radius rather than NAN... + Real minTurnRadius = (maxTurnRate > 0.0f) ? minSpeed / maxTurnRate : BIGNUM; + + if (timeToTravelThatDist) + *timeToTravelThatDist = (minSpeed > 0.0f) ? (minTurnRadius / minSpeed) : 0.0f; + + return minTurnRadius; +} + + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + if (getIsDownhillOnly() && obj->getPosition()->z < goalPos.z) + { + return; + } + + Real maxAcceleration = getMaxAcceleration( obj->getBodyModule()->getDamageState() ); + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState() ); + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + // Locomotion for infantry. + // + // Orient toward goal position + // + Real actualSpeed = physics->getForwardSpeed2D(); + Real angle = obj->getOrientation(); +// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); +// Real desiredAngle = angle + relAngle; + Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + + if (m_template->m_wanderWidthFactor != 0.0f) { + Real angleLimit = PI/8 * m_template->m_wanderWidthFactor; + // This is the wander offline code - it forces the desired angle away from the goal, so we wander back & forth. jba. + if (getFlag(OFFSET_INCREASING)) { + m_angleOffset += m_offsetIncrement*actualSpeed; + if (m_angleOffset > angleLimit) { + setFlag(OFFSET_INCREASING, false); + } + } else { + m_angleOffset -= m_offsetIncrement*actualSpeed; + if (m_angleOffset<-angleLimit) { + setFlag(OFFSET_INCREASING, true); + } + } + desiredAngle = normalizeAngle(desiredAngle+m_angleOffset); + } + + Real relAngle = stdAngleDiff(desiredAngle, angle); + locoUpdate_moveTowardsAngle(obj, desiredAngle); + + // + // Modulate speed according to turning. The more we have to turn, the slower we go + // + const Real QUARTERPI = PI / 4.0f; + Real angleCoeff = (Real)fabs( relAngle ) / (QUARTERPI); + if (angleCoeff > 1.0f) + angleCoeff = 1.0; + + Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; + + //Real slowDownDist = (actualSpeed - m_template->m_minSpeed) / getBraking(); + Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); + if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + { + goalSpeed = m_template->m_minSpeed; + } + + + + // + // Maintain goal speed + // + Real speedDelta = goalSpeed - actualSpeed; + if (speedDelta != 0.0f) + { + Real mass = physics->getMass(); + Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + const Coord3D *dir = obj->getUnitDirectionVector2D(); + + Coord3D force; + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; + force.z = 0.0f; + + + + // apply forces to object + physics->applyMotiveForce( &force ); + } +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + Real maxAcceleration = getMaxAcceleration( obj->getBodyModule()->getDamageState() ); + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState() ); + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + // Locomotion for climbing infantry. + + + Bool moveBackwards = false; + + Real dx, dy, dz; + + Coord3D pos = *obj->getPosition(); + + dx = pos.x - goalPos.x; + dy = pos.y - goalPos.y; + dz = pos.z - goalPos.z; + if (dz*dz > sqr(PATHFIND_CELL_SIZE_F)) { + setFlag(CLIMBING, true); + } + if (fabs(dz)<1) { + setFlag(CLIMBING, false); + } + + + //setFlag(CLIMBING, true); + + if (getFlag(CLIMBING)) { + Coord3D delta = goalPos; + delta.x -= pos.x; + delta.y -= pos.y; + delta.z = 0; + delta.normalize(); + delta.x += pos.x; + delta.y += pos.y; + delta.z = TheTerrainLogic->getGroundHeight(delta.x, delta.y); + if (delta.z < pos.z-0.1) { + moveBackwards = true; + } + + Real groundSlope = fabs(delta.z - pos.z); + if (groundSlope<1.0f) groundSlope = 1.0f; + + if (groundSlope>1.0f) { + desiredSpeed /= groundSlope*4; + } + } + setFlag(MOVING_BACKWARDS, moveBackwards); + + // + // Orient toward goal position + // + Real angle = obj->getOrientation(); +// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); +// Real desiredAngle = angle + relAngle; + Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + Real relAngle = stdAngleDiff(desiredAngle, angle); + + if (moveBackwards) { + desiredAngle = stdAngleDiff(desiredAngle, PI); + relAngle = stdAngleDiff(desiredAngle, angle); + } + + locoUpdate_moveTowardsAngle(obj, desiredAngle); + + // + // Modulate speed according to turning. The more we have to turn, the slower we go + // + const Real QUARTERPI = PI / 4.0f; + Real angleCoeff = (Real)fabs( relAngle ) / (QUARTERPI); + if (angleCoeff > 1.0f) + angleCoeff = 1.0; + + Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; + + Real actualSpeed = physics->getForwardSpeed2D(); + + if (moveBackwards) { + actualSpeed = -actualSpeed; + } + + //Real slowDownDist = (actualSpeed - m_template->m_minSpeed) / getBraking(); + Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); + if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + { + goalSpeed = m_template->m_minSpeed; + } + + // + // Maintain goal speed + // + Real speedDelta = goalSpeed - actualSpeed; + if (moveBackwards) { + speedDelta = -goalSpeed+actualSpeed; + } + if (speedDelta != 0.0f) + { + Real mass = physics->getMass(); + Real acceleration; + if (moveBackwards) { + acceleration = (speedDelta < 0.0f) ? -maxAcceleration : getBraking(); + } else { + acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); + } + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + const Coord3D *dir = obj->getUnitDirectionVector2D(); + + Coord3D force; + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; + force.z = 0.0f; + + // apply forces to object + physics->applyMotiveForce( &force ); + } +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ +#ifdef CIRCLE_FOR_LANDING + if (m_circleThresh > 0.0f) + { + // if we are going a mostly-vertical maneuver, circle in order to + // gain/lose altitude, then resume course... + const Coord3D* pos = obj->getPosition(); + Real dx = goalPos.x - pos->x; + Real dy = goalPos.y - pos->y; + Real dz = goalPos.z - pos->z; + if (fabs(dz) > m_circleThresh) + { + // aim for the spot on the opposite side of the circle. + + // find the direction towards our goal pos + Real angleTowardPos = + (isNearlyZero(dx) && isNearlyZero(dy)) ? + obj->getOrientation() : + atan2(dy, dx); + + Real aimDir = (PI - PI/8); + angleTowardPos += aimDir; + + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real turnRadius = calcMinTurnRadius(bdt, NULL) * 4; + + // project a spot "radius" dist away from it, in that dir + Coord3D desiredPos = goalPos; + desiredPos.x += Cos(angleTowardPos) * turnRadius; + desiredPos.y += Sin(angleTowardPos) * turnRadius; + moveTowardsPositionOther(obj, physics, desiredPos, 0, desiredSpeed); + return; + } + } +#endif + + // handle the 2D component. + moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionHover(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + // handle the 2D component. + moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + + // Only hover locomotors care about their OverWater special effects. (OverWater also affects speed, so this is not a client thing) + Coord3D newPosition = *obj->getPosition(); + if( TheTerrainLogic->isUnderwater( newPosition.x, newPosition.y ) ) + { + if( ! getFlag( OVER_WATER ) ) + { + // Change my model condition because I used to not be over water, but now I am + setFlag( OVER_WATER, TRUE ); + obj->setModelConditionState( MODELCONDITION_OVER_WATER ); + } + } + else + { + if( getFlag( OVER_WATER ) ) + { + // Here, I was, but now I'm not + setFlag( OVER_WATER, FALSE ); + obj->clearModelConditionState( MODELCONDITION_OVER_WATER ); + } + } +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + + Real maxForwardSpeed = getMaxSpeedForCondition(bdt); + desiredSpeed = clamp(m_template->m_minSpeed, desiredSpeed, maxForwardSpeed); + Real actualForwardSpeed = physics->getForwardSpeed3D(); + + if (getBraking() > 0) + { + //Real slowDownDist = (actualForwardSpeed - m_template->m_minSpeed) / getBraking(); + Real slowDownDist = calcSlowDownDist(actualForwardSpeed, m_template->m_minSpeed, getBraking()); + if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + desiredSpeed = m_template->m_minSpeed; + } + + Coord3D localGoalPos = goalPos; +#ifdef USE_ZDIR_DAMPING + Real zDirDamping = 0.0f; +#endif + + //out of the handleBehaviorZ() function + Coord3D pos = *obj->getPosition(); + if( m_preferredHeight != 0.0f && !getFlag(PRECISE_Z_POS) ) + { + // If we have a preferred flight height, and we haven't been told explicitly to ignore it... + Real surfaceHt = getSurfaceHtAtPt(pos.x, pos.y); + localGoalPos.z = m_preferredHeight + surfaceHt; +// localGoalPos.z = goalPos.z; + Real delta = localGoalPos.z - pos.z; + delta *= getPreferredHeightDamping(); + localGoalPos.z = pos.z + delta; + +#ifdef USE_ZDIR_DAMPING + // closer we get to the preferred height, less we adjust z-thrust, + // so we tend to "level out" at that height. we don't use this till + // below, but go ahead and calc it now... + Real MAX_VERTICAL_DAMP_RANGE = m_preferredHeight * 0.5; + delta = fabs(delta); + if (delta > MAX_VERTICAL_DAMP_RANGE) + delta = MAX_VERTICAL_DAMP_RANGE; + zDirDamping = 1.0f - (delta / MAX_VERTICAL_DAMP_RANGE); +#endif + } + + Vector3 forwardDir = obj->getTransformMatrix()->Get_X_Vector(); + + // Maintain goal speed + Real forwardSpeedDelta = desiredSpeed - actualForwardSpeed; + Real maxAccel = (forwardSpeedDelta > 0.0f || getBraking() == 0) ? getMaxAcceleration(bdt) : -getBraking(); + Real maxTurnRate = getMaxTurnRate(bdt); + + // what direction do we need to thrust in, in order to reach the goalpos? + Vector3 desiredThrustDir; + calcDirectionToApplyThrust(obj, physics, localGoalPos, maxAccel, desiredThrustDir); + + // we might not be able to thrust in that dir, so thrust as closely as we can + Real maxThrustAngle = (maxTurnRate > 0) ? (m_template->m_maxThrustAngle) : 0; + Vector3 thrustDir; + Real thrustAngle = tryToRotateVector3D(maxThrustAngle, forwardDir, desiredThrustDir, thrustDir); + + // note that we are trying to orient in the direction of our vel, not the dir of our thrust. + if (!isNearlyZero(physics->getVelocityMagnitude())) + { + const Coord3D* veltmp = physics->getVelocity(); + Vector3 vel(veltmp->x, veltmp->y, veltmp->z); + Bool adjust = true; + if( obj->getStatusBits().test( OBJECT_STATUS_BRAKING ) ) + { + //Real closeInDist = 150.0f; // TODO: get/set this from missileAI? + //Real af = 1.0f - __min((onPathDistToGoal / closeInDist), 1.0); + + //if (af > 0.0f) { + + // vel.Set( + // vel.X * (1.0f - af) + (goalPos.x - pos.x) * af, + // vel.Y * (1.0f - af) + (goalPos.y - pos.y) * af, + // vel.Z * (1.0f - af) + (goalPos.z - pos.z) * af + // ); + // if (isNearlyZero(sqr(vel.X) + sqr(vel.Y) + sqr(vel.Z))) { + // // we are at target. + // adjust = false; + // } + // maxTurnRate = (1.0f + (af * 2.0f) ) * maxTurnRate; + //} + + // DEBUG_LOG((">>> moveTowardsPositionThrust - Braking - maxTurnRate = %f\n", maxTurnRate)); + + // align to target, cause that's where we're going anyway. + + vel.Set(goalPos.x - pos.x, goalPos.y-pos.y, goalPos.z-pos.z); + if (isNearlyZero(sqr(vel.X)+sqr(vel.Y)+sqr(vel.Z))) { + // we are at target. + adjust = false; + } + maxTurnRate = 3*maxTurnRate; + } +#ifdef USE_ZDIR_DAMPING + if (zDirDamping != 0.0f) + { + Vector3 vel2D(veltmp->x, veltmp->y, 0); + // no need to normalize -- this call does that internally + tryToRotateVector3D(-zDirDamping, vel, vel2D, vel); + } +#endif + if (adjust) { + /*Real orient =*/ tryToOrientInThisDirection3D(obj, maxTurnRate, vel); + } + } + + if (forwardSpeedDelta != 0.0f || thrustAngle != 0.0f) + { + if (maxForwardSpeed <= 0.0f) + { + maxForwardSpeed = 0.01f; // In some cases, this is 0, hack for now. jba. + } + Real damping = clamp(0.0f, maxAccel / maxForwardSpeed, 1.0f); + Vector3 curVel(physics->getVelocity()->x, physics->getVelocity()->y, physics->getVelocity()->z); + + Vector3 accelVec = thrustDir * maxAccel - curVel * damping; + //DEBUG_LOG(("accel %f (max %f) vel %f (max %f) damping %f\n",accelVec.Length(),maxAccel,curVel.Length(),maxForwardSpeed,damping)); + + Real mass = physics->getMass(); + + Coord3D force; + force.x = mass * accelVec.X; + force.y = mass * accelVec.Y; + force.z = mass * accelVec.Z; + + // apply forces to object + physics->applyMotiveForce( &force ); + } +} + +//------------------------------------------------------------------------------------------------- +/*static*/ Real Locomotor::getSurfaceHtAtPt(Real x, Real y) +{ + Real ht = 0; + + Real z,waterZ; + if (TheTerrainLogic->isUnderwater(x, y, &waterZ, &z)) { + ht += waterZ; + } else { + ht += z; + } + + return ht; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::calcLiftToUseAtPt(Object* obj, PhysicsBehavior *physics, Real curZ, Real surfaceAtPt, Real preferredHeight) +{ + /* + take the classic equation: + + x = x0 + v*t + 0.5*a*t^2 + + and solve for acceleration. + */ + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxGrossLift = getMaxLift(bdt); + Real maxNetLift = maxGrossLift + TheGlobalData->m_gravity; // note that gravity is always negative. + if (maxNetLift < 0) + maxNetLift = 0; + Real curVelZ = physics->getVelocity()->z; + // going down, braking is limited by net lift; going up, braking is limited by gravity + Real maxAccel; + if (getFlag(ULTRA_ACCURATE)) + maxAccel = (curVelZ < 0) ? 2*maxNetLift : -2*maxNetLift; + else + maxAccel = (curVelZ < 0) ? maxNetLift : TheGlobalData->m_gravity; + // see how far we need to slow to dead stop, given max braking + Real desiredAccel; + const Real TINY_ACCEL = 0.001f; + if (fabs(maxAccel) > TINY_ACCEL) + { + Real deltaZ = preferredHeight - curZ; + // calc how far it will take for us to go from cur speed to zero speed, at max accel. + // Real brakeDist = calcSlowDownDist(curVelZ, 0, maxAccel); + // in theory, the above is the correct calculation, but in practice, + // doesn't work in some situations (eg, opening of USA01 map). Why, I dunno. + // But for now I have gone back to the old, looks-incorrect-to-me-but-works calc. (srj) + Real brakeDist = (sqr(curVelZ) / fabs(maxAccel)); + if (fabs(brakeDist) > fabs(deltaZ)) + { + // if the dist-to-accel (or dist-to-brake) is further than the dist-to-go, + // use the max accel. + desiredAccel = maxAccel; + } + else if (fabs(curVelZ) > m_template->m_speedLimitZ) + { + // or, if we're going too fast, limit it here. + desiredAccel = m_template->m_speedLimitZ - curVelZ; + } + else + { + // ok, figure out the correct accel to use to get us there at zero. + // + // dz = v t + 0.5 a t^2 + // thus + // a = 2(dz - v t)/t^2 + // and + // t = (-v +- sqrt(v*v + 2*a*dz))/a + // + // but if we assume t=1, then + // a=2(dz-v) + // then, plug it back in and see if t is really 1... + desiredAccel = 2.0f * (deltaZ - curVelZ); + } + } + else + { + desiredAccel = 0.0f; + } + Real liftToUse = desiredAccel - TheGlobalData->m_gravity; + if (getFlag(ULTRA_ACCURATE)) + { + // in ultra-accurate mode, we allow cheating. + const Real UP_FACTOR = 3.0f; + if (liftToUse > UP_FACTOR*maxGrossLift) + liftToUse = UP_FACTOR*maxGrossLift; + // srj sez: we used to clip lift to zero here (not allowing neg lift). + // however, I now think that allowing neg lift in ultra-accurate mode is + // a good and desirable thing; in particular, it enables jets to complete + // "short" landings more accurately (previously they sometimes would "float" + // down, which sucked.) if you need to bump this back to zero, check it carefully... + else if (liftToUse < -maxGrossLift) + liftToUse = -maxGrossLift; + } + else + { + if (liftToUse > maxGrossLift) + liftToUse = maxGrossLift; + else if (liftToUse < 0.0f) + liftToUse = 0.0f; + } + + return liftToUse; +} + +//------------------------------------------------------------------------------------------------- +PhysicsTurningType Locomotor::rotateObjAroundLocoPivot(Object* obj, const Coord3D& goalPos, + Real maxTurnRate, Real *relAngle) +{ + Real angle = obj->getOrientation(); + Real offset = getTurnPivotOffset(); + + PhysicsTurningType turn = TURN_NONE; + + if (getFlag(IS_BRAKING)) offset = 0.0f; // When braking we do exact movement towards goal, instead of physics. + //Rotating about pivot moves the object, and can make us miss our goal, so it is disabled. jba. + if (offset != 0.0f) + { + Real radius = obj->getGeometryInfo().getBoundingCircleRadius(); + Real turnPointOffset = offset * radius; + + Coord3D turnPos = *obj->getPosition(); + const Coord3D* dir = obj->getUnitDirectionVector2D(); + turnPos.x += dir->x * turnPointOffset; + turnPos.y += dir->y * turnPointOffset; + Real dx =goalPos.x - turnPos.x; + Real dy = goalPos.y - turnPos.y; + // If we are very close to the goal, we twitch due to rounding error. So just return. jba. + if (fabs(dx)<0.1f && fabs(dy)<0.1f) return TURN_NONE; + Real desiredAngle = atan2(dy, dx); + Real amount = stdAngleDiff(desiredAngle, angle); + if (relAngle) *relAngle = amount; + if (amount>maxTurnRate) { + amount = maxTurnRate; + turn = TURN_POSITIVE; + } else if (amount < -maxTurnRate) { + amount = -maxTurnRate; + turn = TURN_NEGATIVE; + } else { + turn = TURN_NONE; + } + +#if 0 + Coord3D desiredPos = *obj->getPosition(); // well, desired Dir, anyway + desiredPos.x += Cos(angle + amount) * radius; + desiredPos.y += Sin(angle + amount) * radius; + + + // so, the thing is, we want to rotate ourselves so that our *center* is rotated + // by the given amount, but the rotation must be around turnPos. so do a little + // back-calculation. + Real angleDesiredForTurnPos = atan2(desiredPos.y - turnPos.y, desiredPos.x - turnPos.x); + amount = angleDesiredForTurnPos - angle; +#endif + /// @todo srj -- there's probably a more efficient & more direct way to do this. find it. + Matrix3D mtx; + Matrix3D tmp(1); + tmp.Translate(turnPos.x, turnPos.y, 0); + tmp.In_Place_Pre_Rotate_Z(amount); + tmp.Translate(-turnPos.x, -turnPos.y, 0); + + mtx.mul(tmp, *obj->getTransformMatrix()); + + obj->setTransformMatrix(&mtx); + } + else + { + Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + Real amount = stdAngleDiff(desiredAngle, angle); + if (relAngle) *relAngle = amount; + if (amount>maxTurnRate) { + amount = maxTurnRate; + turn = TURN_POSITIVE; + } else if (amount < -maxTurnRate) { + amount = -maxTurnRate; + turn = TURN_NEGATIVE; + } else { + turn = TURN_NONE; + } + obj->setOrientation( normalizeAngle(angle + amount) ); + } + return turn; +} + +//------------------------------------------------------------------------------------------------- +/* + return true if we can maintain the position without being called every frame (eg, we are + resting on the ground), false if not (eg, we are hovering or circling) +*/ +Bool Locomotor::handleBehaviorZ(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos) +{ + Bool requiresConstantCalling = TRUE; + + // keep the agent aligned on the terrain + switch(m_template->m_behaviorZ) + { + case Z_NO_Z_MOTIVE_FORCE: + // nothing to do. + requiresConstantCalling = FALSE; + break; + + case Z_SEA_LEVEL: + requiresConstantCalling = TRUE; + if( !obj->isDisabledByType( DISABLED_HELD ) ) + { + Coord3D pos = *obj->getPosition(); + Real waterZ; + if (TheTerrainLogic->isUnderwater(pos.x, pos.y, &waterZ)) { + pos.z = waterZ; + } else { + pos.z = TheTerrainLogic->getLayerHeight(pos.x, pos.y, obj->getLayer()); + } + obj->setPosition(&pos); + } + break; + + case Z_FIXED_SURFACE_RELATIVE_HEIGHT: + case Z_FIXED_ABSOLUTE_HEIGHT: + requiresConstantCalling = TRUE; + { + Coord3D pos = *obj->getPosition(); + Bool surfaceRel = (m_template->m_behaviorZ == Z_FIXED_SURFACE_RELATIVE_HEIGHT); + Real surfaceHt = surfaceRel ? getSurfaceHtAtPt(pos.x, pos.y) : 0.0f; + pos.z = m_preferredHeight + (surfaceRel ? surfaceHt : 0); + obj->setPosition(&pos); + } + break; + + case Z_RELATIVE_TO_GROUND_AND_BUILDINGS: + requiresConstantCalling = TRUE; + { + // srj sez: use getGroundOrStructureHeight(), because someday it will cache building heights... + Coord3D pos = *obj->getPosition(); + Real surfaceHt = ThePartitionManager->getGroundOrStructureHeight(pos.x, pos.y); + + pos.z = m_preferredHeight + surfaceHt; + + obj->setPosition(&pos); + + } + break; + case Z_SMOOTH_RELATIVE_TO_HIGHEST_LAYER: + requiresConstantCalling = TRUE; + { + if (m_preferredHeight != 0.0f || getFlag(PRECISE_Z_POS)) + { + Coord3D pos = *obj->getPosition(); + + // srj sez: if we aren't on the ground, never find the ground layer + PathfindLayerEnum layerAtDest = obj->getLayer(); + if (layerAtDest == LAYER_GROUND) + layerAtDest = TheTerrainLogic->getHighestLayerForDestination( &pos ); + + Real surfaceHt; + Coord3D normal; + const Bool clip = false; // return the height, even if off the edge of the bridge proper. + surfaceHt = TheTerrainLogic->getLayerHeight( pos.x, pos.y, layerAtDest, &normal, clip ); + + Real preferredHeight = m_preferredHeight + surfaceHt; + if (getFlag(PRECISE_Z_POS)) + preferredHeight = goalPos.z; + + Real delta = preferredHeight - pos.z; + delta *= getPreferredHeightDamping(); + preferredHeight = pos.z + delta; + + Real liftToUse = calcLiftToUseAtPt(obj, physics, pos.z, surfaceHt, preferredHeight); + + //DEBUG_LOG(("HandleBZ %d LiftToUse %f\n",TheGameLogic->getFrame(),liftToUse)); + if (liftToUse != 0.0f) + { + Coord3D force; + force.x = 0.0f; + force.y = 0.0f; + force.z = liftToUse * physics->getMass(); + physics->applyMotiveForce(&force); + } + } + } + break; + + case Z_SURFACE_RELATIVE_HEIGHT: + case Z_ABSOLUTE_HEIGHT: + requiresConstantCalling = TRUE; + { + if (m_preferredHeight != 0.0f || getFlag(PRECISE_Z_POS)) + { + Coord3D pos = *obj->getPosition(); + + Bool surfaceRel = (m_template->m_behaviorZ == Z_SURFACE_RELATIVE_HEIGHT); + Real surfaceHt = surfaceRel ? getSurfaceHtAtPt(pos.x, pos.y) : 0.0f; + Real preferredHeight = m_preferredHeight + (surfaceRel ? surfaceHt : 0); + if (getFlag(PRECISE_Z_POS)) + preferredHeight = goalPos.z; + + Real delta = preferredHeight - pos.z; + delta *= getPreferredHeightDamping(); + preferredHeight = pos.z + delta; + + Real liftToUse = calcLiftToUseAtPt(obj, physics, pos.z, surfaceHt, preferredHeight); + + //DEBUG_LOG(("HandleBZ %d LiftToUse %f\n",TheGameLogic->getFrame(),liftToUse)); + if (liftToUse != 0.0f) + { + Coord3D force; + force.x = 0.0f; + force.y = 0.0f; + force.z = liftToUse * physics->getMass(); + physics->applyMotiveForce(&force); + } + } + } + break; + } + + return requiresConstantCalling; +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxAcceleration = getMaxAcceleration(bdt); + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + Real maxSpeed = getMaxSpeedForCondition(bdt); + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + Real goalSpeed = desiredSpeed; + Real actualSpeed = physics->getForwardSpeed2D(); + + // Locomotion for other things, ie don't know what it is jba :) + // + // Orient toward goal position + // exception: if very close (ie, we could get there in 2 frames or less),\ + // and ULTRA_ACCURATE, just slide into place + // + const Coord3D* pos = obj->getPosition(); + Coord3D dirToApplyForce = *obj->getUnitDirectionVector2D(); + +//DEBUG_ASSERTLOG(!getFlag(ULTRA_ACCURATE),("thresh %f %f (%f %f)\n", +//fabs(goalPos.y - pos->y),fabs(goalPos.x - pos->x), +//fabs(goalPos.y - pos->y)/goalSpeed,fabs(goalPos.x - pos->x)/goalSpeed)); + if (getFlag(ULTRA_ACCURATE) && + fabs(goalPos.y - pos->y) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor && + fabs(goalPos.x - pos->x) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor) + { + // don't turn, just slide in the right direction + physics->setTurning(TURN_NONE); + dirToApplyForce.x = goalPos.x - pos->x; + dirToApplyForce.y = goalPos.y - pos->y; + dirToApplyForce.z = 0.0f; + dirToApplyForce.normalize(); + } + else + { + PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos); + physics->setTurning(rotating); + } + + if (!getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + { + Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); + if (onPathDistToGoal < slowDownDist) + { + goalSpeed = m_template->m_minSpeed; + } + } + + // + // Maintain goal speed + // + Real speedDelta = goalSpeed - actualSpeed; + if (speedDelta != 0.0f) + { + Real mass = physics->getMass(); + Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + Coord3D force; + force.x = accelForce * dirToApplyForce.x; + force.y = accelForce * dirToApplyForce.y; + force.z = 0.0f; + + // apply forces to object + physics->applyMotiveForce( &force ); + } + +} + + +//------------------------------------------------------------------------------------------------- +/* + return true if we can maintain the position without being called every frame (eg, we are + resting on the ground), false if not (eg, we are hovering or circling) +*/ +Bool Locomotor::locoUpdate_maintainCurrentPosition(Object* obj) +{ + if (!getFlag(MAINTAIN_POS_IS_VALID)) + { + m_maintainPos = *obj->getPosition(); + setFlag(MAINTAIN_POS_IS_VALID, true); + } + + m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; + setFlag(IS_BRAKING, false); + PhysicsBehavior *physics = obj->getPhysics(); + if (physics == NULL) + { + DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); + return TRUE; + } + +#ifdef DEBUG_OBJECT_ID_EXISTS +// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_maintainCurrentPosition %f %f %f, speed %f (%f)\n",m_maintainPos.x,m_maintainPos.y,m_maintainPos.z,physics->getSpeed(),physics->getForwardSpeed2D())); +#endif + + Bool requiresConstantCalling = TRUE; // assume the worst. + switch (m_template->m_appearance) + { + case LOCO_THRUST: + maintainCurrentPositionThrust(obj, physics); + requiresConstantCalling = TRUE; + break; + case LOCO_LEGS_TWO: + maintainCurrentPositionLegs(obj, physics); + requiresConstantCalling = FALSE; + break; + case LOCO_CLIMBER: + maintainCurrentPositionLegs(obj, physics); + requiresConstantCalling = FALSE; + break; + case LOCO_WHEELS_FOUR: + case LOCO_MOTORCYCLE: + maintainCurrentPositionWheels(obj, physics); + requiresConstantCalling = FALSE; + break; + case LOCO_TREADS: + maintainCurrentPositionTreads(obj, physics); + requiresConstantCalling = FALSE; + break; + case LOCO_HOVER: + maintainCurrentPositionHover(obj, physics); + requiresConstantCalling = TRUE; + break; + case LOCO_WINGS: + maintainCurrentPositionWings(obj, physics); + requiresConstantCalling = TRUE; + break; + case LOCO_OTHER: + default: + maintainCurrentPositionOther(obj, physics); + requiresConstantCalling = TRUE; + break; + } + + // but we do need to do this even if not moving, for hovering/Thrusting things. + if (handleBehaviorZ(obj, physics, m_maintainPos)) + requiresConstantCalling = TRUE; + + return requiresConstantCalling; +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::maintainCurrentPositionThrust(Object* obj, PhysicsBehavior *physics) +{ + DEBUG_ASSERTCRASH(getFlag(MAINTAIN_POS_IS_VALID), ("invalid maintain pos")); + /// @todo srj -- should these also use the "circling radius" stuff, like wings? + moveTowardsPositionThrust(obj, physics, m_maintainPos, 0, getMinSpeed()); +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physics) +{ + DEBUG_ASSERTCRASH(getFlag(MAINTAIN_POS_IS_VALID), ("invalid maintain pos")); + physics->setTurning(TURN_NONE); + if (physics->isMotive() && obj->isAboveTerrain()) // no need to stop something that isn't moving (or is just sitting on the ground) + { + + // aim for the spot on the opposite side of the circle. + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real turnRadius = m_template->m_circlingRadius; + if (turnRadius == 0.0f) + turnRadius = calcMinTurnRadius(bdt, NULL); + + // find the direction towards our "maintain pos" + const Coord3D* pos = obj->getPosition(); + Real dx = m_maintainPos.x - pos->x; + Real dy = m_maintainPos.y - pos->y; + Real angleTowardMaintainPos = + (isNearlyZero(dx) && isNearlyZero(dy)) ? + obj->getOrientation() : + atan2(dy, dx); + + Real aimDir = (PI - PI/8); + if (turnRadius < 0) + { + turnRadius = -turnRadius; + aimDir = -aimDir; + } + angleTowardMaintainPos += aimDir; + + // project a spot "radius" dist away from it, in that dir + Coord3D desiredPos = m_maintainPos; + desiredPos.x += Cos(angleTowardMaintainPos) * turnRadius; + desiredPos.y += Sin(angleTowardMaintainPos) * turnRadius; + moveTowardsPositionWings(obj, physics, desiredPos, 0, m_template->m_minSpeed); + } +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physics) +{ + physics->setTurning(TURN_NONE); + if (physics->isMotive()) // no need to stop something that isn't moving. + { + DEBUG_ASSERTCRASH(m_template->m_minSpeed == 0.0f, ("HOVER should always have zero minSpeeds (otherwise, they WING)")); + + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxAcceleration = getMaxAcceleration(bdt); + Real actualSpeed = physics->getForwardSpeed2D(); + // + // Stop + // + Real minSpeed = max( 1.0E-10f, m_template->m_minSpeed ); + Real speedDelta = minSpeed - actualSpeed; + if (fabs(speedDelta) > minSpeed) + { + Real mass = physics->getMass(); + Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + const Coord3D *dir = obj->getUnitDirectionVector2D(); + + Coord3D force; + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; + force.z = 0.0f; + + + // Apply a random kick (if applicable) to dirty-up visually. + // The idea is that chopper pilots have to do course corrections all the time + // Because of changes in wind, pressure, etc. + // Those changes are added here, then the + + + + // apply forces to object + physics->applyMotiveForce( &force ); + } + } + +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::maintainCurrentPositionOther(Object* obj, PhysicsBehavior *physics) +{ + + physics->setTurning(TURN_NONE); + if (physics->isMotive()) // no need to stop something that isn't moving. + { + physics->scrubVelocity2D(0); // stop. + } + +} + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- +LocomotorSet::LocomotorSet() +{ + m_locomotors.clear(); + m_validLocomotorSurfaces = 0; + m_downhillOnly = FALSE; + +} + +//------------------------------------------------------------------------------------------------- +LocomotorSet::LocomotorSet(const LocomotorSet& that) +{ + DEBUG_CRASH(("unimplemented")); +} + +//------------------------------------------------------------------------------------------------- +LocomotorSet& LocomotorSet::operator=(const LocomotorSet& that) +{ + if (this != &that) + { + DEBUG_CRASH(("unimplemented")); + } + return *this; +} + +//------------------------------------------------------------------------------------------------- +LocomotorSet::~LocomotorSet() +{ + clear(); +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void LocomotorSet::crc( Xfer *xfer ) +{ + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void LocomotorSet::xfer( Xfer *xfer ) +{ + // version + const XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // count of vector + UnsignedShort count = m_locomotors.size(); + xfer->xferUnsignedShort( &count ); + + // data + if (xfer->getXferMode() == XFER_SAVE) + { + for (LocomotorVector::iterator it = m_locomotors.begin(); it != m_locomotors.end(); ++it) + { + Locomotor* loco = *it; + AsciiString name = loco->getTemplateName(); + xfer->xferAsciiString(&name); + xfer->xferSnapshot(loco); + } + } + else if (xfer->getXferMode() == XFER_LOAD) + { + // vector should be empty at this point + if (m_locomotors.empty() == FALSE) + { + DEBUG_CRASH(( "LocomotorSet::xfer - vector is not empty, but should be\n" )); + throw XFER_LIST_NOT_EMPTY; + } + + for (UnsignedShort i = 0; i < count; ++i) + { + AsciiString name; + xfer->xferAsciiString(&name); + + const LocomotorTemplate* lt = TheLocomotorStore->findLocomotorTemplate(NAMEKEY(name)); + if (lt == NULL) + { + DEBUG_CRASH(( "LocomotorSet::xfer - template %s not found\n", name.str() )); + throw XFER_UNKNOWN_STRING; + } + + Locomotor* loco = TheLocomotorStore->newLocomotor(lt); + xfer->xferSnapshot(loco); + m_locomotors.push_back(loco); + } + } + + xfer->xferInt(&m_validLocomotorSurfaces); + xfer->xferBool(&m_downhillOnly); + +} + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void LocomotorSet::loadPostProcess( void ) +{ + +} // end loadPostProcess + +//------------------------------------------------------------------------------------------------- +void LocomotorSet::xferSelfAndCurLocoPtr(Xfer *xfer, Locomotor** loco) +{ + xfer->xferSnapshot(this); + + if (xfer->getXferMode() == XFER_SAVE) + { + AsciiString name; + if (*loco) + name = (*loco)->getTemplateName(); + xfer->xferAsciiString(&name); + } + else if (xfer->getXferMode() == XFER_LOAD) + { + AsciiString name; + xfer->xferAsciiString(&name); + + if (name.isEmpty()) + { + *loco = NULL; + } + else + { + for (int i = 0; i < m_locomotors.size(); ++i) + { + if (m_locomotors[i]->getTemplateName() == name) + { + *loco = m_locomotors[i]; + return; + } + } + + DEBUG_CRASH(( "LocomotorSet::xfer - template %s not found\n", name.str() )); + throw XFER_UNKNOWN_STRING; + } + } +} + +//------------------------------------------------------------------------------------------------- +void LocomotorSet::clear() +{ + for (int i = 0; i < m_locomotors.size(); ++i) + { + if (m_locomotors[i]) + m_locomotors[i]->deleteInstance(); + } + m_locomotors.clear(); + m_validLocomotorSurfaces = 0; + m_downhillOnly = FALSE; +} + +//------------------------------------------------------------------------------------------------- +void LocomotorSet::addLocomotor(const LocomotorTemplate* lt) +{ + Locomotor* loco = TheLocomotorStore->newLocomotor(lt); + if (loco) + { + m_locomotors.push_back(loco); + m_validLocomotorSurfaces |= loco->getLegalSurfaces(); + if (loco->getIsDownhillOnly()) + { + m_downhillOnly = TRUE; + } + else // Previous locos were gravity only, but this one isn't! + { + DEBUG_ASSERTCRASH(!m_downhillOnly,("LocomotorSet, YOU CAN NOT MIX DOWNHILL-ONLY LOCOMOTORS WITH NON-DOWNHILL-ONLY ONES.")); + } + + } +} + +//------------------------------------------------------------------------------------------------- +Locomotor* LocomotorSet::findLocomotor(LocomotorSurfaceTypeMask t) +{ + for (LocomotorVector::iterator it = m_locomotors.begin(); it != m_locomotors.end(); ++it) + { + Locomotor* curLocomotor = *it; + if (curLocomotor && (curLocomotor->getLegalSurfaces() & t)) + return curLocomotor; + } + return NULL; +} + + From ec743c549c35b7b0c95d25c2c047702250a94a6a Mon Sep 17 00:00:00 2001 From: Andi Date: Sat, 26 Jul 2025 16:29:30 +0200 Subject: [PATCH 39/42] Basic ChronoGun functionality --- GeneralsMD/Code/GameEngine/CMakeLists.txt | 2 + .../GameEngine/Include/Common/DisabledTypes.h | 243 +- .../GameEngine/Include/Common/GlobalData.h | 1137 +- .../Code/GameEngine/Include/Common/INI.h | 857 +- .../GameEngine/Include/GameLogic/Damage.h | 776 +- .../Include/GameLogic/Module/ActiveBody.h | 12 + .../Include/GameLogic/Module/BodyModule.h | 10 + .../GameLogic/Module/ChronoDamageHelper.h | 68 + .../GameLogic/Module/RadiusDecalBehavior.h | 252 +- .../GameLogic/Module/UpgradeSpecialPower.h | 156 +- .../GameEngine/Include/GameLogic/Object.h | 1671 +- .../GameEngine/Source/Common/GlobalData.cpp | 2661 ++-- .../Code/GameEngine/Source/Common/INI/INI.cpp | 4196 ++--- .../Source/Common/System/DisabledTypes.cpp | 125 +- .../Source/Common/System/MemoryInit.cpp | 1633 +- .../Source/Common/Thing/ModuleFactory.cpp | 1498 +- .../Source/GameLogic/Object/Armor.cpp | 382 +- .../Behavior/DelayedUpgradeBehavior.cpp | 496 +- .../GameLogic/Object/Body/ActiveBody.cpp | 3465 +++-- .../Source/GameLogic/Object/Die/DieModule.cpp | 12 +- .../Object/Helper/ChronoDamageHelper.cpp | 133 + .../Source/GameLogic/Object/Locomotor.cpp | 5688 +++---- .../Source/GameLogic/Object/Object.cpp | 12984 ++++++++-------- .../SpecialPower/UpgradeSpecialPower.cpp | 350 +- .../Object/Update/RadiusDecalBehavior.cpp | 396 +- .../Object/Upgrade/LocomotorSetUpgrade.cpp | 292 +- .../Source/GameLogic/System/Damage.cpp | 409 +- 27 files changed, 20185 insertions(+), 19719 deletions(-) create mode 100644 GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ChronoDamageHelper.h create mode 100644 GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Helper/ChronoDamageHelper.cpp diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index 18fd9478830..5ad610481ea 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -450,6 +450,7 @@ set(GAMEENGINE_SRC Include/GameLogic/Module/StructureCollapseUpdate.h Include/GameLogic/Module/StructureToppleUpdate.h Include/GameLogic/Module/SubdualDamageHelper.h + Include/GameLogic/Module/ChronoDamageHelper.h Include/GameLogic/Module/SubObjectsUpgrade.h Include/GameLogic/Module/SupplyCenterCreate.h Include/GameLogic/Module/SupplyCenterDockUpdate.h @@ -956,6 +957,7 @@ set(GAMEENGINE_SRC Source/GameLogic/Object/Helper/ObjectWeaponStatusHelper.cpp Source/GameLogic/Object/Helper/StatusDamageHelper.cpp Source/GameLogic/Object/Helper/SubdualDamageHelper.cpp + Source/GameLogic/Object/Helper/ChronoDamageHelper.cpp Source/GameLogic/Object/Helper/TempWeaponBonusHelper.cpp Source/GameLogic/Object/Locomotor.cpp Source/GameLogic/Object/Object.cpp diff --git a/GeneralsMD/Code/GameEngine/Include/Common/DisabledTypes.h b/GeneralsMD/Code/GameEngine/Include/Common/DisabledTypes.h index c25d1ee012c..250109c782e 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/DisabledTypes.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/DisabledTypes.h @@ -1,121 +1,122 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: DisabledTypes.h ////////////////////////////////////////////////////////////////////////// -// Author: Kris Morness, September 2002 -// Desc: Defines all the types of disabled statii any given object can have. -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __DISABLED_TYPES_H_ -#define __DISABLED_TYPES_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "Lib/BaseType.h" -#include "Common/BitFlags.h" -#include "Common/BitFlagsIO.h" - -//------------------------------------------------------------------------------------------------- -/** Kind of flags for determining groups of things that belong together - * NOTE: You *MUST* keep this in the same order as the DisabledNames[] below */ -//------------------------------------------------------------------------------------------------- -enum DisabledType CPP_11(: Int) -{ - DISABLED_DEFAULT, //Typical disable -- like systems, things that don't need to run. - DISABLED_HACKED, //This unit has been hacked - DISABLED_EMP, //This unit has been disabled via electro-magnetic-pulse. - DISABLED_HELD, //Special case -- held means it can fire and isHeld checks to make sure ONLY held is set! - DISABLED_PARALYZED, //Battle plans have changed, and unit is confused/paralyzed - DISABLED_UNMANNED, //Vehicle is unmanned - DISABLED_UNDERPOWERED,//Seperate from ScriptUnderpowered, the owning player has insufficient power. Energy status controls this - DISABLED_FREEFALL, //This unit has been disabled via being in free fall - - DISABLED_AWESTRUCK, - DISABLED_BRAINWASHED, - DISABLED_SUBDUED, ///< Temporarily shut down by Subdual damage - //These ones are specificially for scripts to enable/reenable! - DISABLED_SCRIPT_DISABLED, - DISABLED_SCRIPT_UNDERPOWERED, - - DISABLED_TELEPORT, // Chrono Legionnaire after teleporting - - DISABLED_COUNT, - - DISABLED_ANY = 65535 ///< Do not use this value for setting disabled types (read-only) -}; - -typedef BitFlags DisabledMaskType; - -#define MAKE_DISABLED_MASK(k) DisabledMaskType(DisabledMaskType::kInit, (k)) -#define MAKE_DISABLED_MASK2(k,a) DisabledMaskType(DisabledMaskType::kInit, (k), (a)) -#define MAKE_DISABLED_MASK3(k,a,b) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b)) -#define MAKE_DISABLED_MASK4(k,a,b,c) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b), (c)) -#define MAKE_DISABLED_MASK5(k,a,b,c,d) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b), (c), (d)) - -inline Bool TEST_DISABLEDMASK(const DisabledMaskType& m, DisabledType t) -{ - return m.test(t); -} - -inline Bool TEST_DISABLEDMASK_ANY(const DisabledMaskType& m, const DisabledMaskType& mask) -{ - return m.anyIntersectionWith(mask); -} - -inline Bool TEST_DISABLEDMASK_MULTI(const DisabledMaskType& m, const DisabledMaskType& mustBeSet, const DisabledMaskType& mustBeClear) -{ - return m.testSetAndClear(mustBeSet, mustBeClear); -} - -inline Bool DISABLEDMASK_ANY_SET(const DisabledMaskType& m) -{ - return m.any(); -} - -inline void CLEAR_DISABLEDMASK(DisabledMaskType& m) -{ - m.clear(); -} - -inline void SET_ALL_DISABLEDMASK_BITS(DisabledMaskType& m) -{ - m.clear(); - m.flip(); -} - -inline void FLIP_DISABLEDMASK(DisabledMaskType& m) -{ - m.flip(); -} - - - -// defined in Common/System/DisabledTypes.cpp -extern DisabledMaskType DISABLEDMASK_NONE; // inits to all zeroes -extern DisabledMaskType DISABLEDMASK_ALL; // inits to all bits set. -void initDisabledMasks(); - -#endif // __DISABLED_TYPES_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: DisabledTypes.h ////////////////////////////////////////////////////////////////////////// +// Author: Kris Morness, September 2002 +// Desc: Defines all the types of disabled statii any given object can have. +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __DISABLED_TYPES_H_ +#define __DISABLED_TYPES_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Lib/BaseType.h" +#include "Common/BitFlags.h" +#include "Common/BitFlagsIO.h" + +//------------------------------------------------------------------------------------------------- +/** Kind of flags for determining groups of things that belong together + * NOTE: You *MUST* keep this in the same order as the DisabledNames[] below */ +//------------------------------------------------------------------------------------------------- +enum DisabledType CPP_11(: Int) +{ + DISABLED_DEFAULT, //Typical disable -- like systems, things that don't need to run. + DISABLED_HACKED, //This unit has been hacked + DISABLED_EMP, //This unit has been disabled via electro-magnetic-pulse. + DISABLED_HELD, //Special case -- held means it can fire and isHeld checks to make sure ONLY held is set! + DISABLED_PARALYZED, //Battle plans have changed, and unit is confused/paralyzed + DISABLED_UNMANNED, //Vehicle is unmanned + DISABLED_UNDERPOWERED,//Seperate from ScriptUnderpowered, the owning player has insufficient power. Energy status controls this + DISABLED_FREEFALL, //This unit has been disabled via being in free fall + + DISABLED_AWESTRUCK, + DISABLED_BRAINWASHED, + DISABLED_SUBDUED, ///< Temporarily shut down by Subdual damage + //These ones are specificially for scripts to enable/reenable! + DISABLED_SCRIPT_DISABLED, + DISABLED_SCRIPT_UNDERPOWERED, + + DISABLED_TELEPORT, // Chrono Legionnaire after teleporting + DISABLED_CHRONO, // Chrono Gun removal + + DISABLED_COUNT, + + DISABLED_ANY = 65535 ///< Do not use this value for setting disabled types (read-only) +}; + +typedef BitFlags DisabledMaskType; + +#define MAKE_DISABLED_MASK(k) DisabledMaskType(DisabledMaskType::kInit, (k)) +#define MAKE_DISABLED_MASK2(k,a) DisabledMaskType(DisabledMaskType::kInit, (k), (a)) +#define MAKE_DISABLED_MASK3(k,a,b) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b)) +#define MAKE_DISABLED_MASK4(k,a,b,c) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b), (c)) +#define MAKE_DISABLED_MASK5(k,a,b,c,d) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b), (c), (d)) + +inline Bool TEST_DISABLEDMASK(const DisabledMaskType& m, DisabledType t) +{ + return m.test(t); +} + +inline Bool TEST_DISABLEDMASK_ANY(const DisabledMaskType& m, const DisabledMaskType& mask) +{ + return m.anyIntersectionWith(mask); +} + +inline Bool TEST_DISABLEDMASK_MULTI(const DisabledMaskType& m, const DisabledMaskType& mustBeSet, const DisabledMaskType& mustBeClear) +{ + return m.testSetAndClear(mustBeSet, mustBeClear); +} + +inline Bool DISABLEDMASK_ANY_SET(const DisabledMaskType& m) +{ + return m.any(); +} + +inline void CLEAR_DISABLEDMASK(DisabledMaskType& m) +{ + m.clear(); +} + +inline void SET_ALL_DISABLEDMASK_BITS(DisabledMaskType& m) +{ + m.clear(); + m.flip(); +} + +inline void FLIP_DISABLEDMASK(DisabledMaskType& m) +{ + m.flip(); +} + + + +// defined in Common/System/DisabledTypes.cpp +extern DisabledMaskType DISABLEDMASK_NONE; // inits to all zeroes +extern DisabledMaskType DISABLEDMASK_ALL; // inits to all bits set. +void initDisabledMasks(); + +#endif // __DISABLED_TYPES_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h index 2256d749b96..d420af04731 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h @@ -1,565 +1,572 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: GlobalData.h ///////////////////////////////////////////////////////////////////////////// -// Global data used by both the client and logic -// Author: trolfs, Michae Booth, Colin Day, April 2001 -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef _GLOBALDATA_H_ -#define _GLOBALDATA_H_ - -#include "Common/GameCommon.h" // ensure we get DUMP_PERF_STATS, or not -#include "Common/AsciiString.h" -#include "Common/GameType.h" -#include "Common/GameMemory.h" -#include "Common/SubsystemInterface.h" -#include "GameClient/Color.h" -#include "GameClient/TintStatus.h" -#include "Common/STLTypedefs.h" -#include "Common/GameCommon.h" -#include "Common/Money.h" - -// FORWARD DECLARATIONS /////////////////////////////////////////////////////////////////////////// -struct FieldParse; -enum _TerrainLOD CPP_11(: Int); -class GlobalData; -class INI; -class WeaponBonusSet; -enum BodyDamageType CPP_11(: Int); -enum AIDebugOptions CPP_11(: Int); -//enum DrawableColorTint CPP_11(: Int); - -// PUBLIC ///////////////////////////////////////////////////////////////////////////////////////// - -const Int MAX_GLOBAL_LIGHTS = 3; - -//------------------------------------------------------------------------------------------------- -/** Global data container class - * Defines all global game data used by the system - * @todo Change this entire system. Otherwise this will end up a huge class containing tons of variables, - * and will cause re-compilation dependancies throughout the codebase. - * OOPS -- TOO LATE! :) */ -//------------------------------------------------------------------------------------------------- -class GlobalData : public SubsystemInterface -{ - -public: - - GlobalData(); - virtual ~GlobalData(); - - void init(); - void reset(); - void update() { } - - Bool setTimeOfDay( TimeOfDay tod ); ///< Use this function to set the Time of day; - - static void parseGameDataDefinition( INI* ini ); - - //----------------------------------------------------------------------------------------------- - struct TerrainLighting - { - RGBColor ambient; - RGBColor diffuse; - Coord3D lightPos; - }; - - //----------------------------------------------------------------------------------------------- - //----------------------------------------------------------------------------------------------- - //----------------------------------------------------------------------------------------------- - - AsciiString m_mapName; ///< hack for now, this whole this is going away - AsciiString m_moveHintName; - Bool m_useTrees; - Bool m_useTreeSway; - Bool m_useDrawModuleLOD; - Bool m_useHeatEffects; - Bool m_useFpsLimit; - Bool m_dumpAssetUsage; - Int m_framesPerSecondLimit; - Int m_chipSetType; /// m_standardPublicBones; - - Real m_standardMinefieldDensity; - Real m_standardMinefieldDistance; - - - Bool m_showMetrics; ///< whether or not to show the metrics. - Money m_defaultStartingCash; ///< The amount of cash a player starts with by default. - - Bool m_debugShowGraphicalFramerate; ///< Whether or not to show the graphical framerate bar. - - Int m_powerBarBase; ///< Logrithmic base for the power bar scale - Real m_powerBarIntervals; ///< how many logrithmic intervals the width will be divided into - Int m_powerBarYellowRange; ///< Red if consumption exceeds production, yellow if consumption this close but under, green if further under - Real m_displayGamma; ///. +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: GlobalData.h ///////////////////////////////////////////////////////////////////////////// +// Global data used by both the client and logic +// Author: trolfs, Michae Booth, Colin Day, April 2001 +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef _GLOBALDATA_H_ +#define _GLOBALDATA_H_ + +#include "Common/GameCommon.h" // ensure we get DUMP_PERF_STATS, or not +#include "Common/AsciiString.h" +#include "Common/GameType.h" +#include "Common/GameMemory.h" +#include "Common/SubsystemInterface.h" +#include "GameClient/Color.h" +#include "GameClient/TintStatus.h" +#include "Common/STLTypedefs.h" +#include "Common/GameCommon.h" +#include "Common/Money.h" + +// FORWARD DECLARATIONS /////////////////////////////////////////////////////////////////////////// +struct FieldParse; +enum _TerrainLOD CPP_11(: Int); +class GlobalData; +class INI; +class WeaponBonusSet; +enum BodyDamageType CPP_11(: Int); +enum AIDebugOptions CPP_11(: Int); +typedef UnsignedInt DeathTypeFlags; +//enum DrawableColorTint CPP_11(: Int); + +// PUBLIC ///////////////////////////////////////////////////////////////////////////////////////// + +const Int MAX_GLOBAL_LIGHTS = 3; + +//------------------------------------------------------------------------------------------------- +/** Global data container class + * Defines all global game data used by the system + * @todo Change this entire system. Otherwise this will end up a huge class containing tons of variables, + * and will cause re-compilation dependancies throughout the codebase. + * OOPS -- TOO LATE! :) */ +//------------------------------------------------------------------------------------------------- +class GlobalData : public SubsystemInterface +{ + +public: + + GlobalData(); + virtual ~GlobalData(); + + void init(); + void reset(); + void update() { } + + Bool setTimeOfDay( TimeOfDay tod ); ///< Use this function to set the Time of day; + + static void parseGameDataDefinition( INI* ini ); + + //----------------------------------------------------------------------------------------------- + struct TerrainLighting + { + RGBColor ambient; + RGBColor diffuse; + Coord3D lightPos; + }; + + //----------------------------------------------------------------------------------------------- + //----------------------------------------------------------------------------------------------- + //----------------------------------------------------------------------------------------------- + + AsciiString m_mapName; ///< hack for now, this whole this is going away + AsciiString m_moveHintName; + Bool m_useTrees; + Bool m_useTreeSway; + Bool m_useDrawModuleLOD; + Bool m_useHeatEffects; + Bool m_useFpsLimit; + Bool m_dumpAssetUsage; + Int m_framesPerSecondLimit; + Int m_chipSetType; /// m_standardPublicBones; + + Real m_standardMinefieldDensity; + Real m_standardMinefieldDistance; + + + Bool m_showMetrics; ///< whether or not to show the metrics. + Money m_defaultStartingCash; ///< The amount of cash a player starts with by default. + + Bool m_debugShowGraphicalFramerate; ///< Whether or not to show the graphical framerate bar. + + Int m_powerBarBase; ///< Logrithmic base for the power bar scale + Real m_powerBarIntervals; ///< how many logrithmic intervals the width will be divided into + Int m_powerBarYellowRange; ///< Red if consumption exceeds production, yellow if consumption this close but under, green if further under + Real m_displayGamma; ///. -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: INI.h //////////////////////////////////////////////////////////////////////////////////// -// Author: Colin Day, November 2001 -// Desc: INI Reader -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __INI_H_ -#define __INI_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include // for offsetof, which we don't use but everyone who includes us does -#include "Common/STLTypedefs.h" -#include "Common/AsciiString.h" -#include "Common/GameCommon.h" - -//------------------------------------------------------------------------------------------------- -class INI; -class Xfer; -class File; -enum ScienceType CPP_11(: Int); - -//------------------------------------------------------------------------------------------------- -/** These control the behavior of loading the INI data into items */ -//------------------------------------------------------------------------------------------------- -enum INILoadType CPP_11(: Int) -{ - INI_LOAD_INVALID, ///< invalid load type - INI_LOAD_OVERWRITE, ///< create new or load *over* existing data instance - INI_LOAD_CREATE_OVERRIDES, ///< create new or load into *new* override data instance - INI_LOAD_MULTIFILE ///< create new or continue loading into existing data instance. -}; - -//------------------------------------------------------------------------------------------------- -/** INI constant defines */ -//------------------------------------------------------------------------------------------------- -enum -{ - INI_MAX_CHARS_PER_LINE = 1028, ///< max characters per line entry in any ini file -}; - -//------------------------------------------------------------------------------------------------- -/** Status return codes for the INI reader */ -//------------------------------------------------------------------------------------------------- -enum -{ - // we map all of these to the same "real" error code, because - // we generally don't care why it failed; but since the code distinguishes, - // I didn't want to wipe out that intelligence. if we ever need to distinguish - // failure modes at runtime, just put in real values for these. - INI_CANT_SEARCH_DIR = ERROR_BAD_INI, - INI_INVALID_DIRECTORY = ERROR_BAD_INI, - INI_INVALID_PARAMS = ERROR_BAD_INI, - INI_INVALID_NAME_LIST = ERROR_BAD_INI, - INI_INVALID_DATA = ERROR_BAD_INI, - INI_MISSING_END_TOKEN = ERROR_BAD_INI, - INI_UNKNOWN_TOKEN = ERROR_BAD_INI, - INI_BUFFER_TOO_SMALL = ERROR_BAD_INI, - INI_FILE_NOT_OPEN = ERROR_BAD_INI, - INI_FILE_ALREADY_OPEN = ERROR_BAD_INI, - INI_CANT_OPEN_FILE = ERROR_BAD_INI, - INI_UNKNOWN_ERROR = ERROR_BAD_INI, - INI_END_OF_FILE = ERROR_BAD_INI -}; - -//------------------------------------------------------------------------------------------------- -/** Function typedef for parsing data block fields. - * - * buffer - the character buffer of the line from INI that we are reading and parsing - * instance - instance of what we're loading (for example a thingtemplate instance) - * store - where to store the data parsed, this is a field in the *instance* 'instance' - */ -//------------------------------------------------------------------------------------------------- -typedef void (*INIFieldParseProc)( INI *ini, void *instance, void *store, const void* userData ); - -//------------------------------------------------------------------------------------------------- -typedef const char* ConstCharPtr; -typedef ConstCharPtr* ConstCharPtrArray; - -//------------------------------------------------------------------------------------------------- -struct LookupListRec -{ - const char* name; - Int value; -}; -typedef const LookupListRec *ConstLookupListRecArray; - -//------------------------------------------------------------------------------------------------- -/** Parse tables for all fields of each data block are created using these */ -//------------------------------------------------------------------------------------------------- -struct FieldParse -{ - const char* token; ///< token of the field - INIFieldParseProc parse; ///< the parse function - const void* userData; ///< field-specific data - Int offset; ///< offset to data field - - inline void set(const char* t, INIFieldParseProc p, const void* u, Int o) - { - token = t; - parse = p; - userData = u; - offset = o; - } -}; - -//------------------------------------------------------------------------------------------------- -class MultiIniFieldParse -{ -private: - enum { MAX_MULTI_FIELDS = 16 }; - - const FieldParse* m_fieldParse[MAX_MULTI_FIELDS]; - UnsignedInt m_extraOffset[MAX_MULTI_FIELDS]; - Int m_count; - -public: - MultiIniFieldParse() : m_count(0) - { - //Added By Sadullah Nader - //Initializations missing and needed - for(Int i = 0; i < MAX_MULTI_FIELDS; i++) - m_extraOffset[i] = 0; - // - - } - - void add(const FieldParse* f, UnsignedInt e = 0); - - inline Int getCount() const { return m_count; } - inline const FieldParse* getNthFieldParse(Int i) const { return m_fieldParse[i]; } - inline UnsignedInt getNthExtraOffset(Int i) const { return m_extraOffset[i]; } -}; - -//------------------------------------------------------------------------------------------------- -/** Function typedef for parsing INI types blocks */ -//------------------------------------------------------------------------------------------------- -typedef void (*INIBlockParse)( INI *ini ); -typedef void (*BuildMultiIniFieldProc)(MultiIniFieldParse& p); - -//------------------------------------------------------------------------------------------------- -/** INI Reader interface */ -//------------------------------------------------------------------------------------------------- -class INI -{ - INI(const INI&); - INI& operator=(const INI&); - -public: - - INI(); - ~INI(); - - void loadDirectory( AsciiString dirName, Bool subdirs, INILoadType loadType, Xfer *pXfer ); ///< load directory of INI files - void load( AsciiString filename, INILoadType loadType, Xfer *pXfer ); ///< load INI file - - static Bool isDeclarationOfType( AsciiString blockType, AsciiString blockName, char *bufferToCheck ); - static Bool isEndOfBlock( char *bufferToCheck ); - - // data type parsing (the highest level of what type of thing we're parsing) - static void parseObjectDefinition( INI *ini ); - static void parseObjectReskinDefinition( INI *ini ); - static void parseObjectExtendDefinition( INI* ini ); - static void parseWeaponTemplateDefinition( INI *ini ); - static void parseScienceDefinition( INI *ini ); - static void parseRankDefinition( INI *ini ); - static void parseCrateTemplateDefinition( INI *ini ); - static void parseLocomotorTemplateDefinition( INI *ini ); - static void parseLanguageDefinition( INI *ini ); - static void parsePlayerTemplateDefinition( INI *ini ); - static void parseGameDataDefinition( INI *ini ); - static void parseMapDataDefinition( INI *ini ); - static void parseAnim2DDefinition( INI *ini ); - static void parseAudioEventDefinition( INI *ini ); - static void parseDialogDefinition( INI *ini ); - static void parseMusicTrackDefinition( INI *ini ); - static void parseWebpageURLDefinition( INI *ini ); - static void parseHeaderTemplateDefinition( INI *ini ); - static void parseParticleSystemDefinition( INI *ini ); - static void parseWaterSettingDefinition( INI *ini ); - static void parseWaterTransparencyDefinition( INI *ini ); - static void parseWeatherDefinition( INI *ini ); - static void parseMappedImageDefinition( INI *ini ); - static void parseArmorDefinition( INI *ini ); - static void parseArmorExtendDefinition( INI *ini ); - static void parseDamageFXDefinition( INI *ini ); - static void parseDrawGroupNumberDefinition( INI *ini ); - static void parseTerrainDefinition( INI *ini ); - static void parseTerrainRoadDefinition( INI *ini ); - static void parseTerrainBridgeDefinition( INI *ini ); - static void parseMetaMapDefinition( INI *ini ); - static void parseFXListDefinition( INI *ini ); - static void parseObjectCreationListDefinition( INI* ini ); - static void parseMultiplayerSettingsDefinition( INI* ini ); - static void parseMultiplayerColorDefinition( INI* ini ); - static void parseMultiplayerStartingMoneyChoiceDefinition( INI* ini ); - static void parseOnlineChatColorDefinition( INI* ini ); - static void parseMapCacheDefinition( INI* ini ); - static void parseVideoDefinition( INI* ini ); - static void parseCommandButtonDefinition( INI *ini ); - static void parseCommandSetDefinition( INI *ini ); - static void parseUpgradeDefinition( INI *ini ); - static void parseMouseDefinition( INI* ini ); - static void parseMouseCursorDefinition( INI* ini ); - static void parseAIDataDefinition( INI *ini ); - static void parseSpecialPowerDefinition( INI *ini ); - static void parseInGameUIDefinition( INI *ini ); - static void parseControlBarSchemeDefinition( INI *ini ); - static void parseControlBarResizerDefinition( INI *ini ); - static void parseShellMenuSchemeDefinition( INI *ini ); - static void parseCampaignDefinition( INI *ini ); - static void parseAudioSettingsDefinition( INI *ini ); - static void parseMiscAudio( INI *ini ); - static void parseStaticGameLODDefinition( INI *ini); - static void parseDynamicGameLODDefinition( INI *ini); - static void parseStaticGameLODLevel( INI* ini, void * , void *store, const void*); - static void parseDynamicGameLODLevel( INI* ini, void * , void *store, const void*); - static void parseLODPreset( INI* ini); - static void parseBenchProfile( INI* ini); - static void parseEvaEvent( INI* ini ); - static void parseCredits( INI* ini ); - static void parseWindowTransitions( INI* ini ); - static void parseChallengeModeDefinition( INI* ini ); - - inline AsciiString getFilename( void ) const { return m_filename; } - inline INILoadType getLoadType( void ) const { return m_loadType; } - inline UnsignedInt getLineNum( void ) const { return m_lineNum; } - inline const char *getSeps( void ) const { return m_seps; } - inline const char *getSepsPercent( void ) const { return m_sepsPercent; } - inline const char *getSepsColon( void ) const { return m_sepsColon; } - inline const char *getSepsQuote( void ) { return m_sepsQuote; } - inline Bool isEOF( void ) const { return m_endOfFile; } - - void initFromINI( void *what, const FieldParse* parseTable ); - void initFromINIMulti( void *what, const MultiIniFieldParse& parseTableList ); - void initFromINIMultiProc( void *what, BuildMultiIniFieldProc proc ); - - static void parseUnsignedByte( INI *ini, void *instance, void *store, const void* userData ); - static void parseShort( INI *ini, void *instance, void *store, const void* userData ); - static void parseUnsignedShort( INI *ini, void *instance, void *store, const void* userData ); - static void parseInt( INI *ini, void *instance, void *store, const void* userData ); - static void parseUnsignedInt( INI *ini, void *instance, void *store, const void* userData ); - static void parseReal( INI *ini, void *instance, void *store, const void* userData ); - static void parsePositiveNonZeroReal( INI *ini, void *instance, void *store, const void* userData ); - static void parseBool( INI *ini, void *instance, void *store, const void* userData ); - static void parseBitInInt32( INI *ini, void *instance, void *store, const void* userData ); - static void parseAsciiString( INI *ini, void *instance, void *store, const void* userData ); - static void parseQuotedAsciiString( INI *ini, void *instance, void *store, const void* userData ); - static void parseAsciiStringVector( INI *ini, void *instance, void *store, const void* userData ); - static void parseAsciiStringVectorAppend( INI *ini, void *instance, void *store, const void* userData ); - static void parseAndTranslateLabel( INI *ini, void *instance, void *store, const void* userData ); - static void parseMappedImage( INI *ini, void *instance, void *store, const void *userData ); - static void parseAnim2DTemplate( INI *ini, void *instance, void *store, const void *userData ); - static void parsePercentToReal( INI *ini, void *instance, void *store, const void* userData ); - static void parseRGBColor( INI *ini, void *instance, void *store, const void* userData ); - static void parseRGBColorReal( INI *ini, void *instance, void *store, const void* userData ); - static void parseRGBAColorInt( INI *ini, void *instance, void *store, const void* userData ); - static void parseColorInt( INI *ini, void *instance, void *store, const void* userData ); - static void parseCoord3D( INI *ini, void *instance, void *store, const void* userData ); - static void parseCoord2D( INI *ini, void *instance, void *store, const void *userData ); - static void parseICoord2D( INI *ini, void *instance, void *store, const void *userData ); - static void parseDynamicAudioEventRTS( INI *ini, void *instance, void *store, const void* userData ); - static void parseAudioEventRTS( INI *ini, void *instance, void *store, const void* userData ); - static void parseFXList( INI *ini, void *instance, void *store, const void* userData ); - static void parseParticleSystemTemplate( INI *ini, void *instance, void *store, const void *userData ); - static void parseObjectCreationList( INI *ini, void *instance, void *store, const void* userData ); - static void parseSpecialPowerTemplate( INI *ini, void *instance, void *store, const void *userData ); - static void parseUpgradeTemplate( INI *ini, void *instance, void *store, const void *userData ); - static void parseScience( INI *ini, void *instance, void *store, const void *userData ); - static void parseScienceVector( INI *ini, void *instance, void *store, const void *userData ); - static void parseWeaponBonusVector( INI *ini, void *instance, void *store, const void *userData ); - static void parseWeaponBonusVectorKeepDefault( INI *ini, void *instance, void *store, const void *userData ); - static void parseGameClientRandomVariable( INI* ini, void *instance, void *store, const void* userData ); - static void parseBitString8( INI *ini, void *instance, void *store, const void* userData ); - static void parseBitString32( INI *ini, void *instance, void *store, const void* userData ); - static void parseByteSizedIndexList( INI *ini, void *instance, void *store, const void* userData ); - static void parseIndexList( INI *ini, void *instance, void *store, const void* userData ); - static void parseIndexListOrNone( INI *ini, void *instance, void *store, const void* userData ); - static void parseLookupList( INI *ini, void *instance, void *store, const void* userData ); - static void parseThingTemplate( INI *ini, void *instance, void *store, const void* userData ); - static void parseArmorTemplate( INI *ini, void *instance, void *store, const void* userData ); - static void parseDamageFX( INI *ini, void *instance, void *store, const void* userData ); - static void parseWeaponTemplate( INI *ini, void *instance, void *store, const void* userData ); - // parse a duration in msec and convert to duration in frames - static void parseDurationReal( INI *ini, void *instance, void *store, const void* userData ); - // parse a duration in msec and convert to duration in integral number of frames, (unsignedint) rounding UP - static void parseDurationUnsignedInt( INI *ini, void *instance, void *store, const void* userData ); - static void parseDurationUnsignedShort( INI *ini, void *instance, void *store, const void *userData ); - // parse acceleration in (dist/sec) and convert to (dist/frame) - static void parseVelocityReal( INI *ini, void *instance, void *store, const void* userData ); - // parse acceleration in (dist/sec^2) and convert to (dist/frame^2) - static void parseAccelerationReal( INI *ini, void *instance, void *store, const void* userData ); - // parse angle in degrees and convert to radians - static void parseAngleReal( INI *ini, void *instance, void *store, const void *userData ); - // note that this parses in degrees/sec, and converts to rads/frame! - static void parseAngularVelocityReal( INI *ini, void *instance, void *store, const void *userData ); - static void parseDamageTypeFlags(INI* ini, void* instance, void* store, const void* userData); - static void parseDeathTypeFlags(INI* ini, void* instance, void* store, const void* userData); - static void parseVeterancyLevelFlags(INI* ini, void* instance, void* store, const void* userData); - static void parseSoundsList( INI* ini, void *instance, void *store, const void* /*userData*/ ); - - // like parseIndexList but special handling for NONE to return -2 (EVA_None) - static void parseEvaNameIndexList(INI* ini, void* instance, void* store, const void* userData); - - /** - return the next token. if seps is null (or omitted), the standard seps are used. - - this will *never* return null; if there are no more tokens, an exception will be thrown. - */ - const char* getNextToken(const char* seps = NULL); - - /** - just like getNextToken(), except that null is returned if no more tokens are present - (rather than throwing an exception). usually you should call getNextToken(), - but for some cases this is handier (ie, parsing a variable-length number of tokens). - */ - const char* getNextTokenOrNull(const char* seps = NULL); - - /** - This is called when the next thing you expect is something like: - - Tag:value - - pass "Tag" (without the colon) for 'expected', and you will have the 'value' - token returned. - - If "Tag" is not the next token, an error is thrown. - */ - const char* getNextSubToken(const char* expected); - - /** - return the next ascii string. this is usually the same the result of getNextToken(), - except that it allows for quote-delimited strings (eg, "foo bar"), so you can - get strings with spaces, and/or empty strings. - */ - AsciiString getNextAsciiString(); - AsciiString getNextQuotedAsciiString(); //fixed version of above. We can't fix the regular one for fear of breaking existing code. :-( - - /** - utility routine that does a sscanf() on the string to get the Science, and throws - an exception if not of the right form. - */ - static ScienceType scanScience(const char* token); - - /** - utility routine that does a sscanf() on the string to get the int, and throws - an exception if not of the right form. - */ - static Int scanInt(const char* token); - - /** - utility routine that does a sscanf() on the string to get the unsigned int, and throws - an exception if not of the right form. - */ - static UnsignedInt scanUnsignedInt(const char* token); - - /** - utility routine that does a sscanf() on the string to get the real, and throws - an exception if not of the right form. - */ - static Real scanReal(const char* token); - static Real scanPercentToReal(const char* token); - - static Int scanIndexList(const char* token, ConstCharPtrArray nameList); - static Int scanLookupList(const char* token, ConstLookupListRecArray lookupList); - - static Bool scanBool(const char* token); - -protected: - - static Bool isValidINIFilename( const char *filename ); ///< is this a valid .ini filename - - void prepFile( AsciiString filename, INILoadType loadType ); - void unPrepFile(); - - void readLine( void ); - - File *m_file; ///< file pointer of file currently loading - - enum - { - INI_READ_BUFFER = 8192 ///< size of internal read buffer - }; - char m_readBuffer[INI_READ_BUFFER]; ///< internal read buffer - unsigned m_readBufferNext; ///< next char in read buffer - unsigned m_readBufferUsed; ///< number of bytes in read buffer - - AsciiString m_filename; ///< filename of file currently loading - INILoadType m_loadType; ///< load time for current file - UnsignedInt m_lineNum; ///< current line number that's been read - char m_buffer[ INI_MAX_CHARS_PER_LINE+1 ];///< buffer to read file contents into - const char *m_seps; ///< for strtok parsing - const char *m_sepsPercent; ///< m_seps with percent delimiter as well - const char *m_sepsColon; ///< m_seps with colon delimiter as well - const char *m_sepsQuote; ///< token to represent a quoted ascii string - const char *m_blockEndToken; ///< token to represent end of data block - Bool m_endOfFile; ///< TRUE when we've hit EOF -#ifdef DEBUG_CRASHING - char m_curBlockStart[ INI_MAX_CHARS_PER_LINE ]; ///< first line of cur block -#endif -}; - -#endif // __INI_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: INI.h //////////////////////////////////////////////////////////////////////////////////// +// Author: Colin Day, November 2001 +// Desc: INI Reader +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __INI_H_ +#define __INI_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include // for offsetof, which we don't use but everyone who includes us does +#include "Common/STLTypedefs.h" +#include "Common/AsciiString.h" +#include "Common/GameCommon.h" + +//------------------------------------------------------------------------------------------------- +class INI; +class Xfer; +class File; +enum ScienceType CPP_11(: Int); + +//------------------------------------------------------------------------------------------------- +/** These control the behavior of loading the INI data into items */ +//------------------------------------------------------------------------------------------------- +enum INILoadType CPP_11(: Int) +{ + INI_LOAD_INVALID, ///< invalid load type + INI_LOAD_OVERWRITE, ///< create new or load *over* existing data instance + INI_LOAD_CREATE_OVERRIDES, ///< create new or load into *new* override data instance + INI_LOAD_MULTIFILE ///< create new or continue loading into existing data instance. +}; + +//------------------------------------------------------------------------------------------------- +/** INI constant defines */ +//------------------------------------------------------------------------------------------------- +enum +{ + INI_MAX_CHARS_PER_LINE = 1028, ///< max characters per line entry in any ini file +}; + +//------------------------------------------------------------------------------------------------- +/** Status return codes for the INI reader */ +//------------------------------------------------------------------------------------------------- +enum +{ + // we map all of these to the same "real" error code, because + // we generally don't care why it failed; but since the code distinguishes, + // I didn't want to wipe out that intelligence. if we ever need to distinguish + // failure modes at runtime, just put in real values for these. + INI_CANT_SEARCH_DIR = ERROR_BAD_INI, + INI_INVALID_DIRECTORY = ERROR_BAD_INI, + INI_INVALID_PARAMS = ERROR_BAD_INI, + INI_INVALID_NAME_LIST = ERROR_BAD_INI, + INI_INVALID_DATA = ERROR_BAD_INI, + INI_MISSING_END_TOKEN = ERROR_BAD_INI, + INI_UNKNOWN_TOKEN = ERROR_BAD_INI, + INI_BUFFER_TOO_SMALL = ERROR_BAD_INI, + INI_FILE_NOT_OPEN = ERROR_BAD_INI, + INI_FILE_ALREADY_OPEN = ERROR_BAD_INI, + INI_CANT_OPEN_FILE = ERROR_BAD_INI, + INI_UNKNOWN_ERROR = ERROR_BAD_INI, + INI_END_OF_FILE = ERROR_BAD_INI +}; + +//------------------------------------------------------------------------------------------------- +/** Function typedef for parsing data block fields. + * + * buffer - the character buffer of the line from INI that we are reading and parsing + * instance - instance of what we're loading (for example a thingtemplate instance) + * store - where to store the data parsed, this is a field in the *instance* 'instance' + */ +//------------------------------------------------------------------------------------------------- +typedef void (*INIFieldParseProc)( INI *ini, void *instance, void *store, const void* userData ); + +//------------------------------------------------------------------------------------------------- +typedef const char* ConstCharPtr; +typedef ConstCharPtr* ConstCharPtrArray; + +//------------------------------------------------------------------------------------------------- +struct LookupListRec +{ + const char* name; + Int value; +}; +typedef const LookupListRec *ConstLookupListRecArray; + +//------------------------------------------------------------------------------------------------- +/** Parse tables for all fields of each data block are created using these */ +//------------------------------------------------------------------------------------------------- +struct FieldParse +{ + const char* token; ///< token of the field + INIFieldParseProc parse; ///< the parse function + const void* userData; ///< field-specific data + Int offset; ///< offset to data field + + inline void set(const char* t, INIFieldParseProc p, const void* u, Int o) + { + token = t; + parse = p; + userData = u; + offset = o; + } +}; + +//------------------------------------------------------------------------------------------------- +class MultiIniFieldParse +{ +private: + enum { MAX_MULTI_FIELDS = 16 }; + + const FieldParse* m_fieldParse[MAX_MULTI_FIELDS]; + UnsignedInt m_extraOffset[MAX_MULTI_FIELDS]; + Int m_count; + +public: + MultiIniFieldParse() : m_count(0) + { + //Added By Sadullah Nader + //Initializations missing and needed + for(Int i = 0; i < MAX_MULTI_FIELDS; i++) + m_extraOffset[i] = 0; + // + + } + + void add(const FieldParse* f, UnsignedInt e = 0); + + inline Int getCount() const { return m_count; } + inline const FieldParse* getNthFieldParse(Int i) const { return m_fieldParse[i]; } + inline UnsignedInt getNthExtraOffset(Int i) const { return m_extraOffset[i]; } +}; + +//------------------------------------------------------------------------------------------------- +/** Function typedef for parsing INI types blocks */ +//------------------------------------------------------------------------------------------------- +typedef void (*INIBlockParse)( INI *ini ); +typedef void (*BuildMultiIniFieldProc)(MultiIniFieldParse& p); + +//------------------------------------------------------------------------------------------------- +/** INI Reader interface */ +//------------------------------------------------------------------------------------------------- +class INI +{ + INI(const INI&); + INI& operator=(const INI&); + +public: + + INI(); + ~INI(); + + void loadDirectory( AsciiString dirName, Bool subdirs, INILoadType loadType, Xfer *pXfer ); ///< load directory of INI files + void load( AsciiString filename, INILoadType loadType, Xfer *pXfer ); ///< load INI file + + static Bool isDeclarationOfType( AsciiString blockType, AsciiString blockName, char *bufferToCheck ); + static Bool isEndOfBlock( char *bufferToCheck ); + + // data type parsing (the highest level of what type of thing we're parsing) + static void parseObjectDefinition( INI *ini ); + static void parseObjectReskinDefinition( INI *ini ); + static void parseObjectExtendDefinition( INI* ini ); + static void parseWeaponTemplateDefinition( INI *ini ); + static void parseScienceDefinition( INI *ini ); + static void parseRankDefinition( INI *ini ); + static void parseCrateTemplateDefinition( INI *ini ); + static void parseLocomotorTemplateDefinition( INI *ini ); + static void parseLanguageDefinition( INI *ini ); + static void parsePlayerTemplateDefinition( INI *ini ); + static void parseGameDataDefinition( INI *ini ); + static void parseMapDataDefinition( INI *ini ); + static void parseAnim2DDefinition( INI *ini ); + static void parseAudioEventDefinition( INI *ini ); + static void parseDialogDefinition( INI *ini ); + static void parseMusicTrackDefinition( INI *ini ); + static void parseWebpageURLDefinition( INI *ini ); + static void parseHeaderTemplateDefinition( INI *ini ); + static void parseParticleSystemDefinition( INI *ini ); + static void parseWaterSettingDefinition( INI *ini ); + static void parseWaterTransparencyDefinition( INI *ini ); + static void parseWeatherDefinition( INI *ini ); + static void parseMappedImageDefinition( INI *ini ); + static void parseArmorDefinition( INI *ini ); + static void parseArmorExtendDefinition( INI *ini ); + static void parseDamageFXDefinition( INI *ini ); + static void parseDrawGroupNumberDefinition( INI *ini ); + static void parseTerrainDefinition( INI *ini ); + static void parseTerrainRoadDefinition( INI *ini ); + static void parseTerrainBridgeDefinition( INI *ini ); + static void parseMetaMapDefinition( INI *ini ); + static void parseFXListDefinition( INI *ini ); + static void parseObjectCreationListDefinition( INI* ini ); + static void parseMultiplayerSettingsDefinition( INI* ini ); + static void parseMultiplayerColorDefinition( INI* ini ); + static void parseMultiplayerStartingMoneyChoiceDefinition( INI* ini ); + static void parseOnlineChatColorDefinition( INI* ini ); + static void parseMapCacheDefinition( INI* ini ); + static void parseVideoDefinition( INI* ini ); + static void parseCommandButtonDefinition( INI *ini ); + static void parseCommandSetDefinition( INI *ini ); + static void parseUpgradeDefinition( INI *ini ); + static void parseMouseDefinition( INI* ini ); + static void parseMouseCursorDefinition( INI* ini ); + static void parseAIDataDefinition( INI *ini ); + static void parseSpecialPowerDefinition( INI *ini ); + static void parseInGameUIDefinition( INI *ini ); + static void parseControlBarSchemeDefinition( INI *ini ); + static void parseControlBarResizerDefinition( INI *ini ); + static void parseShellMenuSchemeDefinition( INI *ini ); + static void parseCampaignDefinition( INI *ini ); + static void parseAudioSettingsDefinition( INI *ini ); + static void parseMiscAudio( INI *ini ); + static void parseStaticGameLODDefinition( INI *ini); + static void parseDynamicGameLODDefinition( INI *ini); + static void parseStaticGameLODLevel( INI* ini, void * , void *store, const void*); + static void parseDynamicGameLODLevel( INI* ini, void * , void *store, const void*); + static void parseLODPreset( INI* ini); + static void parseBenchProfile( INI* ini); + static void parseEvaEvent( INI* ini ); + static void parseCredits( INI* ini ); + static void parseWindowTransitions( INI* ini ); + static void parseChallengeModeDefinition( INI* ini ); + + inline AsciiString getFilename( void ) const { return m_filename; } + inline INILoadType getLoadType( void ) const { return m_loadType; } + inline UnsignedInt getLineNum( void ) const { return m_lineNum; } + inline const char *getSeps( void ) const { return m_seps; } + inline const char *getSepsPercent( void ) const { return m_sepsPercent; } + inline const char *getSepsColon( void ) const { return m_sepsColon; } + inline const char *getSepsQuote( void ) { return m_sepsQuote; } + inline Bool isEOF( void ) const { return m_endOfFile; } + + void initFromINI( void *what, const FieldParse* parseTable ); + void initFromINIMulti( void *what, const MultiIniFieldParse& parseTableList ); + void initFromINIMultiProc( void *what, BuildMultiIniFieldProc proc ); + + static void parseUnsignedByte( INI *ini, void *instance, void *store, const void* userData ); + static void parseShort( INI *ini, void *instance, void *store, const void* userData ); + static void parseUnsignedShort( INI *ini, void *instance, void *store, const void* userData ); + static void parseInt( INI *ini, void *instance, void *store, const void* userData ); + static void parseUnsignedInt( INI *ini, void *instance, void *store, const void* userData ); + static void parseReal( INI *ini, void *instance, void *store, const void* userData ); + static void parsePositiveNonZeroReal( INI *ini, void *instance, void *store, const void* userData ); + static void parseBool( INI *ini, void *instance, void *store, const void* userData ); + static void parseBitInInt32( INI *ini, void *instance, void *store, const void* userData ); + static void parseAsciiString( INI *ini, void *instance, void *store, const void* userData ); + static void parseQuotedAsciiString( INI *ini, void *instance, void *store, const void* userData ); + static void parseAsciiStringVector( INI *ini, void *instance, void *store, const void* userData ); + static void parseAsciiStringVectorAppend( INI *ini, void *instance, void *store, const void* userData ); + static void parseAndTranslateLabel( INI *ini, void *instance, void *store, const void* userData ); + static void parseMappedImage( INI *ini, void *instance, void *store, const void *userData ); + static void parseAnim2DTemplate( INI *ini, void *instance, void *store, const void *userData ); + static void parsePercentToReal( INI *ini, void *instance, void *store, const void* userData ); + static void parseRGBColor( INI *ini, void *instance, void *store, const void* userData ); + static void parseRGBColorReal( INI *ini, void *instance, void *store, const void* userData ); + static void parseRGBAColorInt( INI *ini, void *instance, void *store, const void* userData ); + static void parseColorInt( INI *ini, void *instance, void *store, const void* userData ); + static void parseCoord3D( INI *ini, void *instance, void *store, const void* userData ); + static void parseCoord2D( INI *ini, void *instance, void *store, const void *userData ); + static void parseICoord2D( INI *ini, void *instance, void *store, const void *userData ); + static void parseDynamicAudioEventRTS( INI *ini, void *instance, void *store, const void* userData ); + static void parseAudioEventRTS( INI *ini, void *instance, void *store, const void* userData ); + static void parseFXList( INI *ini, void *instance, void *store, const void* userData ); + static void parseParticleSystemTemplate( INI *ini, void *instance, void *store, const void *userData ); + static void parseObjectCreationList( INI *ini, void *instance, void *store, const void* userData ); + static void parseSpecialPowerTemplate( INI *ini, void *instance, void *store, const void *userData ); + static void parseUpgradeTemplate( INI *ini, void *instance, void *store, const void *userData ); + static void parseScience( INI *ini, void *instance, void *store, const void *userData ); + static void parseScienceVector( INI *ini, void *instance, void *store, const void *userData ); + static void parseWeaponBonusVector( INI *ini, void *instance, void *store, const void *userData ); + static void parseWeaponBonusVectorKeepDefault( INI *ini, void *instance, void *store, const void *userData ); + static void parseGameClientRandomVariable( INI* ini, void *instance, void *store, const void* userData ); + static void parseBitString8( INI *ini, void *instance, void *store, const void* userData ); + static void parseBitString32( INI *ini, void *instance, void *store, const void* userData ); + static void parseByteSizedIndexList( INI *ini, void *instance, void *store, const void* userData ); + static void parseIndexList( INI *ini, void *instance, void *store, const void* userData ); + static void parseIndexListOrNone( INI *ini, void *instance, void *store, const void* userData ); + static void parseLookupList( INI *ini, void *instance, void *store, const void* userData ); + static void parseThingTemplate( INI *ini, void *instance, void *store, const void* userData ); + static void parseArmorTemplate( INI *ini, void *instance, void *store, const void* userData ); + static void parseDamageFX( INI *ini, void *instance, void *store, const void* userData ); + static void parseWeaponTemplate( INI *ini, void *instance, void *store, const void* userData ); + // parse a duration in msec and convert to duration in frames + static void parseDurationReal( INI *ini, void *instance, void *store, const void* userData ); + // parse a duration in msec and convert to duration in integral number of frames, (unsignedint) rounding UP + static void parseDurationUnsignedInt( INI *ini, void *instance, void *store, const void* userData ); + static void parseDurationUnsignedShort( INI *ini, void *instance, void *store, const void *userData ); + // parse acceleration in (dist/sec) and convert to (dist/frame) + static void parseVelocityReal( INI *ini, void *instance, void *store, const void* userData ); + // parse acceleration in (dist/sec^2) and convert to (dist/frame^2) + static void parseAccelerationReal( INI *ini, void *instance, void *store, const void* userData ); + // parse angle in degrees and convert to radians + static void parseAngleReal( INI *ini, void *instance, void *store, const void *userData ); + // note that this parses in degrees/sec, and converts to rads/frame! + static void parseAngularVelocityReal( INI *ini, void *instance, void *store, const void *userData ); + static void parseDamageTypeFlags(INI* ini, void* instance, void* store, const void* userData); + static void parseDeathTypeFlags(INI* ini, void* instance, void* store, const void* userData); + static void parseDeathTypeFlagsList(INI* ini, void* instance, void* store, const void* userData); + static void parseVeterancyLevelFlags(INI* ini, void* instance, void* store, const void* userData); + static void parseSoundsList( INI* ini, void *instance, void *store, const void* /*userData*/ ); + + // like parseIndexList but special handling for NONE to return -2 (EVA_None) + static void parseEvaNameIndexList(INI* ini, void* instance, void* store, const void* userData); + + /** + return the next token. if seps is null (or omitted), the standard seps are used. + + this will *never* return null; if there are no more tokens, an exception will be thrown. + */ + const char* getNextToken(const char* seps = NULL); + + /** + just like getNextToken(), except that null is returned if no more tokens are present + (rather than throwing an exception). usually you should call getNextToken(), + but for some cases this is handier (ie, parsing a variable-length number of tokens). + */ + const char* getNextTokenOrNull(const char* seps = NULL); + + /** + This is called when the next thing you expect is something like: + + Tag:value + + pass "Tag" (without the colon) for 'expected', and you will have the 'value' + token returned. + + If "Tag" is not the next token, an error is thrown. + */ + const char* getNextSubToken(const char* expected); + + /** + return the next ascii string. this is usually the same the result of getNextToken(), + except that it allows for quote-delimited strings (eg, "foo bar"), so you can + get strings with spaces, and/or empty strings. + */ + AsciiString getNextAsciiString(); + AsciiString getNextQuotedAsciiString(); //fixed version of above. We can't fix the regular one for fear of breaking existing code. :-( + + /** + utility routine that does a sscanf() on the string to get the Science, and throws + an exception if not of the right form. + */ + static ScienceType scanScience(const char* token); + + /** + utility routine that does a sscanf() on the string to get the int, and throws + an exception if not of the right form. + */ + static Int scanInt(const char* token); + + /** + utility routine that does a sscanf() on the string to get the unsigned int, and throws + an exception if not of the right form. + */ + static UnsignedInt scanUnsignedInt(const char* token); + + /** + utility routine that does a sscanf() on the string to get the real, and throws + an exception if not of the right form. + */ + static Real scanReal(const char* token); + static Real scanPercentToReal(const char* token); + + static Int scanIndexList(const char* token, ConstCharPtrArray nameList); + static Int scanLookupList(const char* token, ConstLookupListRecArray lookupList); + + static Bool scanBool(const char* token); + +protected: + + static Bool isValidINIFilename( const char *filename ); ///< is this a valid .ini filename + + void prepFile( AsciiString filename, INILoadType loadType ); + void unPrepFile(); + + void readLine( void ); + + File *m_file; ///< file pointer of file currently loading + + enum + { + INI_READ_BUFFER = 8192 ///< size of internal read buffer + }; + char m_readBuffer[INI_READ_BUFFER]; ///< internal read buffer + unsigned m_readBufferNext; ///< next char in read buffer + unsigned m_readBufferUsed; ///< number of bytes in read buffer + + AsciiString m_filename; ///< filename of file currently loading + INILoadType m_loadType; ///< load time for current file + UnsignedInt m_lineNum; ///< current line number that's been read + char m_buffer[ INI_MAX_CHARS_PER_LINE+1 ];///< buffer to read file contents into + const char *m_seps; ///< for strtok parsing + const char *m_sepsPercent; ///< m_seps with percent delimiter as well + const char *m_sepsColon; ///< m_seps with colon delimiter as well + const char *m_sepsQuote; ///< token to represent a quoted ascii string + const char *m_blockEndToken; ///< token to represent end of data block + Bool m_endOfFile; ///< TRUE when we've hit EOF +#ifdef DEBUG_CRASHING + char m_curBlockStart[ INI_MAX_CHARS_PER_LINE ]; ///< first line of cur block +#endif +}; + +#endif // __INI_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h index f1a0a7e96f2..5dbe6269a47 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h @@ -1,376 +1,400 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: Damage.h ///////////////////////////////////////////////////////////////////////////////// -// Author: Colin Day, November 2001 -// Desc: Damage description -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __DAMAGE_H_ -#define __DAMAGE_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "Common/BitFlags.h" -#include "Common/GameType.h" -#include "Common/ObjectStatusTypes.h" // Precompiled header anyway, no detangling possibility -#include "Common/Snapshot.h" - - -// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// -class Object; -class INI; -class ThingTemplate; - -//------------------------------------------------------------------------------------------------- -/** Damage types, keep this in sync with DamageTypeFlags::s_bitNameList[] */ -//------------------------------------------------------------------------------------------------- -enum DamageType CPP_11(: Int) -{ - DAMAGE_EXPLOSION = 0, - DAMAGE_CRUSH = 1, - DAMAGE_ARMOR_PIERCING = 2, - DAMAGE_SMALL_ARMS = 3, - DAMAGE_GATTLING = 4, - DAMAGE_RADIATION = 5, - DAMAGE_FLAME = 6, - DAMAGE_LASER = 7, - DAMAGE_SNIPER = 8, - DAMAGE_POISON = 9, - DAMAGE_HEALING = 10, - DAMAGE_UNRESISTABLE = 11, // this is for scripting to cause 'armorproof' damage - DAMAGE_WATER = 12, - DAMAGE_DEPLOY = 13, // for transports to deploy units and order them to all attack. - DAMAGE_SURRENDER = 14, // if something "dies" to surrender damage, they surrender.... duh! - DAMAGE_HACK = 15, - DAMAGE_KILLPILOT = 16, // special snipe attack that kills the pilot and renders a vehicle unmanned. - DAMAGE_PENALTY = 17, // from game penalty (you won't receive radar warnings BTW) - DAMAGE_FALLING = 18, - DAMAGE_MELEE = 19, // Blades, clubs... - DAMAGE_DISARM = 20, // "special" damage type used for disarming mines, bombs, etc (NOT for "disarming" an opponent!) - DAMAGE_HAZARD_CLEANUP = 21, // special damage type for cleaning up hazards like radiation or bio-poison. - DAMAGE_PARTICLE_BEAM = 22, // Incinerates virtually everything (insanely powerful orbital beam) - DAMAGE_TOPPLING = 23, // damage from getting toppled. - DAMAGE_INFANTRY_MISSILE = 24, - DAMAGE_AURORA_BOMB = 25, - DAMAGE_LAND_MINE = 26, - DAMAGE_JET_MISSILES = 27, - DAMAGE_STEALTHJET_MISSILES = 28, - DAMAGE_MOLOTOV_COCKTAIL = 29, - DAMAGE_COMANCHE_VULCAN = 30, - DAMAGE_SUBDUAL_MISSILE = 31, ///< Damage that does not kill you, but produces some special effect based on your Body Module. Seperate HP from normal damage. - DAMAGE_SUBDUAL_VEHICLE = 32, - DAMAGE_SUBDUAL_BUILDING = 33, - DAMAGE_SUBDUAL_UNRESISTABLE = 34, - DAMAGE_MICROWAVE = 35, ///< Radiation that only affects infantry - DAMAGE_KILL_GARRISONED = 36, ///< Kills Passengers up to the number specified in Damage - DAMAGE_STATUS = 37, ///< Damage that gives a status condition, not that does hitpoint damage - - // Please note: There is a string array DamageTypeFlags::s_bitNameList[] - - DAMAGE_NUM_TYPES // keep this last -}; - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- - -typedef BitFlags DamageTypeFlags; - -inline Bool getDamageTypeFlag(DamageTypeFlags flags, DamageType dt) -{ - return flags.test(dt); -} - -inline DamageTypeFlags setDamageTypeFlag(DamageTypeFlags flags, DamageType dt) -{ - flags.set(dt, TRUE); - return flags; -} - -inline DamageTypeFlags clearDamageTypeFlag(DamageTypeFlags flags, DamageType dt) -{ - flags.set(dt, FALSE); - return flags; -} - -// Instead of checking against a single damage type, gather the question here so we can have more than one -inline Bool IsSubdualDamage( DamageType type ) -{ - switch( type ) - { - case DAMAGE_SUBDUAL_MISSILE: - case DAMAGE_SUBDUAL_VEHICLE: - case DAMAGE_SUBDUAL_BUILDING: - case DAMAGE_SUBDUAL_UNRESISTABLE: - return TRUE; - } - - return FALSE; -} - -/// Does this type of damage go to internalChangeHealth? -inline Bool IsHealthDamagingDamage( DamageType type ) -{ - // The need for this function brought to you by "Have the guy with no game experience write the weapon code" Foundation. - // Health Damage should be one type of WeaponEffect. Thinking "Weapons can only do damage" is why AoE is boring. - switch( type ) - { - case DAMAGE_STATUS: - case DAMAGE_SUBDUAL_MISSILE: - case DAMAGE_SUBDUAL_VEHICLE: - case DAMAGE_SUBDUAL_BUILDING: - case DAMAGE_SUBDUAL_UNRESISTABLE: - case DAMAGE_KILLPILOT: - case DAMAGE_KILL_GARRISONED: - return FALSE; - } - - return TRUE; -} - -inline void SET_ALL_DAMAGE_TYPE_BITS(DamageTypeFlags& m) -{ - m.clear(); - m.flip(); -} - -extern DamageTypeFlags DAMAGE_TYPE_FLAGS_NONE; -extern DamageTypeFlags DAMAGE_TYPE_FLAGS_ALL; -void initDamageTypeFlags(); - - -//------------------------------------------------------------------------------------------------- -/** Death types, keep this in sync with TheDeathNames[] */ -//------------------------------------------------------------------------------------------------- -enum DeathType CPP_11(: Int) -{ - // note that these DELIBERATELY have (slightly) different names from the damage names, - // since there isn't necessarily a one-to-one correspondence. e.g., DEATH_BURNED - // can come from DAMAGE_FLAME but also from DAMAGE_PARTICLE_BEAM. - DEATH_NORMAL = 0, - DEATH_NONE = 1, ///< this is a "special case" that can't normally cause death - DEATH_CRUSHED = 2, - DEATH_BURNED = 3, - DEATH_EXPLODED = 4, - DEATH_POISONED = 5, - DEATH_TOPPLED = 6, - DEATH_FLOODED = 7, - DEATH_SUICIDED = 8, - DEATH_LASERED = 9, - DEATH_DETONATED = 10, /**< this is the "death" that occurs when a missile/warhead/etc detonates normally, - as opposed to being shot down, etc */ - DEATH_SPLATTED = 11, /**< the death that results from DAMAGE_FALLING */ - DEATH_POISONED_BETA = 12, - - // these are the "extra" types for yet-to-be-defined stuff. Don't bother renaming or adding - // or removing these; they are reserved for modders :-) - DEATH_EXTRA_2 = 13, - DEATH_EXTRA_3 = 14, - DEATH_EXTRA_4 = 15, - DEATH_EXTRA_5 = 16, - DEATH_EXTRA_6 = 17, - DEATH_EXTRA_7 = 18, - DEATH_EXTRA_8 = 19, - DEATH_POISONED_GAMMA = 20, - - DEATH_NUM_TYPES // keep this last -}; - -#ifdef DEFINE_DEATH_NAMES -static const char *TheDeathNames[] = -{ - "NORMAL", - "NONE", - "CRUSHED", - "BURNED", - "EXPLODED", - "POISONED", - "TOPPLED", - "FLOODED", - "SUICIDED", - "LASERED", - "DETONATED", - "SPLATTED", - "POISONED_BETA", - "EXTRA_2", - "EXTRA_3", - "EXTRA_4", - "EXTRA_5", - "EXTRA_6", - "EXTRA_7", - "EXTRA_8", - "POISONED_GAMMA", - - NULL -}; -#endif // end DEFINE_DEATH_NAMES - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- - -typedef UnsignedInt DeathTypeFlags; - -const DeathTypeFlags DEATH_TYPE_FLAGS_ALL = 0xffffffff; -const DeathTypeFlags DEATH_TYPE_FLAGS_NONE = 0x00000000; - -inline Bool getDeathTypeFlag(DeathTypeFlags flags, DeathType dt) -{ - return (flags & (1UL << (dt - 1))) != 0; -} - -inline DeathTypeFlags setDeathTypeFlag(DeathTypeFlags flags, DeathType dt) -{ - return (flags | (1UL << (dt - 1))); -} - -inline DeathTypeFlags clearDeathTypeFlag(DeathTypeFlags flags, DeathType dt) -{ - return (flags & ~(1UL << (dt - 1))); -} - -//------------------------------------------------------------------------------------------------- -/** Damage info inputs */ -//------------------------------------------------------------------------------------------------- -class DamageInfoInput : public Snapshot -{ - -public: - - DamageInfoInput( void ) - { - m_sourceID = INVALID_ID; - m_sourceTemplate = NULL; - m_sourcePlayerMask = 0; - m_damageType = DAMAGE_EXPLOSION; - m_damageStatusType = OBJECT_STATUS_NONE; - m_damageFXOverride = DAMAGE_UNRESISTABLE; - m_deathType = DEATH_NORMAL; - m_amount = 0; - m_kill = FALSE; - - m_shockWaveVector.zero(); - m_shockWaveAmount = 0.0f; - m_shockWaveRadius = 0.0f; - m_shockWaveTaperOff = 0.0f; - } - - ObjectID m_sourceID; ///< source of the damage - const ThingTemplate *m_sourceTemplate; ///< source of the damage (the template). - PlayerMaskType m_sourcePlayerMask; ///< Player mask of m_sourceID. - DamageType m_damageType; ///< type of damage - ObjectStatusTypes m_damageStatusType; ///< If status damage, what type - DamageType m_damageFXOverride; ///< If not marked as the default of Unresistable, the damage type to use in doDamageFX instead of the real damamge type - DeathType m_deathType; ///< if this kills us, death type to be used - Real m_amount; ///< # value of how much damage to inflict - Bool m_kill; ///< will always cause object to die regardless of damage. - - // These are used for damage causing shockwave, forcing units affected to be pushed around - Coord3D m_shockWaveVector; ///< This represents the incoming damage vector - Real m_shockWaveAmount; ///< This represents the amount of shockwave created by the damage. 0 = no shockwave, 1.0 = shockwave equal to damage. - Real m_shockWaveRadius; ///< This represents the effect radius of the shockwave. - Real m_shockWaveTaperOff; ///< This represents the taper off effect of the shockwave at the tip of the radius. 0.0 means shockwave is 0% at the radius edge. - - -protected: - - // snapshot methods - virtual void crc( Xfer *xfer ) { } - virtual void xfer( Xfer *xfer ); - virtual void loadPostProcess( void ) { } - -}; - -const Real HUGE_DAMAGE_AMOUNT = 999999.0f; - -//------------------------------------------------------------------------------------------------- -/** Damage into outputs */ -//------------------------------------------------------------------------------------------------- -class DamageInfoOutput : public Snapshot -{ - -public: - - DamageInfoOutput( void ) - { - m_actualDamageDealt = 0; - m_actualDamageClipped = 0; - m_noEffect = false; - } - - /** - m_actualDamageDealt is the damage we tried to apply to object (after multipliers and such). - m_actualDamageClipped is the value of m_actualDamageDealt, but clipped to the max health remaining of the obj. - example: - a mammoth tank fires a round at a small tank, attempting 100 damage. - the small tank has a damage multiplier of 50%, meaning that only 50 damage is applied. - furthermore, the small tank has only 30 health remaining. - so: m_actualDamageDealt = 50, m_actualDamageClipped = 30. - - this distinction is useful, since visual fx really wants to do the fx for "50 damage", - even though it was more than necessary to kill this object; game logic, on the other hand, - may want to know the "clipped" damage for ai purposes. - */ - Real m_actualDamageDealt; - Real m_actualDamageClipped; ///< (see comment for m_actualDamageDealt) - Bool m_noEffect; ///< if true, no damage was done at all (generally due to being InactiveBody) - -protected: - - // snapshot methods - virtual void crc( Xfer *xfer ) { } - virtual void xfer( Xfer *xfer ); - virtual void loadPostProcess( void ) { } - -}; - -//------------------------------------------------------------------------------------------------- -/** DamageInfo is a descriptor of damage we're trying to inflict. The structure - * is divided up into two parts, inputs and outputs. - * - * INPUTS: You must provide valid values for these fields in order for damage - * calculation to correctly take place - * OUTPUT: Upon returning from damage issuing functions, the output fields - * will be filled with the results of the damage occurrence - */ -//------------------------------------------------------------------------------------------------- -class DamageInfo : public Snapshot -{ - -public: - - DamageInfoInput in; ///< inputs for the damage info - DamageInfoOutput out; ///< results for the damage occurrence - -protected: - - virtual void crc( Xfer *xfer ) { } - virtual void xfer( Xfer *xfer ); - virtual void loadPostProcess( void ){ } - -}; - -#endif // __DAMAGE_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: Damage.h ///////////////////////////////////////////////////////////////////////////////// +// Author: Colin Day, November 2001 +// Desc: Damage description +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __DAMAGE_H_ +#define __DAMAGE_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Common/BitFlags.h" +#include "Common/GameType.h" +#include "Common/ObjectStatusTypes.h" // Precompiled header anyway, no detangling possibility +#include "Common/Snapshot.h" + + +// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// +class Object; +class INI; +class ThingTemplate; + +//------------------------------------------------------------------------------------------------- +/** Damage types, keep this in sync with DamageTypeFlags::s_bitNameList[] */ +//------------------------------------------------------------------------------------------------- +enum DamageType CPP_11(: Int) +{ + DAMAGE_EXPLOSION = 0, + DAMAGE_CRUSH = 1, + DAMAGE_ARMOR_PIERCING = 2, + DAMAGE_SMALL_ARMS = 3, + DAMAGE_GATTLING = 4, + DAMAGE_RADIATION = 5, + DAMAGE_FLAME = 6, + DAMAGE_LASER = 7, + DAMAGE_SNIPER = 8, + DAMAGE_POISON = 9, + DAMAGE_HEALING = 10, + DAMAGE_UNRESISTABLE = 11, // this is for scripting to cause 'armorproof' damage + DAMAGE_WATER = 12, + DAMAGE_DEPLOY = 13, // for transports to deploy units and order them to all attack. + DAMAGE_SURRENDER = 14, // if something "dies" to surrender damage, they surrender.... duh! + DAMAGE_HACK = 15, + DAMAGE_KILLPILOT = 16, // special snipe attack that kills the pilot and renders a vehicle unmanned. + DAMAGE_PENALTY = 17, // from game penalty (you won't receive radar warnings BTW) + DAMAGE_FALLING = 18, + DAMAGE_MELEE = 19, // Blades, clubs... + DAMAGE_DISARM = 20, // "special" damage type used for disarming mines, bombs, etc (NOT for "disarming" an opponent!) + DAMAGE_HAZARD_CLEANUP = 21, // special damage type for cleaning up hazards like radiation or bio-poison. + DAMAGE_PARTICLE_BEAM = 22, // Incinerates virtually everything (insanely powerful orbital beam) + DAMAGE_TOPPLING = 23, // damage from getting toppled. + DAMAGE_INFANTRY_MISSILE = 24, + DAMAGE_AURORA_BOMB = 25, + DAMAGE_LAND_MINE = 26, + DAMAGE_JET_MISSILES = 27, + DAMAGE_STEALTHJET_MISSILES = 28, + DAMAGE_MOLOTOV_COCKTAIL = 29, + DAMAGE_COMANCHE_VULCAN = 30, + DAMAGE_SUBDUAL_MISSILE = 31, ///< Damage that does not kill you, but produces some special effect based on your Body Module. Seperate HP from normal damage. + DAMAGE_SUBDUAL_VEHICLE = 32, + DAMAGE_SUBDUAL_BUILDING = 33, + DAMAGE_SUBDUAL_UNRESISTABLE = 34, + DAMAGE_MICROWAVE = 35, ///< Radiation that only affects infantry + DAMAGE_KILL_GARRISONED = 36, ///< Kills Passengers up to the number specified in Damage + DAMAGE_STATUS = 37, ///< Damage that gives a status condition, not that does hitpoint damage + // -- + // Generic additional damage types (no special logic) + DAMAGE_SONIC, + DAMAGE_ACID, + DAMAGE_JET_BOMB, + DAMAGE_ANTI_TANK_GUN, + DAMAGE_ANTI_TANK_MISSILE, + DAMAGE_ANTI_AIR_GUN, + DAMAGE_ANTI_AIR_MISSILE, + DAMAGE_SEISMIC, + DAMAGE_RAD_BEAM, + DAMAGE_TESLA, + + // Specific damage types with special logic attached + DAMAGE_CHRONO_GUN, ///< Disable target and remove them once health threshold is reached + DAMAGE_CHRONO_UNRESISTABLE, ///< Used for recovery from CHRONO_GUN + // DAMAGE_ZOMBIE_VIRUS, // TODO + // DAMAGE_MIND_CONTROL, // TODO + + + // Please note: There is a string array DamageTypeFlags::s_bitNameList[] + + DAMAGE_NUM_TYPES // keep this last +}; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +typedef BitFlags DamageTypeFlags; + +inline Bool getDamageTypeFlag(DamageTypeFlags flags, DamageType dt) +{ + return flags.test(dt); +} + +inline DamageTypeFlags setDamageTypeFlag(DamageTypeFlags flags, DamageType dt) +{ + flags.set(dt, TRUE); + return flags; +} + +inline DamageTypeFlags clearDamageTypeFlag(DamageTypeFlags flags, DamageType dt) +{ + flags.set(dt, FALSE); + return flags; +} + +// Instead of checking against a single damage type, gather the question here so we can have more than one +inline Bool IsSubdualDamage( DamageType type ) +{ + switch( type ) + { + case DAMAGE_SUBDUAL_MISSILE: + case DAMAGE_SUBDUAL_VEHICLE: + case DAMAGE_SUBDUAL_BUILDING: + case DAMAGE_SUBDUAL_UNRESISTABLE: + return TRUE; + } + + return FALSE; +} + +/// Does this type of damage go to internalChangeHealth? +inline Bool IsHealthDamagingDamage( DamageType type ) +{ + // The need for this function brought to you by "Have the guy with no game experience write the weapon code" Foundation. + // Health Damage should be one type of WeaponEffect. Thinking "Weapons can only do damage" is why AoE is boring. + switch( type ) + { + case DAMAGE_STATUS: + case DAMAGE_SUBDUAL_MISSILE: + case DAMAGE_SUBDUAL_VEHICLE: + case DAMAGE_SUBDUAL_BUILDING: + case DAMAGE_SUBDUAL_UNRESISTABLE: + case DAMAGE_KILLPILOT: + case DAMAGE_KILL_GARRISONED: + return FALSE; + } + + return TRUE; +} + +inline void SET_ALL_DAMAGE_TYPE_BITS(DamageTypeFlags& m) +{ + m.clear(); + m.flip(); +} + +extern DamageTypeFlags DAMAGE_TYPE_FLAGS_NONE; +extern DamageTypeFlags DAMAGE_TYPE_FLAGS_ALL; +void initDamageTypeFlags(); + + +//------------------------------------------------------------------------------------------------- +/** Death types, keep this in sync with TheDeathNames[] */ +//------------------------------------------------------------------------------------------------- +enum DeathType CPP_11(: Int) +{ + // note that these DELIBERATELY have (slightly) different names from the damage names, + // since there isn't necessarily a one-to-one correspondence. e.g., DEATH_BURNED + // can come from DAMAGE_FLAME but also from DAMAGE_PARTICLE_BEAM. + DEATH_NORMAL = 0, + DEATH_NONE = 1, ///< this is a "special case" that can't normally cause death + DEATH_CRUSHED = 2, + DEATH_BURNED = 3, + DEATH_EXPLODED = 4, + DEATH_POISONED = 5, + DEATH_TOPPLED = 6, + DEATH_FLOODED = 7, + DEATH_SUICIDED = 8, + DEATH_LASERED = 9, + DEATH_DETONATED = 10, /**< this is the "death" that occurs when a missile/warhead/etc detonates normally, + as opposed to being shot down, etc */ + DEATH_SPLATTED = 11, /**< the death that results from DAMAGE_FALLING */ + DEATH_POISONED_BETA = 12, + + // these are the "extra" types for yet-to-be-defined stuff. Don't bother renaming or adding + // or removing these; they are reserved for modders :-) + DEATH_EXTRA_2 = 13, + DEATH_EXTRA_3 = 14, + DEATH_EXTRA_4 = 15, + DEATH_EXTRA_5 = 16, + DEATH_EXTRA_6 = 17, + DEATH_EXTRA_7 = 18, + DEATH_EXTRA_8 = 19, + DEATH_POISONED_GAMMA = 20, + + //New Death Types + DEATH_CHRONO, + + DEATH_NUM_TYPES // keep this last +}; + +#ifdef DEFINE_DEATH_NAMES +static const char *TheDeathNames[] = +{ + "NORMAL", + "NONE", + "CRUSHED", + "BURNED", + "EXPLODED", + "POISONED", + "TOPPLED", + "FLOODED", + "SUICIDED", + "LASERED", + "DETONATED", + "SPLATTED", + "POISONED_BETA", + "EXTRA_2", + "EXTRA_3", + "EXTRA_4", + "EXTRA_5", + "EXTRA_6", + "EXTRA_7", + "EXTRA_8", + "POISONED_GAMMA", + //New: + "CHRONO", + + NULL +}; +#endif // end DEFINE_DEATH_NAMES + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +typedef UnsignedInt DeathTypeFlags; + +const DeathTypeFlags DEATH_TYPE_FLAGS_ALL = 0xffffffff; +const DeathTypeFlags DEATH_TYPE_FLAGS_NONE = 0x00000000; + +inline Bool getDeathTypeFlag(DeathTypeFlags flags, DeathType dt) +{ + return (flags & (1UL << (dt - 1))) != 0; +} + +inline DeathTypeFlags setDeathTypeFlag(DeathTypeFlags flags, DeathType dt) +{ + return (flags | (1UL << (dt - 1))); +} + +inline DeathTypeFlags clearDeathTypeFlag(DeathTypeFlags flags, DeathType dt) +{ + return (flags & ~(1UL << (dt - 1))); +} + +//------------------------------------------------------------------------------------------------- +/** Damage info inputs */ +//------------------------------------------------------------------------------------------------- +class DamageInfoInput : public Snapshot +{ + +public: + + DamageInfoInput( void ) + { + m_sourceID = INVALID_ID; + m_sourceTemplate = NULL; + m_sourcePlayerMask = 0; + m_damageType = DAMAGE_EXPLOSION; + m_damageStatusType = OBJECT_STATUS_NONE; + m_damageFXOverride = DAMAGE_UNRESISTABLE; + m_deathType = DEATH_NORMAL; + m_amount = 0; + m_kill = FALSE; + + m_shockWaveVector.zero(); + m_shockWaveAmount = 0.0f; + m_shockWaveRadius = 0.0f; + m_shockWaveTaperOff = 0.0f; + } + + ObjectID m_sourceID; ///< source of the damage + const ThingTemplate *m_sourceTemplate; ///< source of the damage (the template). + PlayerMaskType m_sourcePlayerMask; ///< Player mask of m_sourceID. + DamageType m_damageType; ///< type of damage + ObjectStatusTypes m_damageStatusType; ///< If status damage, what type + DamageType m_damageFXOverride; ///< If not marked as the default of Unresistable, the damage type to use in doDamageFX instead of the real damamge type + DeathType m_deathType; ///< if this kills us, death type to be used + Real m_amount; ///< # value of how much damage to inflict + Bool m_kill; ///< will always cause object to die regardless of damage. + + // These are used for damage causing shockwave, forcing units affected to be pushed around + Coord3D m_shockWaveVector; ///< This represents the incoming damage vector + Real m_shockWaveAmount; ///< This represents the amount of shockwave created by the damage. 0 = no shockwave, 1.0 = shockwave equal to damage. + Real m_shockWaveRadius; ///< This represents the effect radius of the shockwave. + Real m_shockWaveTaperOff; ///< This represents the taper off effect of the shockwave at the tip of the radius. 0.0 means shockwave is 0% at the radius edge. + + +protected: + + // snapshot methods + virtual void crc( Xfer *xfer ) { } + virtual void xfer( Xfer *xfer ); + virtual void loadPostProcess( void ) { } + +}; + +const Real HUGE_DAMAGE_AMOUNT = 999999.0f; + +//------------------------------------------------------------------------------------------------- +/** Damage into outputs */ +//------------------------------------------------------------------------------------------------- +class DamageInfoOutput : public Snapshot +{ + +public: + + DamageInfoOutput( void ) + { + m_actualDamageDealt = 0; + m_actualDamageClipped = 0; + m_noEffect = false; + } + + /** + m_actualDamageDealt is the damage we tried to apply to object (after multipliers and such). + m_actualDamageClipped is the value of m_actualDamageDealt, but clipped to the max health remaining of the obj. + example: + a mammoth tank fires a round at a small tank, attempting 100 damage. + the small tank has a damage multiplier of 50%, meaning that only 50 damage is applied. + furthermore, the small tank has only 30 health remaining. + so: m_actualDamageDealt = 50, m_actualDamageClipped = 30. + + this distinction is useful, since visual fx really wants to do the fx for "50 damage", + even though it was more than necessary to kill this object; game logic, on the other hand, + may want to know the "clipped" damage for ai purposes. + */ + Real m_actualDamageDealt; + Real m_actualDamageClipped; ///< (see comment for m_actualDamageDealt) + Bool m_noEffect; ///< if true, no damage was done at all (generally due to being InactiveBody) + +protected: + + // snapshot methods + virtual void crc( Xfer *xfer ) { } + virtual void xfer( Xfer *xfer ); + virtual void loadPostProcess( void ) { } + +}; + +//------------------------------------------------------------------------------------------------- +/** DamageInfo is a descriptor of damage we're trying to inflict. The structure + * is divided up into two parts, inputs and outputs. + * + * INPUTS: You must provide valid values for these fields in order for damage + * calculation to correctly take place + * OUTPUT: Upon returning from damage issuing functions, the output fields + * will be filled with the results of the damage occurrence + */ +//------------------------------------------------------------------------------------------------- +class DamageInfo : public Snapshot +{ + +public: + + DamageInfoInput in; ///< inputs for the damage info + DamageInfoOutput out; ///< results for the damage occurrence + +protected: + + virtual void crc( Xfer *xfer ) { } + virtual void xfer( Xfer *xfer ); + virtual void loadPostProcess( void ){ } + +}; + +#endif // __DAMAGE_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h index ecfa264ae20..25deafe1168 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h @@ -89,6 +89,11 @@ class ActiveBody : public BodyModule virtual Bool hasAnySubdualDamage() const; virtual Real getCurrentSubdualDamageAmount() const { return m_currentSubdualDamage; } + virtual UnsignedInt getChronoDamageHealRate() const; + virtual Real getChronoDamageHealAmount() const; + virtual Bool hasAnyChronoDamage() const; + virtual Real getCurrentChronoDamageAmount() const { return m_currentChronoDamage; } + virtual const DamageInfo *getLastDamageInfo() const { return &m_lastDamageInfo; } ///< return info on last damage dealt to this object virtual UnsignedInt getLastDamageTimestamp() const { return m_lastDamageTimestamp; } ///< return frame of last damage dealt virtual UnsignedInt getLastHealingTimestamp() const { return m_lastHealingTimestamp; } ///< return frame of last damage dealt @@ -128,6 +133,11 @@ class ActiveBody : public BodyModule virtual Bool canBeSubdued() const; virtual void onSubdualChange( Bool isNowSubdued );///< Override this if you want a totally different effect than DISABLED_SUBDUED + // Chrono + virtual Bool isSubduedChrono() const; + virtual void onSubdualChronoChange(Bool isNowSubdued); ///< Override this if you want a totally different effect than DISABLED_SUBDUED + + virtual void overrideDamageFX(DamageFX* damageFX); protected: @@ -145,6 +155,7 @@ class ActiveBody : public BodyModule Bool shouldRetaliateAgainstAggressor(Object *obj, Object *damager); virtual void internalAddSubdualDamage( Real delta ); ///< change health + virtual void internalAddChronoDamage( Real delta ); ///< change health private: @@ -153,6 +164,7 @@ class ActiveBody : public BodyModule Real m_maxHealth; ///< max health this object can have Real m_initialHealth; ///< starting health for this object Real m_currentSubdualDamage; ///< Starts at zero and goes up. Inherited modules will do something when "subdued". + Real m_currentChronoDamage; ///< Same as Subdual, but for CHRONO_GUN BodyDamageType m_curDamageState; ///< last known damage state UnsignedInt m_nextDamageFXTime; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BodyModule.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BodyModule.h index 4493e55148a..d841fb8e6c0 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BodyModule.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BodyModule.h @@ -154,6 +154,11 @@ class BodyModuleInterface virtual Bool hasAnySubdualDamage() const = 0; virtual Real getCurrentSubdualDamageAmount() const = 0; + virtual UnsignedInt getChronoDamageHealRate() const = 0; + virtual Real getChronoDamageHealAmount() const = 0; + virtual Bool hasAnyChronoDamage() const = 0; + virtual Real getCurrentChronoDamageAmount() const = 0; + virtual BodyDamageType getDamageState() const = 0; virtual void setDamageState( BodyDamageType newState ) = 0; ///< control damage state directly. Will adjust hitpoints. virtual void setAflame( Bool setting ) = 0;///< This is a major change like a damage state. @@ -247,6 +252,11 @@ class BodyModule : public BehaviorModule, public BodyModuleInterface virtual Bool hasAnySubdualDamage() const{return FALSE;} virtual Real getCurrentSubdualDamageAmount() const { return 0.0f; } + virtual UnsignedInt getChronoDamageHealRate() const { return 0; } + virtual Real getChronoDamageHealAmount() const { return 0.0f; } + virtual Bool hasAnyChronoDamage() const { return FALSE; } + virtual Real getCurrentChronoDamageAmount() const { return 0.0f; } + virtual Real getInitialHealth() const {return 0.0f;} // return initial health virtual BodyDamageType getDamageState() const = 0; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ChronoDamageHelper.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ChronoDamageHelper.h new file mode 100644 index 00000000000..7a4476c585c --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ChronoDamageHelper.h @@ -0,0 +1,68 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: ChronoDamageHelper.h //////////////////////////////////////////////////////////////////////// +// Author: Andi W, July 2025 +// Desc: Object helper - Clears chrono disable status and heals chrono damage since Body modules can't have Updates +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __ChronoDamageHelper_H_ +#define __ChronoDamageHelper_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "GameLogic/Module/ObjectHelper.h" + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +class ChronoDamageHelperModuleData : public ModuleData +{ + +}; + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +class ChronoDamageHelper : public ObjectHelper +{ + + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( ChronoDamageHelper, ChronoDamageHelperModuleData ) + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(ChronoDamageHelper, "ChronoDamageHelper" ) + +public: + + ChronoDamageHelper( Thing *thing, const ModuleData *modData ); + // virtual destructor prototype provided by memory pool object + + virtual DisabledMaskType getDisabledTypesToProcess() const { return DISABLEDMASK_ALL; } + virtual UpdateSleepTime update(); + + void notifyChronoDamage( Real amount ); + +protected: + UnsignedInt m_healingStepCountdown; +}; + + +#endif // end __ChronoDamageHelper_H_ diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h index acaa1984030..a22c8dbdfa7 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h @@ -1,126 +1,126 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: RadiusDecalBehavior.h ///////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __RadiusDecalBehavior_H_ -#define __RadiusDecalBehavior_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "GameLogic/Module/BehaviorModule.h" -#include "GameLogic/Module/UpgradeModule.h" -#include "GameLogic/Module/UpdateModule.h" -#include "GameClient/RadiusDecal.h" - -//------------------------------------------------------------------------------------------------- -class RadiusDecalBehaviorModuleData : public UpdateModuleData -{ -public: - UpgradeMuxData m_upgradeMuxData; - Bool m_initiallyActive; - - RadiusDecalTemplate m_decalTemplate; - Real m_decalRadius; - - RadiusDecalBehaviorModuleData(); - - static void buildFieldParse(MultiIniFieldParse& p); -}; - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -class RadiusDecalBehavior : public UpdateModule, public UpgradeMux -{ - - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( RadiusDecalBehavior, "RadiusDecalBehavior" ) - MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( RadiusDecalBehavior, RadiusDecalBehaviorModuleData ) - -public: - - RadiusDecalBehavior( Thing *thing, const ModuleData* moduleData ); - // virtual destructor prototype provided by memory pool declaration - - // module methids - static Int getInterfaceMask() { return UpdateModule::getInterfaceMask() | (MODULEINTERFACE_UPGRADE); } - - // BehaviorModule - virtual UpgradeModuleInterface* getUpgrade() { return this; } - - //void createRadiusDecal( const Coord3D& pos ); - // void createRadiusDecal( const RadiusDecalTemplate& tmpl, Real radius, const Coord3D& pos ); - - void createRadiusDecal( void ); - void killRadiusDecal( void ); - - // UpdateModuleInterface - virtual UpdateSleepTime update(); - - virtual DisabledMaskType getDisabledTypesToProcess() const { return MAKE_DISABLED_MASK(DISABLED_HELD); } - -protected: - - - virtual void upgradeImplementation() - { - createRadiusDecal(); - setWakeFrame(getObject(), UPDATE_SLEEP_NONE); - } - - virtual void getUpgradeActivationMasks(UpgradeMaskType& activation, UpgradeMaskType& conflicting) const - { - getRadiusDecalBehaviorModuleData()->m_upgradeMuxData.getUpgradeActivationMasks(activation, conflicting); - } - - virtual void performUpgradeFX() - { - getRadiusDecalBehaviorModuleData()->m_upgradeMuxData.performUpgradeFX(getObject()); - } - - virtual void processUpgradeRemoval() - { - // I can't take it any more. Let the record show that I think the UpgradeMux multiple inheritence is CRAP. - getRadiusDecalBehaviorModuleData()->m_upgradeMuxData.muxDataProcessUpgradeRemoval(getObject()); - } - - virtual Bool requiresAllActivationUpgrades() const - { - return getRadiusDecalBehaviorModuleData()->m_upgradeMuxData.m_requiresAllTriggers; - } - - inline Bool isUpgradeActive() const { return isAlreadyUpgraded(); } - - virtual Bool isSubObjectsUpgrade() { return false; } - -private: - - RadiusDecal m_radiusDecal; - - void clearDecal( void ); -}; - -#endif // __RadiusDecalBehavior_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: RadiusDecalBehavior.h ///////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __RadiusDecalBehavior_H_ +#define __RadiusDecalBehavior_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "GameLogic/Module/BehaviorModule.h" +#include "GameLogic/Module/UpgradeModule.h" +#include "GameLogic/Module/UpdateModule.h" +#include "GameClient/RadiusDecal.h" + +//------------------------------------------------------------------------------------------------- +class RadiusDecalBehaviorModuleData : public UpdateModuleData +{ +public: + UpgradeMuxData m_upgradeMuxData; + Bool m_initiallyActive; + + RadiusDecalTemplate m_decalTemplate; + Real m_decalRadius; + + RadiusDecalBehaviorModuleData(); + + static void buildFieldParse(MultiIniFieldParse& p); +}; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class RadiusDecalBehavior : public UpdateModule, public UpgradeMux +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( RadiusDecalBehavior, "RadiusDecalBehavior" ) + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( RadiusDecalBehavior, RadiusDecalBehaviorModuleData ) + +public: + + RadiusDecalBehavior( Thing *thing, const ModuleData* moduleData ); + // virtual destructor prototype provided by memory pool declaration + + // module methids + static Int getInterfaceMask() { return UpdateModule::getInterfaceMask() | (MODULEINTERFACE_UPGRADE); } + + // BehaviorModule + virtual UpgradeModuleInterface* getUpgrade() { return this; } + + //void createRadiusDecal( const Coord3D& pos ); + // void createRadiusDecal( const RadiusDecalTemplate& tmpl, Real radius, const Coord3D& pos ); + + void createRadiusDecal( void ); + void killRadiusDecal( void ); + + // UpdateModuleInterface + virtual UpdateSleepTime update(); + + virtual DisabledMaskType getDisabledTypesToProcess() const { return MAKE_DISABLED_MASK(DISABLED_HELD); } + +protected: + + + virtual void upgradeImplementation() + { + createRadiusDecal(); + setWakeFrame(getObject(), UPDATE_SLEEP_NONE); + } + + virtual void getUpgradeActivationMasks(UpgradeMaskType& activation, UpgradeMaskType& conflicting) const + { + getRadiusDecalBehaviorModuleData()->m_upgradeMuxData.getUpgradeActivationMasks(activation, conflicting); + } + + virtual void performUpgradeFX() + { + getRadiusDecalBehaviorModuleData()->m_upgradeMuxData.performUpgradeFX(getObject()); + } + + virtual void processUpgradeRemoval() + { + // I can't take it any more. Let the record show that I think the UpgradeMux multiple inheritence is CRAP. + getRadiusDecalBehaviorModuleData()->m_upgradeMuxData.muxDataProcessUpgradeRemoval(getObject()); + } + + virtual Bool requiresAllActivationUpgrades() const + { + return getRadiusDecalBehaviorModuleData()->m_upgradeMuxData.m_requiresAllTriggers; + } + + inline Bool isUpgradeActive() const { return isAlreadyUpgraded(); } + + virtual Bool isSubObjectsUpgrade() { return false; } + +private: + + RadiusDecal m_radiusDecal; + + void clearDecal( void ); +}; + +#endif // __RadiusDecalBehavior_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UpgradeSpecialPower.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UpgradeSpecialPower.h index 8ec2286d3f2..87b475a60ed 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UpgradeSpecialPower.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UpgradeSpecialPower.h @@ -1,78 +1,78 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: UpgradeSpecialPower.h ///////////////////////////////////////////////////////////////// -// Author: Andreas W, July 25 -// Desc: Special Power will grant an upgrade to the object -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __UPGRADE_SPECIAL_POWER_H_ -#define __UPGRADE_SPECIAL_POWER_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "GameLogic/Module/SpecialPowerModule.h" - -// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// -class FXList; - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -class UpgradeSpecialPowerModuleData : public SpecialPowerModuleData -{ - -public: - - UpgradeSpecialPowerModuleData(void); - - static void buildFieldParse(MultiIniFieldParse& p); - - AsciiString m_upgradeName; ///< name of the upgrade to be granted. - -}; - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -class UpgradeSpecialPower : public SpecialPowerModule -{ - - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(UpgradeSpecialPower, "UpgradeSpecialPower") - MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA(UpgradeSpecialPower, UpgradeSpecialPowerModuleData) - -public: - - UpgradeSpecialPower(Thing* thing, const ModuleData* moduleData); - // virtual destructor prototype provided by memory pool object - - virtual void doSpecialPower(UnsignedInt commandOptions); - - virtual void doSpecialPowerAtObject(Object* obj, UnsignedInt commandOptions); - -protected: - - void grantUpgrade(Object* object); -}; - -#endif +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: UpgradeSpecialPower.h ///////////////////////////////////////////////////////////////// +// Author: Andreas W, July 25 +// Desc: Special Power will grant an upgrade to the object +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __UPGRADE_SPECIAL_POWER_H_ +#define __UPGRADE_SPECIAL_POWER_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "GameLogic/Module/SpecialPowerModule.h" + +// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// +class FXList; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class UpgradeSpecialPowerModuleData : public SpecialPowerModuleData +{ + +public: + + UpgradeSpecialPowerModuleData(void); + + static void buildFieldParse(MultiIniFieldParse& p); + + AsciiString m_upgradeName; ///< name of the upgrade to be granted. + +}; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class UpgradeSpecialPower : public SpecialPowerModule +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(UpgradeSpecialPower, "UpgradeSpecialPower") + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA(UpgradeSpecialPower, UpgradeSpecialPowerModuleData) + +public: + + UpgradeSpecialPower(Thing* thing, const ModuleData* moduleData); + // virtual destructor prototype provided by memory pool object + + virtual void doSpecialPower(UnsignedInt commandOptions); + + virtual void doSpecialPowerAtObject(Object* obj, UnsignedInt commandOptions); + +protected: + + void grantUpgrade(Object* object); +}; + +#endif diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h index 9abdb117015..18156264983 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h @@ -1,834 +1,837 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// Object.h /////////////////////////////////////////////////////////////////// -// Simple base object -// Author: Michael S. Booth, October 2000 - -#pragma once -#ifndef _OBJECT_H_ -#define _OBJECT_H_ - -#include "Lib/BaseType.h" - -#include "Common/Geometry.h" -#include "Common/Snapshot.h" -#include "Common/SpecialPowerMaskType.h" -#include "Common/DisabledTypes.h" -#include "Common/Thing.h" -#include "Common/ObjectStatusTypes.h" -#include "Common/Upgrade.h" - -#include "GameClient/Color.h" - -#include "GameLogic/Damage.h" //for kill() -#include "GameLogic/WeaponBonusConditionFlags.h" -#include "GameLogic/WeaponSet.h" -#include "GameLogic/WeaponSetFlags.h" -#include "GameLogic/Module/StealthUpdate.h" - -//----------------------------------------------------------------------------- -// Forward References -//----------------------------------------------------------------------------- - -class AIGroup; -class AIUpdateInterface; -class Anim2DTemplate; -class BehaviorModule; -class BehaviorModuleInterface; -class BodyModuleInterface; -class CollideModule; -class CollideModuleInterface; -class CommandButton; -class ContainModuleInterface; -class CountermeasuresBehaviorInterface; -class CreateModuleInterface; -class DamageInfo; -class DamageInfoInput; -class DamageModule; -class DamageModuleInterface; -class DestroyModuleInterface; -class DockUpdateInterface; -class Dict; -class DieModule; -class DieModuleInterface; -class ExitInterface; -class ExperienceTracker; -class FiringTracker; -class Module; -class PartitionData; -class PhysicsBehavior; -class PhysicsUpdate; -class Player; -class PolygonTrigger; -class ProductionUpdateInterface; -class ProjectileUpdateInterface; -class RadarObject; -class SightingInfo; -class SpawnBehaviorInterface; -class SpecialAbilityUpdate; -class SpecialPowerCompletionDie; -class SpecialPowerModuleInterface; -class SpecialPowerTemplate; -class SpecialPowerUpdateInterface; -class Team; -class UpdateModule; -class UpdateModuleInterface; -class UpgradeModule; -class UpgradeModuleInterface; -class UpgradeTemplate; - -class ObjectHeldHelper; -class ObjectDisabledHelper; -class ObjectSMCHelper; -class ObjectRepulsorHelper; -class StatusDamageHelper; -class SubdualDamageHelper; -class TempWeaponBonusHelper; -class ObjectWeaponStatusHelper; -class ObjectDefectionHelper; - -enum CommandSourceType CPP_11(: Int); -enum HackerAttackMode CPP_11(: Int); -enum NameKeyType CPP_11(: Int); -enum SpecialPowerType CPP_11(: Int); -enum WeaponBonusConditionType CPP_11(: Int); -enum WeaponChoiceCriteria CPP_11(: Int); -enum WeaponSetConditionType CPP_11(: Int); -enum WeaponSetType CPP_11(: Int); -enum ArmorSetType CPP_11(: Int); -enum WeaponStatus CPP_11(: Int); -enum RadarPriorityType CPP_11(: Int); -enum CanAttackResult CPP_11(: Int); -// enum TintStatus CPP_11(: Int); - -// For ObjectStatusTypes -#include "Common/ObjectStatusTypes.h" - -// For ObjectScriptStatusBit -#include "GameLogic/ObjectScriptStatusBits.h" - -// For TintStatus -#include "GameClient/TintStatus.h" - -//----------------------------------------------------------------------------- -// Type Defines -//----------------------------------------------------------------------------- - -struct TTriggerInfo -{ - const PolygonTrigger* pTrigger; ///< The trigger area that the object is inside. - Byte entered; ///< True if the object entered this trigger area this frame. - Byte exited; ///< True if the object entered this trigger area this frame. - Byte isInside; ///< True if the object is inside this trigger area this frame. - Byte padding; ///< unused. - - TTriggerInfo() : entered(false), exited(false), isInside(false), padding(false), pTrigger(NULL) { } - -}; - -//---------------------------------------------------- - - -enum CrushSquishTestType CPP_11(: Int) -{ - TEST_CRUSH_ONLY, - TEST_SQUISH_ONLY, - TEST_CRUSH_OR_SQUISH -}; - - -// --------------------------------------------------- -/** - * Object definition. Objects are manipulated via TheGameLogic singleton. - * @todo Create an ObjectInterface class. - */ -class Object : public Thing, public Snapshot -{ - - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(Object, "ObjectPool" ) - /// destructor is non-public in order to require the use of TheGameLogic->destroyObject() - MEMORY_POOL_DELETEINSTANCE_VISIBILITY(protected) - -public: - - /// Object constructor automatically attaches all objects to "TheGameLogic" - Object(const ThingTemplate *thing, const ObjectStatusMaskType &objectStatusMask, Team *team); - - void initObject(); - - void onDestroy(); ///< run during TheGameLogic::destroyObject - - Object* getNextObject() { return m_next; } - const Object* getNextObject() const { return m_next; } - - void updateObjValuesFromMapProperties(Dict* properties); ///< Brings in properties set in the editor. - - // ids and binding - ObjectID getID() const { return m_id; } ///< this object's unique ID - void friend_bindToDrawable( Drawable *draw ); ///< set drawable association. for use ONLY by GameLogic! - Drawable* getDrawable() const { return m_drawable; } ///< drawable (if any) bound to obj - - ObjectID getProducerID() const { return m_producerID; } - void setProducer(const Object* obj); - - ObjectID getBuilderID() const { return m_builderID; } - void setBuilder( const Object *obj ); - - void enterGroup( AIGroup *group ); ///< become a member of the AIGroup - void leaveGroup( void ); ///< leave our current AIGroup - AIGroup *getGroup(void); - - // physical properties - Bool isMobile() const; ///< returns true if object is currently able to move - Bool isAbleToAttack() const; ///< returns true if object currently has some kind of attack capability - - void maskObject( Bool mask ); ///< mask/unmask object - - /** - Booby traps are set off by many random actions, so those actions are responsible for calling this. - Return value is if a booby trap was set off, so caller can react. - Those actions are: planting any type of bomb, entering, starting to capture, dying. - */ - Bool checkAndDetonateBoobyTrap(const Object *victim); - - // cannot set velocity, since this is calculated from position every frame - Bool isDestroyed() const { return m_status.test( OBJECT_STATUS_DESTROYED ); } ///< Returns TRUE if object has been destroyed - Bool isAirborneTarget() const { return m_status.test( OBJECT_STATUS_AIRBORNE_TARGET ); } ///< Our locomotor will control marking us as a valid target for anti air weapons or not - Bool isUsingAirborneLocomotor( void ) const; ///< returns true if the current locomotor is an airborne one - - /// central place for us to put any additional capture logic - void onCapture( Player *oldOwner, Player *newOwner ); - - /// And game death logic. Destroy is deletion of object as code - void onDie( DamageInfo *damageInfo ); - - // health and damage - void attemptDamage( DamageInfo *damageInfo ); ///< damage object as specified by the info - void attemptHealing(Real amount, const Object* source); ///< heal object as specified by the info - Bool attemptHealingFromSoleBenefactor ( Real amount, const Object* source, UnsignedInt duration );///< for the non-stacking healers like ambulance and propaganda - ObjectID getSoleHealingBenefactor( void ) const; - - Real estimateDamage( DamageInfoInput& damageInfo ) const; - void kill( DamageType damageType = DAMAGE_UNRESISTABLE, DeathType deathType = DEATH_NORMAL ); ///< kill the object with an optional type of damage and death. - void healCompletely(); ///< Restore max health to this Object - void notifySubdualDamage( Real amount );///< At this level, we just pass this on to our helper and do a special tint - void doStatusDamage( ObjectStatusTypes status, Real duration );///< At this level, we just pass this on to our helper - void doTempWeaponBonus( WeaponBonusConditionType status, UnsignedInt duration, TintStatus tintStatus = TINT_STATUS_INVALID );///< At this level, we just pass this on to our helper - - void scoreTheKill( const Object *victim ); ///< I just killed this object. - void onVeterancyLevelChanged( VeterancyLevel oldLevel, VeterancyLevel newLevel, Bool provideFeedback = TRUE ); ///< I just achieved this level right this moment - ExperienceTracker* getExperienceTracker() {return m_experienceTracker;} - const ExperienceTracker* getExperienceTracker() const {return m_experienceTracker;} - VeterancyLevel getVeterancyLevel() const; - - inline const AsciiString& getName() const { return m_name; } - inline void setName( const AsciiString& newName ) { m_name = newName; } - - inline Team* getTeam() { return m_team; } - inline const Team *getTeam() const { return m_team; } - - void restoreOriginalTeam(); - - void setTeam( Team* team ); ///< sets the unit's team AND original team - void setTemporaryTeam( Team* team ); ///< sets the unit's team BUT NOT its original team - - Player* getControllingPlayer() const; - Relationship getRelationship(const Object *that) const; - - Color getIndicatorColor() const; - Color getNightIndicatorColor() const; - Bool hasCustomIndicatorColor() const { return m_indicatorColor != 0; } - void setCustomIndicatorColor(Color c); - void removeCustomIndicatorColor(); - - Bool isLocallyControlled() const; - Bool isNeutralControlled() const; - - Bool getIsUndetectedDefector(void) const { return BitIsSet(m_privateStatus, UNDETECTED_DEFECTOR); } - void friend_setUndetectedDefector(Bool status); - - inline Bool isOffMap() const { return BitIsSet(m_privateStatus, OFF_MAP); } - - inline Bool isCaptured() const { return BitIsSet(m_privateStatus, CAPTURED); } - void setCaptured(Bool isCaptured); - - inline const GeometryInfo& getGeometryInfo() const { return m_geometryInfo; } - void setGeometryInfo(const GeometryInfo& geom); - void setGeometryInfoZ( Real newZ ); - - void onCollide( Object *other, const Coord3D *loc, const Coord3D *normal ); - - Real getCarrierDeckHeight() const; - // access to modules - //----------------------------------------------------------------------------- - - //This is a good creation inspector. There's been multitudes of issues with conflicts of - //Objects getting constructed causing crashes either because the modules aren't created - //yet, and there's stuff being done inside of setTeam() that cares. - Bool areModulesReady() const { return m_modulesReady; } - - BehaviorModule** getBehaviorModules() const { return m_behaviors; } - - BodyModuleInterface* getBodyModule() const { return m_body; } - ContainModuleInterface* getContain() const { return m_contain; } - StealthUpdate* getStealth() const { return m_stealth; } - SpawnBehaviorInterface* getSpawnBehaviorInterface() const; - ProjectileUpdateInterface* getProjectileUpdateInterface() const; - - - // special case for the AIUpdateInterface, since it will be referred to a great deal - inline AIUpdateInterface *getAIUpdateInterface() { return m_ai; } - inline const AIUpdateInterface* getAIUpdateInterface() const { return m_ai; } - - inline AIUpdateInterface *getAI() { return m_ai; } - inline const AIUpdateInterface* getAI() const { return m_ai; } - - inline PhysicsBehavior* getPhysics() { return m_physics; } - inline const PhysicsBehavior* getPhysics() const { return m_physics; } - void topple( const Coord3D *toppleDirection, Real toppleSpeed, UnsignedInt options ); - - UpdateModule* findUpdateModule(NameKeyType key) const { return (UpdateModule*)findModule(key); } - DamageModule* findDamageModule(NameKeyType key) const { return (DamageModule*)findModule(key); } - - Bool isSalvageCrate() const; - - // - // Find us our production update interface if we have one. This method exists simply - // because we do this in a lot of places in the code and I want a convenient way to get thsi (CBD) - // - ProductionUpdateInterface* getProductionUpdateInterface( void ); - - // - // Find us our dock update interface if we have one. Again, this method exists simple - // because we want to do this in a lot of places throughout the code - // - DockUpdateInterface *getDockUpdateInterface( void ); - - // Ditto for special powers -- Kris - SpecialPowerModuleInterface* findSpecialPowerModuleInterface( SpecialPowerType type ) const; - SpecialPowerModuleInterface* findAnyShortcutSpecialPowerModuleInterface() const; - SpecialAbilityUpdate* findSpecialAbilityUpdate( SpecialPowerType type ) const; - SpecialPowerCompletionDie* findSpecialPowerCompletionDie() const; - SpecialPowerUpdateInterface* findSpecialPowerWithOverridableDestinationActive( SpecialPowerType type = SPECIAL_INVALID ) const; - SpecialPowerUpdateInterface* findSpecialPowerWithOverridableDestination( SpecialPowerType type = SPECIAL_INVALID ) const; - - CountermeasuresBehaviorInterface* getCountermeasuresBehaviorInterface(); - const CountermeasuresBehaviorInterface* getCountermeasuresBehaviorInterface() const; - - inline ObjectStatusMaskType getStatusBits() const { return m_status; } - inline Bool testStatus( ObjectStatusTypes bit ) const { return m_status.test( bit ); } - void setStatus( ObjectStatusMaskType objectStatus, Bool set = true ); - inline void clearStatus( ObjectStatusMaskType objectStatus ) { setStatus( objectStatus, false ); } - void updateUpgradeModules(); ///< We need to go through our Upgrade Modules and see which should be activated - UpgradeMaskType getObjectCompletedUpgradeMask() const { return m_objectUpgradesCompleted; } ///< Upgrades I complete locally - - //This function sucks. - //It was added for objects that can disguise as other objects and contain upgraded subobject overrides. - //A concrete example is the bomb truck. Different payloads are displayed based on which upgrades have been - //made. When the bomb truck disguises as something else, these subobjects are lost because the vector is - //stored in W3DDrawModule. When we revert back to the original bomb truck, we call this function to - //recalculate those upgraded subobjects. - void forceRefreshSubObjectUpgradeStatus(); - - // Useful for status bits that can be set by the scripting system - inline Bool testScriptStatusBit(ObjectScriptStatusBit b) const { return BitIsSet(m_scriptStatus, b); } - void setScriptStatus( ObjectScriptStatusBit bit, Bool set = true ); - inline void clearScriptStatus( ObjectScriptStatusBit bit ) { setScriptStatus(bit, false); } - - // Selectable is individually controlled on an object by object basis for design now. - // It defaults to the thingTemplate->isKindof(KINDOF_SELECTABLE), however, it can be overridden on an - // object by object basis. Finally, it can be temporarily overriden by the OBJECT_STATUS_UNSELECTABLE. - // jba. - void setSelectable(Bool selectable); - Bool isSelectable() const; - - Bool isMassSelectable() const; - - // User specified formation. - void setFormationID(enum FormationID id) {m_formationID = id;} - enum FormationID getFormationID(void) const {return m_formationID;} - void setFormationOffset(const Coord2D& offset) {m_formationOffset = offset;} - void getFormationOffset(Coord2D* offset) const {*offset = m_formationOffset;} - - -//THIS FUNCTION BELONGS AT THE OBJECT LEVEL BECAUSE THERE IS AT LEAST ONE SPECIAL UNIT -//(ANGRY MOB) WHICH NEEDS LOGIC-SIDE POSITION CALC'S... -//IT WOULD PROBABLY BE WISE TO MOVE ALL THE HARD-CODED DEFAULTS BELOW -//INTO A new Drawable::getHealthBox..() WHICH USES GEOM0INFO, MODEL DATA, INI DATA, ETC. - void getHealthBoxPosition(Coord3D& pos) const; - Bool getHealthBoxDimensions(Real &healthBoxHeight, Real &healthBoxWidth) const; - - inline Bool isEffectivelyDead() const { return (m_privateStatus & EFFECTIVELY_DEAD) != 0; } - void setEffectivelyDead(Bool dead); - - void markSingleUseCommandUsed() { m_singleUseCommandUsed = true; } - Bool hasSingleUseCommandBeenUsed() const { return m_singleUseCommandUsed; } - - /// returns true iff the object can run over the other object. - Bool canCrushOrSquish(Object *otherObj, CrushSquishTestType testType = TEST_CRUSH_OR_SQUISH) const; - UnsignedByte getCrusherLevel() const; - UnsignedByte getCrushableLevel() const; - - Bool hasUpgrade( const UpgradeTemplate *upgradeT ) const ; ///< does this object already have this upgrade - Bool affectedByUpgrade( const UpgradeTemplate *upgradeT ) const ; ///< can the object even "have" this upgrade, will it do something? - void giveUpgrade( const UpgradeTemplate *upgradeT ); ///< give upgrade to this object - void removeUpgrade( const UpgradeTemplate *upgradeT ); ///< remove upgrade from this object - - Bool hasCountermeasures() const; - void reportMissileForCountermeasures( Object *missile ); - ObjectID calculateCountermeasureToDivertTo( const Object& victim ); - - void calcNaturalRallyPoint(Coord2D *pt); ///< calc the "natural" starting rally point - void setConstructionPercent( Real percent ) { m_constructionPercent = percent; } - Real getConstructionPercent() const { return m_constructionPercent; } - - void setLayer( PathfindLayerEnum layer ); - PathfindLayerEnum getLayer() const { return m_layer; } - - void setDestinationLayer( PathfindLayerEnum layer ); - PathfindLayerEnum getDestinationLayer() const { return m_destinationLayer; } - - void prependToList(Object **pListHead); - void removeFromList(Object **pListHead); - Bool isInList(Object **pListHead) const; - - // this is intended for use ONLY by GameLogic. - void friend_deleteInstance() { deleteInstance(); } - - /// cache the partition module (should be called only by PartitionData) - void friend_setPartitionData(PartitionData *pd) { m_partitionData = pd; } - PartitionData *friend_getPartitionData() const { return m_partitionData; } - const PartitionData *friend_getConstPartitionData() const { return m_partitionData; } - - void onPartitionCellChange();///< We have moved a 'significant' amount, so do maintenence that can be considered 'cell-based' - void handlePartitionCellMaintenance(); ///< Undo and redo all shroud actions. Call when something has changed, like position or ownership or Death - - Real getVisionRange() const; ///< How far can you see? This is dynamic so it is in Object. - void setVisionRange( Real newVisionRange ); ///< Access to setting someone's Vision distance - Real getShroudRange() const; ///< How far can you shroud? Even more dynamic since it'll start at zero for everyone. - void setShroudRange( Real newShroudRange ); ///< Access to setting someone's shrouding distance - Real getShroudClearingRange() const; ///< How far do you clear shroud? - void setShroudClearingRange( Real newShroudClearingRange ); ///< Access to setting someone's clear shroud distance - void setVisionSpied(Bool setting, Int byWhom);///< Change who is looking through our eyes - - // Both of these calls are intended to only be used by TerrainLogic, specifically setActiveBoundary() - void friend_prepareForMapBoundaryAdjust(void); - void friend_notifyOfNewMapBoundary(void); - - // data for the radar - void friend_setRadarData( RadarObject *rd ) { m_radarData = rd; } - RadarObject *friend_getRadarData() { return m_radarData; } - RadarPriorityType getRadarPriority() const; - - // contained-by - inline Object *getContainedBy() { return m_containedBy; } - inline const Object *getContainedBy() const { return m_containedBy; } - inline UnsignedInt getContainedByFrame() const { return m_containedByFrame; } - inline Bool isContained() const { return m_containedBy != NULL; } - void onContainedBy( Object *containedBy ); - void onRemovedFrom( Object *removedFrom ); - Int getTransportSlotCount() const; - void friend_setContainedBy( Object *containedBy ) { m_containedBy = containedBy; } - - // Special Powers ------------------------------------------------------------------------------- - SpecialPowerModuleInterface *getSpecialPowerModule( const SpecialPowerTemplate *specialPowerTemplate ) const; - void doSpecialPower( const SpecialPowerTemplate *specialPowerTemplate, UnsignedInt commandOptions, Bool forced = false ); ///< execute power - void doSpecialPowerAtObject( const SpecialPowerTemplate *specialPowerTemplate, Object *obj, UnsignedInt commandOptions, Bool forced = false ); ///< execute power - void doSpecialPowerAtLocation( const SpecialPowerTemplate *specialPowerTemplate, const Coord3D *loc, Real angle, UnsignedInt commandOptions, Bool forced = false ); ///< execute power - void doSpecialPowerUsingWaypoints( const SpecialPowerTemplate *specialPowerTemplate, const Waypoint *way, UnsignedInt commandOptions, Bool forced = false ); ///< execute power - - void doCommandButton( const CommandButton *commandButton, CommandSourceType cmdSource ); - void doCommandButtonAtObject( const CommandButton *commandButton, Object *obj, CommandSourceType cmdSource ); - void doCommandButtonAtPosition( const CommandButton *commandButton, const Coord3D *pos, CommandSourceType cmdSource ); - void doCommandButtonUsingWaypoints( const CommandButton *commandButton, const Waypoint *way, CommandSourceType cmdSource ); - - /** - For Object specific dynamic command sets. Different from the Science specific ones handled in ThingTemplate - */ - const AsciiString& getCommandSetString() const; - void setCommandSetStringOverride( AsciiString newCommandSetString ) { m_commandSetStringOverride = newCommandSetString; } - - /// People are faking their commandsets, and, Surprise!, they are authoritative. Challenge everything. - Bool canProduceUpgrade( const UpgradeTemplate *upgrade ); - - - // Weapons & Damage ------------------------------------------------------------------------------------------------- - void reloadAllAmmo(Bool now); - Bool isOutOfAmmo() const; - Bool hasAnyWeapon() const; - Bool hasAnyDamageWeapon() const; //Kris: a should be used for real weapons that directly inflict damage... not deploy, hack, etc. - Bool hasWeaponToDealDamageType(DamageType typeToDeal) const; - Real getLargestWeaponRange() const; - UnsignedInt getMostPercentReadyToFireAnyWeapon() const; - - Weapon* getWeaponInWeaponSlot(WeaponSlotType wslot) const { return m_weaponSet.getWeaponInWeaponSlot(wslot); } - UnsignedInt getWeaponInWeaponSlotCommandSourceMask( WeaponSlotType wSlot ) const { return m_weaponSet.getNthCommandSourceMask( wSlot ); } - Bool getWeaponInWeaponSlotSyncedToSlot(WeaponSlotType thisSlot, WeaponSlotType otherSlot) const; - - // see if this current weapon set's weapons has shared reload times - const Bool isReloadTimeShared() const { return m_weaponSet.isSharedReloadTime(); } - - Weapon* getCurrentWeapon(WeaponSlotType* wslot = NULL); - const Weapon* getCurrentWeapon(WeaponSlotType* wslot = NULL) const; - void setFiringConditionForCurrentWeapon() const; - void adjustModelConditionForWeaponStatus(); ///< Check to see if I should change my model condition. - void fireCurrentWeapon(Object *target); - void fireCurrentWeapon(const Coord3D* pos); - void preFireCurrentWeapon( const Object *victim ); - void preFireCurrentWeapon(const Coord3D* pos); - UnsignedInt getLastShotFiredFrame() const; ///< Get the frame a shot was last fired on - ObjectID getLastVictimID() const; ///< Get the last victim we shot at - Weapon* findWaypointFollowingCapableWeapon(); - Bool getAmmoPipShowingInfo(Int& numTotal, Int& numFull) const; - - void notifyFiringTrackerShotFired( const Weapon* weaponFired, ObjectID victimID ) ; - - /** - Determines if the unit has any weapon that could conceivably - harm the victim. this does not take range, ammo, etc. into - account, but immutable weapon properties, such as "can you - target airborne victims". - */ - /* - NOTE: getAbleToAttackSpecificObject NO LONGER internally calls isAbleToAttack(), - since that isn't an incredibly fast call, and this is called repeatedly in some inner loops - where we already know that isAbleToAttack() == true. so you should always - call isAbleToAttack prior to calling this! (srj) - */ - CanAttackResult getAbleToAttackSpecificObject( AbleToAttackType t, const Object* target, CommandSourceType commandSource, WeaponSlotType specificSlot = (WeaponSlotType)-1 ) const; - - //Used for base defenses and otherwise stationary units to see if you can attack a position potentially out of range. - CanAttackResult getAbleToUseWeaponAgainstTarget( AbleToAttackType attackType, const Object *victim, const Coord3D *pos, CommandSourceType commandSource, WeaponSlotType specificSlot = (WeaponSlotType)-1 ) const; - - /** - Selects the best weapon for the given target, and sets it as the current weapon. - If there is no weapon that can damage the target, false is returned (and the current-weapon is unchanged). - Note that this DOES take weapon attack range into account. - */ - Bool chooseBestWeaponForTarget(const Object* target, WeaponChoiceCriteria criteria, CommandSourceType cmdSource); - - // set and/or clear a single modelcondition flag - void setModelConditionState( ModelConditionFlagType a ); - void clearModelConditionState( ModelConditionFlagType a ); - void clearAndSetModelConditionState( ModelConditionFlagType clr, ModelConditionFlagType set ); - - //Special model states are states that are turned on for a period of time, and turned off - //automatically -- used for cheer, and scripted special moment animations. Setting a special - //state will automatically clear any other special states that may be turned on so you can only - //have one at a time. - void setSpecialModelConditionState( ModelConditionFlagType set, UnsignedInt frames = 0 ); - void clearSpecialModelConditionStates(); - - // set and/or clear multiple modelcondition flags - void clearModelConditionFlags( const ModelConditionFlags& clr ); - void setModelConditionFlags( const ModelConditionFlags& set ); - void clearAndSetModelConditionFlags( const ModelConditionFlags& clr, const ModelConditionFlags& set ); - - void setWeaponSetFlag(WeaponSetType wst); - void clearWeaponSetFlag(WeaponSetType wst); - inline Bool testWeaponSetFlag(WeaponSetType wst) const { return m_curWeaponSetFlags.test(wst); } - inline const WeaponSetFlags& getWeaponSetFlags() const { return m_curWeaponSetFlags; } - Bool setWeaponLock( WeaponSlotType weaponSlot, WeaponLockType lockType ){ return m_weaponSet.setWeaponLock( weaponSlot, lockType ); } - void releaseWeaponLock(WeaponLockType lockType){ m_weaponSet.releaseWeaponLock(lockType); } - Bool isCurWeaponLocked() const { return m_weaponSet.isCurWeaponLocked(); } - - void setArmorSetFlag(ArmorSetType ast); - void clearArmorSetFlag(ArmorSetType ast); - Bool testArmorSetFlag(ArmorSetType ast) const; - - /// return true if the template has the specified special power flag set - // @todo: inline - Bool hasSpecialPower( SpecialPowerType type ) const; - Bool hasAnySpecialPower() const; - - void setWeaponBonusCondition(WeaponBonusConditionType wst); - void clearWeaponBonusCondition(WeaponBonusConditionType wst); - - // note, the !=0 at the end is important, to convert this into a boolean type! (srj) - Bool testWeaponBonusCondition(WeaponBonusConditionType wst) const { return (m_weaponBonusCondition & (1 << wst)) != 0; } - inline WeaponBonusConditionFlags getWeaponBonusCondition() const { return m_weaponBonusCondition; } - inline void setWeaponBonusConditionFlags(WeaponBonusConditionFlags flags) { m_weaponBonusCondition = flags; } - - Bool getSingleLogicalBonePosition(const char* boneName, Coord3D* position, Matrix3D* transform) const; - Bool getSingleLogicalBonePositionOnTurret(WhichTurretType whichTurret, const char* boneName, Coord3D* position, Matrix3D* transform) const; - Int getMultiLogicalBonePosition(const char* boneNamePrefix, Int maxBones, Coord3D* positions, Matrix3D* transforms, Bool convertToWorld = TRUE ) const; - - // Entered & exited. - Bool didEnter(const PolygonTrigger *pTrigger) const; - Bool didExit(const PolygonTrigger *pTrigger) const; - Bool isInside(const PolygonTrigger *pTrigger) const; - - // exiting of any kind - ExitInterface *getObjectExitInterface() const; ///< get exit interface is present - Bool hasExitInterface() const { return getObjectExitInterface() != 0; } - - ObjectShroudStatus getShroudedStatus(Int playerIndex) const; - - DisabledMaskType getDisabledFlags() const { return m_disabledMask; } - Bool isDisabled() const { return m_disabledMask.any(); } - Bool clearDisabled( DisabledType type ); - - void setDisabled( DisabledType type ); - void setDisabledUntil( DisabledType type, UnsignedInt frame ); - Bool isDisabledByType( DisabledType type ) const { return TEST_DISABLEDMASK( m_disabledMask, type ); } - - UnsignedInt getDisabledUntil( DisabledType type = DISABLED_ANY ) const; - - void pauseAllSpecialPowers( const Bool disabling ) const; - - //Checks any timers and clears disabled statii that have expired. - void checkDisabledStatus(); - - //When an AIAttackState is over, it needs to clean up any weapons that might be in leech range mode - //or else those weapons will have unlimited range! - void clearLeechRangeModeForAllWeapons(); - - Int getNumConsecutiveShotsFiredAtTarget( const Object *victim) const; - - void setHealthBoxOffset( const Coord3D& offset ) { m_healthBoxOffset = offset; } ///< for special amorphous like angry mob - - void defect( Team *newTeam, UnsignedInt detectionTime ); - void goInvulnerable( UnsignedInt time ); - - // This is public, since there is no Thing level master setting of Turret stuff. It is all done in a sleepy hamlet - // of a module called TurretAI. - virtual void reactToTurretChange( WhichTurretType turret, Real oldRotation, Real oldPitch ); - - // Convenience function for checking certain kindof bits - Bool isStructure(void) const; - - // Convenience function for checking certain kindof bits - Bool isFactionStructure(void) const; - - // Convenience function for checking certain kindof bits - Bool isNonFactionStructure(void) const; - - Bool isHero(void) const; - - Bool getReceivingDifficultyBonus() const { return m_isReceivingDifficultyBonus; } - void setReceivingDifficultyBonus(Bool receive); - - inline UnsignedInt getSafeOcclusionFrame(void) { return m_safeOcclusionFrame; } //< this is an object specific frame at which it's safe to enable building occlusion. - inline void setSafeOcclusionFrame(UnsignedInt frame) { m_safeOcclusionFrame = frame;} - - // All of our cheating for radars and power go here. - // This is the function that we now call in becomingTeamMember to adjust our power. - // If incoming is true, we're working on the incoming player, if its false, we're on the outgoing - // player. These are friend_s for player. - void friend_adjustPowerForPlayer( Bool incoming ); - -protected: - - void setOrRestoreTeam( Team* team, Bool restoring ); - - void onDisabledEdge(Bool becomingDisabled); - // All of our cheating for radars and power go here. - - - // snapshot methods - void crc( Xfer *xfer ); - void xfer( Xfer *xfer ); - void loadPostProcess(); - - void handleShroud(); - void handleValueMap(); - void handleThreatMap(); - - // NOTE NOTE NOTE -- this is a private method. Do Not Ever Make It Public. - // If you think you need to make it public, you are wrong. Don't do it. - // It will go away someday. Yeah, right. Just like GlobalData. - Module* findModule(NameKeyType key) const; - - Bool didEnterOrExit() const; - - void setID( ObjectID id ); - virtual Object *asObjectMeth() { return this; } - virtual const Object *asObjectMeth() const { return this; } - - virtual Real calculateHeightAboveTerrain(void) const; // Calculates the actual height above terrain. Doesn't use cache. - - void updateTriggerAreaFlags(void); - void setTriggerAreaFlagsForChangeInPosition(void); - - /// Look and unlook are protected. They should be called from Object::reasonToLook. Like Capture, or death. - void look(); - void unlook(); - void shroud(); - void unshroud(); - - /// value and threat functions are protected, and should only be called from handleValueMap - void addValue(); - void removeValue(); - - void addThreat(); - void removeThreat(); - - virtual void reactToTransformChange(const Matrix3D* oldMtx, const Coord3D* oldPos, Real oldAngle); - -private: - - // yes, private. No, really. Private. Don't expose. - enum ObjectPrivateStatusBits - { - EFFECTIVELY_DEAD = (1 << 0), ///< Object is effectively dead - UNDETECTED_DEFECTOR = (1 << 1), ///< set to true when I defect from my team; set to false when I attack anything or when time runs out - CAPTURED = (1 << 2), ///< set to true if I've been captured, otherwise, its false. (Note: Never becomes false once it's true) - OFF_MAP = (1 << 3) ///< set to true if I am known to be OFF the current map. - // NOTE: Object currently only uses a Byte for this, so if you add status bits, you may need to enlarge that field. - }; - - ObjectID m_id; ///< this object's unique ID - ObjectID m_producerID; ///< object that produced us, if any - ObjectID m_builderID; ///< object that is building or has built us (dozers or workers are builders) - Drawable* m_drawable; ///< drawable (if any) for this object - AsciiString m_name; ///< internal name - - Object * m_next; - Object * m_prev; - ObjectStatusMaskType m_status; ///< status bits (see ObjectStatusMaskType) - - GeometryInfo m_geometryInfo; - - AIGroup* m_group; ///< if non-NULL, we are part of this group of agents - - // These will last for my lifetime. I will reuse them and reset them. The truly dynamic ones are in PartitionManager - SightingInfo *m_partitionLastLook; ///< Where and for whom I last looked, so I can undo its effects when I stop - SightingInfo *m_partitionRevealAllLastLook; ///< And a seperate look to reveal at a different range if so marked - Int m_visionSpiedBy[MAX_PLAYER_COUNT]; ///< Reference count of having units spied on by players. - PlayerMaskType m_visionSpiedMask; ///< For quick lookup and edge triggered maintenance - - SightingInfo *m_partitionLastShroud; ///< Where and for whom I last shrouded, so I can undo its effects when I stop - SightingInfo *m_partitionLastThreat; ///< Where and for whom I last delt with threat, so I can undo its effects when I stop - SightingInfo *m_partitionLastValue; ///< Where and for whom I last delt with value, so I can undo its effects when I stop - - Real m_visionRange; ///< looking range - Real m_shroudClearingRange; ///< looking range for shroud ONLY - Real m_shroudRange; ///< like looking range, this is how far I shroud others' looks - - DisabledMaskType m_disabledMask; - UnsignedInt m_disabledTillFrame[ DISABLED_COUNT ]; - - UnsignedInt m_smcUntil; - - enum { NUM_SLEEP_HELPERS = 8 }; - ObjectRepulsorHelper* m_repulsorHelper; - ObjectSMCHelper* m_smcHelper; - ObjectWeaponStatusHelper* m_wsHelper; - ObjectDefectionHelper* m_defectionHelper; - StatusDamageHelper* m_statusDamageHelper; - SubdualDamageHelper* m_subdualDamageHelper; - TempWeaponBonusHelper* m_tempWeaponBonusHelper; - FiringTracker* m_firingTracker; ///< Tracker is really a "helper" and is included NUM_SLEEP_HELPERS - - // modules - BehaviorModule** m_behaviors; // BehaviorModule, not BehaviorModuleInterface - - // cache these, for convenience - ContainModuleInterface* m_contain; - BodyModuleInterface* m_body; - StealthUpdate* m_stealth; - - AIUpdateInterface* m_ai; ///< ai interface (if any), cached for handy access. (duplicate of entry in the module array!) - PhysicsBehavior* m_physics; ///< physics interface (if any), cached for handy access. (duplicate of entry in the module array!) - - PartitionData* m_partitionData; ///< our PartitionData - RadarObject* m_radarData; ///< radar data - ExperienceTracker* m_experienceTracker; ///< Manages experience, gaining levels, and value when killed - - Object* m_containedBy; /**< an object can only be contained by at most one - other object, this is that object (if present) */ - ObjectID m_xferContainedByID; ///< xfer uses IDs to store pointers and looks them up after - UnsignedInt m_containedByFrame; ///< frame we were contained by m_containedBy - - Real m_constructionPercent; ///< for objects being built ... this is the amount completed (0.0 to 100.0) - UpgradeMaskType m_objectUpgradesCompleted; ///< Bit field of upgrades locally completed. - - Team* m_team; ///< team that is current owner of this guy - AsciiString m_originalTeamName; ///< team that was the original ("birth") team of this guy - Color m_indicatorColor; ///< if nonzero, use this instead of controlling player's color - - Coord3D m_healthBoxOffset; ///< generally zero, except for special amorphous ones like angry mob - - /// @todo srj -- convert to non-DLINK list, after it is once again possible to test the change - MAKE_DLINK(Object, TeamMemberList) ///< other Things that are members of the same team - - // Weapons & Damage ------------------------------------------------------------------------------------------------- - WeaponSet m_weaponSet; - WeaponSetFlags m_curWeaponSetFlags; - WeaponBonusConditionFlags m_weaponBonusCondition; - Byte m_lastWeaponCondition[WEAPONSLOT_COUNT]; - - SpecialPowerMaskType m_specialPowerBits; ///< bits determining what kind of special abilities this object has access to. - - //////////////////////////////////////< for the non-stacking healers like ambulance and propaganda - ObjectID m_soleHealingBenefactorID; ///< who is the only other object that can give me this non-stacking heal benefit? - UnsignedInt m_soleHealingBenefactorExpirationFrame; ///< on what frame can I accept healing (thus to switch) from a new benefactor - /////////////////////////////////// - - // Entered & exited housekeeping. - enum { MAX_TRIGGER_AREA_INFOS = 5 }; - TTriggerInfo m_triggerInfo[MAX_TRIGGER_AREA_INFOS]; - UnsignedInt m_enteredOrExitedFrame; - ICoord3D m_iPos; - - PathfindLayerEnum m_layer; // Layer object is pathing on. - PathfindLayerEnum m_destinationLayer; // Layer of current path goal. - - // User formations. - FormationID m_formationID; - Coord2D m_formationOffset; - - AsciiString m_commandSetStringOverride;///< To allow specific object to switch command sets - - UnsignedInt m_safeOcclusionFrame; ///. +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// Object.h /////////////////////////////////////////////////////////////////// +// Simple base object +// Author: Michael S. Booth, October 2000 + +#pragma once +#ifndef _OBJECT_H_ +#define _OBJECT_H_ + +#include "Lib/BaseType.h" + +#include "Common/Geometry.h" +#include "Common/Snapshot.h" +#include "Common/SpecialPowerMaskType.h" +#include "Common/DisabledTypes.h" +#include "Common/Thing.h" +#include "Common/ObjectStatusTypes.h" +#include "Common/Upgrade.h" + +#include "GameClient/Color.h" + +#include "GameLogic/Damage.h" //for kill() +#include "GameLogic/WeaponBonusConditionFlags.h" +#include "GameLogic/WeaponSet.h" +#include "GameLogic/WeaponSetFlags.h" +#include "GameLogic/Module/StealthUpdate.h" + +//----------------------------------------------------------------------------- +// Forward References +//----------------------------------------------------------------------------- + +class AIGroup; +class AIUpdateInterface; +class Anim2DTemplate; +class BehaviorModule; +class BehaviorModuleInterface; +class BodyModuleInterface; +class CollideModule; +class CollideModuleInterface; +class CommandButton; +class ContainModuleInterface; +class CountermeasuresBehaviorInterface; +class CreateModuleInterface; +class DamageInfo; +class DamageInfoInput; +class DamageModule; +class DamageModuleInterface; +class DestroyModuleInterface; +class DockUpdateInterface; +class Dict; +class DieModule; +class DieModuleInterface; +class ExitInterface; +class ExperienceTracker; +class FiringTracker; +class Module; +class PartitionData; +class PhysicsBehavior; +class PhysicsUpdate; +class Player; +class PolygonTrigger; +class ProductionUpdateInterface; +class ProjectileUpdateInterface; +class RadarObject; +class SightingInfo; +class SpawnBehaviorInterface; +class SpecialAbilityUpdate; +class SpecialPowerCompletionDie; +class SpecialPowerModuleInterface; +class SpecialPowerTemplate; +class SpecialPowerUpdateInterface; +class Team; +class UpdateModule; +class UpdateModuleInterface; +class UpgradeModule; +class UpgradeModuleInterface; +class UpgradeTemplate; + +class ObjectHeldHelper; +class ObjectDisabledHelper; +class ObjectSMCHelper; +class ObjectRepulsorHelper; +class StatusDamageHelper; +class SubdualDamageHelper; +class ChronoDamageHelper; +class TempWeaponBonusHelper; +class ObjectWeaponStatusHelper; +class ObjectDefectionHelper; + +enum CommandSourceType CPP_11(: Int); +enum HackerAttackMode CPP_11(: Int); +enum NameKeyType CPP_11(: Int); +enum SpecialPowerType CPP_11(: Int); +enum WeaponBonusConditionType CPP_11(: Int); +enum WeaponChoiceCriteria CPP_11(: Int); +enum WeaponSetConditionType CPP_11(: Int); +enum WeaponSetType CPP_11(: Int); +enum ArmorSetType CPP_11(: Int); +enum WeaponStatus CPP_11(: Int); +enum RadarPriorityType CPP_11(: Int); +enum CanAttackResult CPP_11(: Int); +// enum TintStatus CPP_11(: Int); + +// For ObjectStatusTypes +#include "Common/ObjectStatusTypes.h" + +// For ObjectScriptStatusBit +#include "GameLogic/ObjectScriptStatusBits.h" + +// For TintStatus +#include "GameClient/TintStatus.h" + +//----------------------------------------------------------------------------- +// Type Defines +//----------------------------------------------------------------------------- + +struct TTriggerInfo +{ + const PolygonTrigger* pTrigger; ///< The trigger area that the object is inside. + Byte entered; ///< True if the object entered this trigger area this frame. + Byte exited; ///< True if the object entered this trigger area this frame. + Byte isInside; ///< True if the object is inside this trigger area this frame. + Byte padding; ///< unused. + + TTriggerInfo() : entered(false), exited(false), isInside(false), padding(false), pTrigger(NULL) { } + +}; + +//---------------------------------------------------- + + +enum CrushSquishTestType CPP_11(: Int) +{ + TEST_CRUSH_ONLY, + TEST_SQUISH_ONLY, + TEST_CRUSH_OR_SQUISH +}; + + +// --------------------------------------------------- +/** + * Object definition. Objects are manipulated via TheGameLogic singleton. + * @todo Create an ObjectInterface class. + */ +class Object : public Thing, public Snapshot +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(Object, "ObjectPool" ) + /// destructor is non-public in order to require the use of TheGameLogic->destroyObject() + MEMORY_POOL_DELETEINSTANCE_VISIBILITY(protected) + +public: + + /// Object constructor automatically attaches all objects to "TheGameLogic" + Object(const ThingTemplate *thing, const ObjectStatusMaskType &objectStatusMask, Team *team); + + void initObject(); + + void onDestroy(); ///< run during TheGameLogic::destroyObject + + Object* getNextObject() { return m_next; } + const Object* getNextObject() const { return m_next; } + + void updateObjValuesFromMapProperties(Dict* properties); ///< Brings in properties set in the editor. + + // ids and binding + ObjectID getID() const { return m_id; } ///< this object's unique ID + void friend_bindToDrawable( Drawable *draw ); ///< set drawable association. for use ONLY by GameLogic! + Drawable* getDrawable() const { return m_drawable; } ///< drawable (if any) bound to obj + + ObjectID getProducerID() const { return m_producerID; } + void setProducer(const Object* obj); + + ObjectID getBuilderID() const { return m_builderID; } + void setBuilder( const Object *obj ); + + void enterGroup( AIGroup *group ); ///< become a member of the AIGroup + void leaveGroup( void ); ///< leave our current AIGroup + AIGroup *getGroup(void); + + // physical properties + Bool isMobile() const; ///< returns true if object is currently able to move + Bool isAbleToAttack() const; ///< returns true if object currently has some kind of attack capability + + void maskObject( Bool mask ); ///< mask/unmask object + + /** + Booby traps are set off by many random actions, so those actions are responsible for calling this. + Return value is if a booby trap was set off, so caller can react. + Those actions are: planting any type of bomb, entering, starting to capture, dying. + */ + Bool checkAndDetonateBoobyTrap(const Object *victim); + + // cannot set velocity, since this is calculated from position every frame + Bool isDestroyed() const { return m_status.test( OBJECT_STATUS_DESTROYED ); } ///< Returns TRUE if object has been destroyed + Bool isAirborneTarget() const { return m_status.test( OBJECT_STATUS_AIRBORNE_TARGET ); } ///< Our locomotor will control marking us as a valid target for anti air weapons or not + Bool isUsingAirborneLocomotor( void ) const; ///< returns true if the current locomotor is an airborne one + + /// central place for us to put any additional capture logic + void onCapture( Player *oldOwner, Player *newOwner ); + + /// And game death logic. Destroy is deletion of object as code + void onDie( DamageInfo *damageInfo ); + + // health and damage + void attemptDamage( DamageInfo *damageInfo ); ///< damage object as specified by the info + void attemptHealing(Real amount, const Object* source); ///< heal object as specified by the info + Bool attemptHealingFromSoleBenefactor ( Real amount, const Object* source, UnsignedInt duration );///< for the non-stacking healers like ambulance and propaganda + ObjectID getSoleHealingBenefactor( void ) const; + + Real estimateDamage( DamageInfoInput& damageInfo ) const; + void kill( DamageType damageType = DAMAGE_UNRESISTABLE, DeathType deathType = DEATH_NORMAL ); ///< kill the object with an optional type of damage and death. + void healCompletely(); ///< Restore max health to this Object + void notifySubdualDamage( Real amount );///< At this level, we just pass this on to our helper and do a special tint + void notifyChronoDamage( Real amount );///< At this level, we just pass this on to our helper and do a special tint + void doStatusDamage( ObjectStatusTypes status, Real duration );///< At this level, we just pass this on to our helper + void doTempWeaponBonus( WeaponBonusConditionType status, UnsignedInt duration, TintStatus tintStatus = TINT_STATUS_INVALID );///< At this level, we just pass this on to our helper + + void scoreTheKill( const Object *victim ); ///< I just killed this object. + void onVeterancyLevelChanged( VeterancyLevel oldLevel, VeterancyLevel newLevel, Bool provideFeedback = TRUE ); ///< I just achieved this level right this moment + ExperienceTracker* getExperienceTracker() {return m_experienceTracker;} + const ExperienceTracker* getExperienceTracker() const {return m_experienceTracker;} + VeterancyLevel getVeterancyLevel() const; + + inline const AsciiString& getName() const { return m_name; } + inline void setName( const AsciiString& newName ) { m_name = newName; } + + inline Team* getTeam() { return m_team; } + inline const Team *getTeam() const { return m_team; } + + void restoreOriginalTeam(); + + void setTeam( Team* team ); ///< sets the unit's team AND original team + void setTemporaryTeam( Team* team ); ///< sets the unit's team BUT NOT its original team + + Player* getControllingPlayer() const; + Relationship getRelationship(const Object *that) const; + + Color getIndicatorColor() const; + Color getNightIndicatorColor() const; + Bool hasCustomIndicatorColor() const { return m_indicatorColor != 0; } + void setCustomIndicatorColor(Color c); + void removeCustomIndicatorColor(); + + Bool isLocallyControlled() const; + Bool isNeutralControlled() const; + + Bool getIsUndetectedDefector(void) const { return BitIsSet(m_privateStatus, UNDETECTED_DEFECTOR); } + void friend_setUndetectedDefector(Bool status); + + inline Bool isOffMap() const { return BitIsSet(m_privateStatus, OFF_MAP); } + + inline Bool isCaptured() const { return BitIsSet(m_privateStatus, CAPTURED); } + void setCaptured(Bool isCaptured); + + inline const GeometryInfo& getGeometryInfo() const { return m_geometryInfo; } + void setGeometryInfo(const GeometryInfo& geom); + void setGeometryInfoZ( Real newZ ); + + void onCollide( Object *other, const Coord3D *loc, const Coord3D *normal ); + + Real getCarrierDeckHeight() const; + // access to modules + //----------------------------------------------------------------------------- + + //This is a good creation inspector. There's been multitudes of issues with conflicts of + //Objects getting constructed causing crashes either because the modules aren't created + //yet, and there's stuff being done inside of setTeam() that cares. + Bool areModulesReady() const { return m_modulesReady; } + + BehaviorModule** getBehaviorModules() const { return m_behaviors; } + + BodyModuleInterface* getBodyModule() const { return m_body; } + ContainModuleInterface* getContain() const { return m_contain; } + StealthUpdate* getStealth() const { return m_stealth; } + SpawnBehaviorInterface* getSpawnBehaviorInterface() const; + ProjectileUpdateInterface* getProjectileUpdateInterface() const; + + + // special case for the AIUpdateInterface, since it will be referred to a great deal + inline AIUpdateInterface *getAIUpdateInterface() { return m_ai; } + inline const AIUpdateInterface* getAIUpdateInterface() const { return m_ai; } + + inline AIUpdateInterface *getAI() { return m_ai; } + inline const AIUpdateInterface* getAI() const { return m_ai; } + + inline PhysicsBehavior* getPhysics() { return m_physics; } + inline const PhysicsBehavior* getPhysics() const { return m_physics; } + void topple( const Coord3D *toppleDirection, Real toppleSpeed, UnsignedInt options ); + + UpdateModule* findUpdateModule(NameKeyType key) const { return (UpdateModule*)findModule(key); } + DamageModule* findDamageModule(NameKeyType key) const { return (DamageModule*)findModule(key); } + + Bool isSalvageCrate() const; + + // + // Find us our production update interface if we have one. This method exists simply + // because we do this in a lot of places in the code and I want a convenient way to get thsi (CBD) + // + ProductionUpdateInterface* getProductionUpdateInterface( void ); + + // + // Find us our dock update interface if we have one. Again, this method exists simple + // because we want to do this in a lot of places throughout the code + // + DockUpdateInterface *getDockUpdateInterface( void ); + + // Ditto for special powers -- Kris + SpecialPowerModuleInterface* findSpecialPowerModuleInterface( SpecialPowerType type ) const; + SpecialPowerModuleInterface* findAnyShortcutSpecialPowerModuleInterface() const; + SpecialAbilityUpdate* findSpecialAbilityUpdate( SpecialPowerType type ) const; + SpecialPowerCompletionDie* findSpecialPowerCompletionDie() const; + SpecialPowerUpdateInterface* findSpecialPowerWithOverridableDestinationActive( SpecialPowerType type = SPECIAL_INVALID ) const; + SpecialPowerUpdateInterface* findSpecialPowerWithOverridableDestination( SpecialPowerType type = SPECIAL_INVALID ) const; + + CountermeasuresBehaviorInterface* getCountermeasuresBehaviorInterface(); + const CountermeasuresBehaviorInterface* getCountermeasuresBehaviorInterface() const; + + inline ObjectStatusMaskType getStatusBits() const { return m_status; } + inline Bool testStatus( ObjectStatusTypes bit ) const { return m_status.test( bit ); } + void setStatus( ObjectStatusMaskType objectStatus, Bool set = true ); + inline void clearStatus( ObjectStatusMaskType objectStatus ) { setStatus( objectStatus, false ); } + void updateUpgradeModules(); ///< We need to go through our Upgrade Modules and see which should be activated + UpgradeMaskType getObjectCompletedUpgradeMask() const { return m_objectUpgradesCompleted; } ///< Upgrades I complete locally + + //This function sucks. + //It was added for objects that can disguise as other objects and contain upgraded subobject overrides. + //A concrete example is the bomb truck. Different payloads are displayed based on which upgrades have been + //made. When the bomb truck disguises as something else, these subobjects are lost because the vector is + //stored in W3DDrawModule. When we revert back to the original bomb truck, we call this function to + //recalculate those upgraded subobjects. + void forceRefreshSubObjectUpgradeStatus(); + + // Useful for status bits that can be set by the scripting system + inline Bool testScriptStatusBit(ObjectScriptStatusBit b) const { return BitIsSet(m_scriptStatus, b); } + void setScriptStatus( ObjectScriptStatusBit bit, Bool set = true ); + inline void clearScriptStatus( ObjectScriptStatusBit bit ) { setScriptStatus(bit, false); } + + // Selectable is individually controlled on an object by object basis for design now. + // It defaults to the thingTemplate->isKindof(KINDOF_SELECTABLE), however, it can be overridden on an + // object by object basis. Finally, it can be temporarily overriden by the OBJECT_STATUS_UNSELECTABLE. + // jba. + void setSelectable(Bool selectable); + Bool isSelectable() const; + + Bool isMassSelectable() const; + + // User specified formation. + void setFormationID(enum FormationID id) {m_formationID = id;} + enum FormationID getFormationID(void) const {return m_formationID;} + void setFormationOffset(const Coord2D& offset) {m_formationOffset = offset;} + void getFormationOffset(Coord2D* offset) const {*offset = m_formationOffset;} + + +//THIS FUNCTION BELONGS AT THE OBJECT LEVEL BECAUSE THERE IS AT LEAST ONE SPECIAL UNIT +//(ANGRY MOB) WHICH NEEDS LOGIC-SIDE POSITION CALC'S... +//IT WOULD PROBABLY BE WISE TO MOVE ALL THE HARD-CODED DEFAULTS BELOW +//INTO A new Drawable::getHealthBox..() WHICH USES GEOM0INFO, MODEL DATA, INI DATA, ETC. + void getHealthBoxPosition(Coord3D& pos) const; + Bool getHealthBoxDimensions(Real &healthBoxHeight, Real &healthBoxWidth) const; + + inline Bool isEffectivelyDead() const { return (m_privateStatus & EFFECTIVELY_DEAD) != 0; } + void setEffectivelyDead(Bool dead); + + void markSingleUseCommandUsed() { m_singleUseCommandUsed = true; } + Bool hasSingleUseCommandBeenUsed() const { return m_singleUseCommandUsed; } + + /// returns true iff the object can run over the other object. + Bool canCrushOrSquish(Object *otherObj, CrushSquishTestType testType = TEST_CRUSH_OR_SQUISH) const; + UnsignedByte getCrusherLevel() const; + UnsignedByte getCrushableLevel() const; + + Bool hasUpgrade( const UpgradeTemplate *upgradeT ) const ; ///< does this object already have this upgrade + Bool affectedByUpgrade( const UpgradeTemplate *upgradeT ) const ; ///< can the object even "have" this upgrade, will it do something? + void giveUpgrade( const UpgradeTemplate *upgradeT ); ///< give upgrade to this object + void removeUpgrade( const UpgradeTemplate *upgradeT ); ///< remove upgrade from this object + + Bool hasCountermeasures() const; + void reportMissileForCountermeasures( Object *missile ); + ObjectID calculateCountermeasureToDivertTo( const Object& victim ); + + void calcNaturalRallyPoint(Coord2D *pt); ///< calc the "natural" starting rally point + void setConstructionPercent( Real percent ) { m_constructionPercent = percent; } + Real getConstructionPercent() const { return m_constructionPercent; } + + void setLayer( PathfindLayerEnum layer ); + PathfindLayerEnum getLayer() const { return m_layer; } + + void setDestinationLayer( PathfindLayerEnum layer ); + PathfindLayerEnum getDestinationLayer() const { return m_destinationLayer; } + + void prependToList(Object **pListHead); + void removeFromList(Object **pListHead); + Bool isInList(Object **pListHead) const; + + // this is intended for use ONLY by GameLogic. + void friend_deleteInstance() { deleteInstance(); } + + /// cache the partition module (should be called only by PartitionData) + void friend_setPartitionData(PartitionData *pd) { m_partitionData = pd; } + PartitionData *friend_getPartitionData() const { return m_partitionData; } + const PartitionData *friend_getConstPartitionData() const { return m_partitionData; } + + void onPartitionCellChange();///< We have moved a 'significant' amount, so do maintenence that can be considered 'cell-based' + void handlePartitionCellMaintenance(); ///< Undo and redo all shroud actions. Call when something has changed, like position or ownership or Death + + Real getVisionRange() const; ///< How far can you see? This is dynamic so it is in Object. + void setVisionRange( Real newVisionRange ); ///< Access to setting someone's Vision distance + Real getShroudRange() const; ///< How far can you shroud? Even more dynamic since it'll start at zero for everyone. + void setShroudRange( Real newShroudRange ); ///< Access to setting someone's shrouding distance + Real getShroudClearingRange() const; ///< How far do you clear shroud? + void setShroudClearingRange( Real newShroudClearingRange ); ///< Access to setting someone's clear shroud distance + void setVisionSpied(Bool setting, Int byWhom);///< Change who is looking through our eyes + + // Both of these calls are intended to only be used by TerrainLogic, specifically setActiveBoundary() + void friend_prepareForMapBoundaryAdjust(void); + void friend_notifyOfNewMapBoundary(void); + + // data for the radar + void friend_setRadarData( RadarObject *rd ) { m_radarData = rd; } + RadarObject *friend_getRadarData() { return m_radarData; } + RadarPriorityType getRadarPriority() const; + + // contained-by + inline Object *getContainedBy() { return m_containedBy; } + inline const Object *getContainedBy() const { return m_containedBy; } + inline UnsignedInt getContainedByFrame() const { return m_containedByFrame; } + inline Bool isContained() const { return m_containedBy != NULL; } + void onContainedBy( Object *containedBy ); + void onRemovedFrom( Object *removedFrom ); + Int getTransportSlotCount() const; + void friend_setContainedBy( Object *containedBy ) { m_containedBy = containedBy; } + + // Special Powers ------------------------------------------------------------------------------- + SpecialPowerModuleInterface *getSpecialPowerModule( const SpecialPowerTemplate *specialPowerTemplate ) const; + void doSpecialPower( const SpecialPowerTemplate *specialPowerTemplate, UnsignedInt commandOptions, Bool forced = false ); ///< execute power + void doSpecialPowerAtObject( const SpecialPowerTemplate *specialPowerTemplate, Object *obj, UnsignedInt commandOptions, Bool forced = false ); ///< execute power + void doSpecialPowerAtLocation( const SpecialPowerTemplate *specialPowerTemplate, const Coord3D *loc, Real angle, UnsignedInt commandOptions, Bool forced = false ); ///< execute power + void doSpecialPowerUsingWaypoints( const SpecialPowerTemplate *specialPowerTemplate, const Waypoint *way, UnsignedInt commandOptions, Bool forced = false ); ///< execute power + + void doCommandButton( const CommandButton *commandButton, CommandSourceType cmdSource ); + void doCommandButtonAtObject( const CommandButton *commandButton, Object *obj, CommandSourceType cmdSource ); + void doCommandButtonAtPosition( const CommandButton *commandButton, const Coord3D *pos, CommandSourceType cmdSource ); + void doCommandButtonUsingWaypoints( const CommandButton *commandButton, const Waypoint *way, CommandSourceType cmdSource ); + + /** + For Object specific dynamic command sets. Different from the Science specific ones handled in ThingTemplate + */ + const AsciiString& getCommandSetString() const; + void setCommandSetStringOverride( AsciiString newCommandSetString ) { m_commandSetStringOverride = newCommandSetString; } + + /// People are faking their commandsets, and, Surprise!, they are authoritative. Challenge everything. + Bool canProduceUpgrade( const UpgradeTemplate *upgrade ); + + + // Weapons & Damage ------------------------------------------------------------------------------------------------- + void reloadAllAmmo(Bool now); + Bool isOutOfAmmo() const; + Bool hasAnyWeapon() const; + Bool hasAnyDamageWeapon() const; //Kris: a should be used for real weapons that directly inflict damage... not deploy, hack, etc. + Bool hasWeaponToDealDamageType(DamageType typeToDeal) const; + Real getLargestWeaponRange() const; + UnsignedInt getMostPercentReadyToFireAnyWeapon() const; + + Weapon* getWeaponInWeaponSlot(WeaponSlotType wslot) const { return m_weaponSet.getWeaponInWeaponSlot(wslot); } + UnsignedInt getWeaponInWeaponSlotCommandSourceMask( WeaponSlotType wSlot ) const { return m_weaponSet.getNthCommandSourceMask( wSlot ); } + Bool getWeaponInWeaponSlotSyncedToSlot(WeaponSlotType thisSlot, WeaponSlotType otherSlot) const; + + // see if this current weapon set's weapons has shared reload times + const Bool isReloadTimeShared() const { return m_weaponSet.isSharedReloadTime(); } + + Weapon* getCurrentWeapon(WeaponSlotType* wslot = NULL); + const Weapon* getCurrentWeapon(WeaponSlotType* wslot = NULL) const; + void setFiringConditionForCurrentWeapon() const; + void adjustModelConditionForWeaponStatus(); ///< Check to see if I should change my model condition. + void fireCurrentWeapon(Object *target); + void fireCurrentWeapon(const Coord3D* pos); + void preFireCurrentWeapon( const Object *victim ); + void preFireCurrentWeapon(const Coord3D* pos); + UnsignedInt getLastShotFiredFrame() const; ///< Get the frame a shot was last fired on + ObjectID getLastVictimID() const; ///< Get the last victim we shot at + Weapon* findWaypointFollowingCapableWeapon(); + Bool getAmmoPipShowingInfo(Int& numTotal, Int& numFull) const; + + void notifyFiringTrackerShotFired( const Weapon* weaponFired, ObjectID victimID ) ; + + /** + Determines if the unit has any weapon that could conceivably + harm the victim. this does not take range, ammo, etc. into + account, but immutable weapon properties, such as "can you + target airborne victims". + */ + /* + NOTE: getAbleToAttackSpecificObject NO LONGER internally calls isAbleToAttack(), + since that isn't an incredibly fast call, and this is called repeatedly in some inner loops + where we already know that isAbleToAttack() == true. so you should always + call isAbleToAttack prior to calling this! (srj) + */ + CanAttackResult getAbleToAttackSpecificObject( AbleToAttackType t, const Object* target, CommandSourceType commandSource, WeaponSlotType specificSlot = (WeaponSlotType)-1 ) const; + + //Used for base defenses and otherwise stationary units to see if you can attack a position potentially out of range. + CanAttackResult getAbleToUseWeaponAgainstTarget( AbleToAttackType attackType, const Object *victim, const Coord3D *pos, CommandSourceType commandSource, WeaponSlotType specificSlot = (WeaponSlotType)-1 ) const; + + /** + Selects the best weapon for the given target, and sets it as the current weapon. + If there is no weapon that can damage the target, false is returned (and the current-weapon is unchanged). + Note that this DOES take weapon attack range into account. + */ + Bool chooseBestWeaponForTarget(const Object* target, WeaponChoiceCriteria criteria, CommandSourceType cmdSource); + + // set and/or clear a single modelcondition flag + void setModelConditionState( ModelConditionFlagType a ); + void clearModelConditionState( ModelConditionFlagType a ); + void clearAndSetModelConditionState( ModelConditionFlagType clr, ModelConditionFlagType set ); + + //Special model states are states that are turned on for a period of time, and turned off + //automatically -- used for cheer, and scripted special moment animations. Setting a special + //state will automatically clear any other special states that may be turned on so you can only + //have one at a time. + void setSpecialModelConditionState( ModelConditionFlagType set, UnsignedInt frames = 0 ); + void clearSpecialModelConditionStates(); + + // set and/or clear multiple modelcondition flags + void clearModelConditionFlags( const ModelConditionFlags& clr ); + void setModelConditionFlags( const ModelConditionFlags& set ); + void clearAndSetModelConditionFlags( const ModelConditionFlags& clr, const ModelConditionFlags& set ); + + void setWeaponSetFlag(WeaponSetType wst); + void clearWeaponSetFlag(WeaponSetType wst); + inline Bool testWeaponSetFlag(WeaponSetType wst) const { return m_curWeaponSetFlags.test(wst); } + inline const WeaponSetFlags& getWeaponSetFlags() const { return m_curWeaponSetFlags; } + Bool setWeaponLock( WeaponSlotType weaponSlot, WeaponLockType lockType ){ return m_weaponSet.setWeaponLock( weaponSlot, lockType ); } + void releaseWeaponLock(WeaponLockType lockType){ m_weaponSet.releaseWeaponLock(lockType); } + Bool isCurWeaponLocked() const { return m_weaponSet.isCurWeaponLocked(); } + + void setArmorSetFlag(ArmorSetType ast); + void clearArmorSetFlag(ArmorSetType ast); + Bool testArmorSetFlag(ArmorSetType ast) const; + + /// return true if the template has the specified special power flag set + // @todo: inline + Bool hasSpecialPower( SpecialPowerType type ) const; + Bool hasAnySpecialPower() const; + + void setWeaponBonusCondition(WeaponBonusConditionType wst); + void clearWeaponBonusCondition(WeaponBonusConditionType wst); + + // note, the !=0 at the end is important, to convert this into a boolean type! (srj) + Bool testWeaponBonusCondition(WeaponBonusConditionType wst) const { return (m_weaponBonusCondition & (1 << wst)) != 0; } + inline WeaponBonusConditionFlags getWeaponBonusCondition() const { return m_weaponBonusCondition; } + inline void setWeaponBonusConditionFlags(WeaponBonusConditionFlags flags) { m_weaponBonusCondition = flags; } + + Bool getSingleLogicalBonePosition(const char* boneName, Coord3D* position, Matrix3D* transform) const; + Bool getSingleLogicalBonePositionOnTurret(WhichTurretType whichTurret, const char* boneName, Coord3D* position, Matrix3D* transform) const; + Int getMultiLogicalBonePosition(const char* boneNamePrefix, Int maxBones, Coord3D* positions, Matrix3D* transforms, Bool convertToWorld = TRUE ) const; + + // Entered & exited. + Bool didEnter(const PolygonTrigger *pTrigger) const; + Bool didExit(const PolygonTrigger *pTrigger) const; + Bool isInside(const PolygonTrigger *pTrigger) const; + + // exiting of any kind + ExitInterface *getObjectExitInterface() const; ///< get exit interface is present + Bool hasExitInterface() const { return getObjectExitInterface() != 0; } + + ObjectShroudStatus getShroudedStatus(Int playerIndex) const; + + DisabledMaskType getDisabledFlags() const { return m_disabledMask; } + Bool isDisabled() const { return m_disabledMask.any(); } + Bool clearDisabled( DisabledType type ); + + void setDisabled( DisabledType type ); + void setDisabledUntil( DisabledType type, UnsignedInt frame ); + Bool isDisabledByType( DisabledType type ) const { return TEST_DISABLEDMASK( m_disabledMask, type ); } + + UnsignedInt getDisabledUntil( DisabledType type = DISABLED_ANY ) const; + + void pauseAllSpecialPowers( const Bool disabling ) const; + + //Checks any timers and clears disabled statii that have expired. + void checkDisabledStatus(); + + //When an AIAttackState is over, it needs to clean up any weapons that might be in leech range mode + //or else those weapons will have unlimited range! + void clearLeechRangeModeForAllWeapons(); + + Int getNumConsecutiveShotsFiredAtTarget( const Object *victim) const; + + void setHealthBoxOffset( const Coord3D& offset ) { m_healthBoxOffset = offset; } ///< for special amorphous like angry mob + + void defect( Team *newTeam, UnsignedInt detectionTime ); + void goInvulnerable( UnsignedInt time ); + + // This is public, since there is no Thing level master setting of Turret stuff. It is all done in a sleepy hamlet + // of a module called TurretAI. + virtual void reactToTurretChange( WhichTurretType turret, Real oldRotation, Real oldPitch ); + + // Convenience function for checking certain kindof bits + Bool isStructure(void) const; + + // Convenience function for checking certain kindof bits + Bool isFactionStructure(void) const; + + // Convenience function for checking certain kindof bits + Bool isNonFactionStructure(void) const; + + Bool isHero(void) const; + + Bool getReceivingDifficultyBonus() const { return m_isReceivingDifficultyBonus; } + void setReceivingDifficultyBonus(Bool receive); + + inline UnsignedInt getSafeOcclusionFrame(void) { return m_safeOcclusionFrame; } //< this is an object specific frame at which it's safe to enable building occlusion. + inline void setSafeOcclusionFrame(UnsignedInt frame) { m_safeOcclusionFrame = frame;} + + // All of our cheating for radars and power go here. + // This is the function that we now call in becomingTeamMember to adjust our power. + // If incoming is true, we're working on the incoming player, if its false, we're on the outgoing + // player. These are friend_s for player. + void friend_adjustPowerForPlayer( Bool incoming ); + +protected: + + void setOrRestoreTeam( Team* team, Bool restoring ); + + void onDisabledEdge(Bool becomingDisabled); + // All of our cheating for radars and power go here. + + + // snapshot methods + void crc( Xfer *xfer ); + void xfer( Xfer *xfer ); + void loadPostProcess(); + + void handleShroud(); + void handleValueMap(); + void handleThreatMap(); + + // NOTE NOTE NOTE -- this is a private method. Do Not Ever Make It Public. + // If you think you need to make it public, you are wrong. Don't do it. + // It will go away someday. Yeah, right. Just like GlobalData. + Module* findModule(NameKeyType key) const; + + Bool didEnterOrExit() const; + + void setID( ObjectID id ); + virtual Object *asObjectMeth() { return this; } + virtual const Object *asObjectMeth() const { return this; } + + virtual Real calculateHeightAboveTerrain(void) const; // Calculates the actual height above terrain. Doesn't use cache. + + void updateTriggerAreaFlags(void); + void setTriggerAreaFlagsForChangeInPosition(void); + + /// Look and unlook are protected. They should be called from Object::reasonToLook. Like Capture, or death. + void look(); + void unlook(); + void shroud(); + void unshroud(); + + /// value and threat functions are protected, and should only be called from handleValueMap + void addValue(); + void removeValue(); + + void addThreat(); + void removeThreat(); + + virtual void reactToTransformChange(const Matrix3D* oldMtx, const Coord3D* oldPos, Real oldAngle); + +private: + + // yes, private. No, really. Private. Don't expose. + enum ObjectPrivateStatusBits + { + EFFECTIVELY_DEAD = (1 << 0), ///< Object is effectively dead + UNDETECTED_DEFECTOR = (1 << 1), ///< set to true when I defect from my team; set to false when I attack anything or when time runs out + CAPTURED = (1 << 2), ///< set to true if I've been captured, otherwise, its false. (Note: Never becomes false once it's true) + OFF_MAP = (1 << 3) ///< set to true if I am known to be OFF the current map. + // NOTE: Object currently only uses a Byte for this, so if you add status bits, you may need to enlarge that field. + }; + + ObjectID m_id; ///< this object's unique ID + ObjectID m_producerID; ///< object that produced us, if any + ObjectID m_builderID; ///< object that is building or has built us (dozers or workers are builders) + Drawable* m_drawable; ///< drawable (if any) for this object + AsciiString m_name; ///< internal name + + Object * m_next; + Object * m_prev; + ObjectStatusMaskType m_status; ///< status bits (see ObjectStatusMaskType) + + GeometryInfo m_geometryInfo; + + AIGroup* m_group; ///< if non-NULL, we are part of this group of agents + + // These will last for my lifetime. I will reuse them and reset them. The truly dynamic ones are in PartitionManager + SightingInfo *m_partitionLastLook; ///< Where and for whom I last looked, so I can undo its effects when I stop + SightingInfo *m_partitionRevealAllLastLook; ///< And a seperate look to reveal at a different range if so marked + Int m_visionSpiedBy[MAX_PLAYER_COUNT]; ///< Reference count of having units spied on by players. + PlayerMaskType m_visionSpiedMask; ///< For quick lookup and edge triggered maintenance + + SightingInfo *m_partitionLastShroud; ///< Where and for whom I last shrouded, so I can undo its effects when I stop + SightingInfo *m_partitionLastThreat; ///< Where and for whom I last delt with threat, so I can undo its effects when I stop + SightingInfo *m_partitionLastValue; ///< Where and for whom I last delt with value, so I can undo its effects when I stop + + Real m_visionRange; ///< looking range + Real m_shroudClearingRange; ///< looking range for shroud ONLY + Real m_shroudRange; ///< like looking range, this is how far I shroud others' looks + + DisabledMaskType m_disabledMask; + UnsignedInt m_disabledTillFrame[ DISABLED_COUNT ]; + + UnsignedInt m_smcUntil; + + enum { NUM_SLEEP_HELPERS = 8 }; + ObjectRepulsorHelper* m_repulsorHelper; + ObjectSMCHelper* m_smcHelper; + ObjectWeaponStatusHelper* m_wsHelper; + ObjectDefectionHelper* m_defectionHelper; + StatusDamageHelper* m_statusDamageHelper; + SubdualDamageHelper* m_subdualDamageHelper; + ChronoDamageHelper* m_chronoDamageHelper; + TempWeaponBonusHelper* m_tempWeaponBonusHelper; + FiringTracker* m_firingTracker; ///< Tracker is really a "helper" and is included NUM_SLEEP_HELPERS + + // modules + BehaviorModule** m_behaviors; // BehaviorModule, not BehaviorModuleInterface + + // cache these, for convenience + ContainModuleInterface* m_contain; + BodyModuleInterface* m_body; + StealthUpdate* m_stealth; + + AIUpdateInterface* m_ai; ///< ai interface (if any), cached for handy access. (duplicate of entry in the module array!) + PhysicsBehavior* m_physics; ///< physics interface (if any), cached for handy access. (duplicate of entry in the module array!) + + PartitionData* m_partitionData; ///< our PartitionData + RadarObject* m_radarData; ///< radar data + ExperienceTracker* m_experienceTracker; ///< Manages experience, gaining levels, and value when killed + + Object* m_containedBy; /**< an object can only be contained by at most one + other object, this is that object (if present) */ + ObjectID m_xferContainedByID; ///< xfer uses IDs to store pointers and looks them up after + UnsignedInt m_containedByFrame; ///< frame we were contained by m_containedBy + + Real m_constructionPercent; ///< for objects being built ... this is the amount completed (0.0 to 100.0) + UpgradeMaskType m_objectUpgradesCompleted; ///< Bit field of upgrades locally completed. + + Team* m_team; ///< team that is current owner of this guy + AsciiString m_originalTeamName; ///< team that was the original ("birth") team of this guy + Color m_indicatorColor; ///< if nonzero, use this instead of controlling player's color + + Coord3D m_healthBoxOffset; ///< generally zero, except for special amorphous ones like angry mob + + /// @todo srj -- convert to non-DLINK list, after it is once again possible to test the change + MAKE_DLINK(Object, TeamMemberList) ///< other Things that are members of the same team + + // Weapons & Damage ------------------------------------------------------------------------------------------------- + WeaponSet m_weaponSet; + WeaponSetFlags m_curWeaponSetFlags; + WeaponBonusConditionFlags m_weaponBonusCondition; + Byte m_lastWeaponCondition[WEAPONSLOT_COUNT]; + + SpecialPowerMaskType m_specialPowerBits; ///< bits determining what kind of special abilities this object has access to. + + //////////////////////////////////////< for the non-stacking healers like ambulance and propaganda + ObjectID m_soleHealingBenefactorID; ///< who is the only other object that can give me this non-stacking heal benefit? + UnsignedInt m_soleHealingBenefactorExpirationFrame; ///< on what frame can I accept healing (thus to switch) from a new benefactor + /////////////////////////////////// + + // Entered & exited housekeeping. + enum { MAX_TRIGGER_AREA_INFOS = 5 }; + TTriggerInfo m_triggerInfo[MAX_TRIGGER_AREA_INFOS]; + UnsignedInt m_enteredOrExitedFrame; + ICoord3D m_iPos; + + PathfindLayerEnum m_layer; // Layer object is pathing on. + PathfindLayerEnum m_destinationLayer; // Layer of current path goal. + + // User formations. + FormationID m_formationID; + Coord2D m_formationOffset; + + AsciiString m_commandSetStringOverride;///< To allow specific object to switch command sets + + UnsignedInt m_safeOcclusionFrame; ///. -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: GlobalData.cpp /////////////////////////////////////////////////////////////////////////// -// The GameLogicData object -// Author: trolfs, Michael Booth, Colin Day, April 2001 -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//#pragma once - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine - -#define DEFINE_TERRAIN_LOD_NAMES -#define DEFINE_TIME_OF_DAY_NAMES -#define DEFINE_WEATHER_NAMES -#define DEFINE_BODYDAMAGETYPE_NAMES -#define DEFINE_PANNING_NAMES - -#include "Common/crc.h" -#include "Common/file.h" -#include "Common/FileSystem.h" -#include "Common/GameAudio.h" -#include "Common/INI.h" -#include "Common/Registry.h" -#include "Common/UserPreferences.h" -#include "Common/version.h" - -#include "GameLogic/AI.h" -#include "GameLogic/Weapon.h" -#include "GameLogic/Module/BodyModule.h" - -#include "GameClient/Color.h" -#include "GameClient/TerrainVisual.h" -#include "GameClient/TintStatus.h" - -#include "GameNetwork/FirewallHelper.h" - -// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// -GlobalData* TheWritableGlobalData = NULL; ///< The global data singleton - -//------------------------------------------------------------------------------------------------- -GlobalData* GlobalData::m_theOriginal = NULL; - - - -//------------------------------------------------------------------------------------------------- -/*static*/ void GlobalData::parseTintStatusType(INI* ini, void* instance, void* store, const void* userData) -{ - TintStatus tintType = (TintStatus)INI::scanIndexList(ini->getNextToken(), TintStatusFlags::getBitNames()); - - DrawableColorTint* colorTintTypes = (DrawableColorTint*)(store); - DrawableColorTint* tintEntry = &colorTintTypes[tintType]; - - INI::parseRGBColorReal(ini, instance, &tintEntry->color, NULL); - INI::parseRGBColorReal(ini, instance, &tintEntry->colorInfantry, NULL); - - INI::parseUnsignedInt(ini, instance, &tintEntry->attackFrames, NULL); - INI::parseUnsignedInt(ini, instance, &tintEntry->decayFrames, NULL); -} - - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -/*static*/ const FieldParse GlobalData::s_GlobalDataFieldParseTable[] = -{ - { "Windowed", INI::parseBool, NULL, offsetof( GlobalData, m_windowed ) }, - { "XResolution", INI::parseInt, NULL, offsetof( GlobalData, m_xResolution ) }, - { "YResolution", INI::parseInt, NULL, offsetof( GlobalData, m_yResolution ) }, - { "MapName", INI::parseAsciiString,NULL, offsetof( GlobalData, m_mapName ) }, - { "MoveHintName", INI::parseAsciiString,NULL, offsetof( GlobalData, m_moveHintName ) }, - { "UseTrees", INI::parseBool, NULL, offsetof( GlobalData, m_useTrees ) }, - { "UseFPSLimit", INI::parseBool, NULL, offsetof( GlobalData, m_useFpsLimit ) }, - { "DumpAssetUsage", INI::parseBool, NULL, offsetof( GlobalData, m_dumpAssetUsage ) }, - { "FramesPerSecondLimit", INI::parseInt, NULL, offsetof( GlobalData, m_framesPerSecondLimit ) }, - { "ChipsetType", INI::parseInt, NULL, offsetof( GlobalData, m_chipSetType ) }, - { "MaxShellScreens", INI::parseInt, NULL, offsetof( GlobalData, m_maxShellScreens ) }, - { "UseCloudMap", INI::parseBool, NULL, offsetof( GlobalData, m_useCloudMap ) }, - { "UseLightMap", INI::parseBool, NULL, offsetof( GlobalData, m_useLightMap ) }, - { "BilinearTerrainTex", INI::parseBool, NULL, offsetof( GlobalData, m_bilinearTerrainTex ) }, - { "TrilinearTerrainTex", INI::parseBool, NULL, offsetof( GlobalData, m_trilinearTerrainTex ) }, - { "MultiPassTerrain", INI::parseBool, NULL, offsetof( GlobalData, m_multiPassTerrain ) }, - { "AdjustCliffTextures", INI::parseBool, NULL, offsetof( GlobalData, m_adjustCliffTextures ) }, - { "Use3WayTerrainBlends", INI::parseInt, NULL, offsetof( GlobalData, m_use3WayTerrainBlends ) }, - { "StretchTerrain", INI::parseBool, NULL, offsetof( GlobalData, m_stretchTerrain ) }, - { "UseHalfHeightMap", INI::parseBool, NULL, offsetof( GlobalData, m_useHalfHeightMap ) }, - - - { "DrawEntireTerrain", INI::parseBool, NULL, offsetof( GlobalData, m_drawEntireTerrain ) }, - { "TerrainLOD", INI::parseIndexList, TerrainLODNames, offsetof( GlobalData, m_terrainLOD ) }, - { "TerrainLODTargetTimeMS", INI::parseInt, NULL, offsetof( GlobalData, m_terrainLODTargetTimeMS ) }, - { "RightMouseAlwaysScrolls", INI::parseBool, NULL, offsetof( GlobalData, m_rightMouseAlwaysScrolls ) }, - { "UseWaterPlane", INI::parseBool, NULL, offsetof( GlobalData, m_useWaterPlane ) }, - { "UseCloudPlane", INI::parseBool, NULL, offsetof( GlobalData, m_useCloudPlane ) }, - { "DownwindAngle", INI::parseReal, NULL, offsetof( GlobalData, m_downwindAngle ) }, - { "UseShadowVolumes", INI::parseBool, NULL, offsetof( GlobalData, m_useShadowVolumes ) }, - { "UseShadowDecals", INI::parseBool, NULL, offsetof( GlobalData, m_useShadowDecals ) }, - { "TextureReductionFactor", INI::parseInt, NULL, offsetof( GlobalData, m_textureReductionFactor ) }, - { "UseBehindBuildingMarker", INI::parseBool, NULL, offsetof( GlobalData, m_enableBehindBuildingMarkers ) }, - { "WaterPositionX", INI::parseReal, NULL, offsetof( GlobalData, m_waterPositionX ) }, - { "WaterPositionY", INI::parseReal, NULL, offsetof( GlobalData, m_waterPositionY ) }, - { "WaterPositionZ", INI::parseReal, NULL, offsetof( GlobalData, m_waterPositionZ ) }, - { "WaterExtentX", INI::parseReal, NULL, offsetof( GlobalData, m_waterExtentX ) }, - { "WaterExtentY", INI::parseReal, NULL, offsetof( GlobalData, m_waterExtentY ) }, - { "WaterType", INI::parseInt, NULL, offsetof( GlobalData, m_waterType ) }, - { "FeatherWater", INI::parseInt, NULL, offsetof( GlobalData, m_featherWater ) }, - { "ShowSoftWaterEdge", INI::parseBool, NULL, offsetof( GlobalData, m_showSoftWaterEdge ) }, - - // nasty ick, we need to save this data with a map and not hard code INI values - { "VertexWaterAvailableMaps1", INI::parseAsciiString, NULL, offsetof( GlobalData, m_vertexWaterAvailableMaps[ 0 ] ) }, - { "VertexWaterHeightClampLow1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampLow[ 0 ] ) }, - { "VertexWaterHeightClampHi1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampHi[ 0 ] ) }, - { "VertexWaterAngle1", INI::parseAngleReal, NULL, offsetof( GlobalData, m_vertexWaterAngle[ 0 ] ) }, - { "VertexWaterXPosition1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterXPosition[ 0 ] ) }, - { "VertexWaterYPosition1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterYPosition[ 0 ] ) }, - { "VertexWaterZPosition1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterZPosition[ 0 ] ) }, - { "VertexWaterXGridCells1", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterXGridCells[ 0 ] ) }, - { "VertexWaterYGridCells1", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterYGridCells[ 0 ] ) }, - { "VertexWaterGridSize1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterGridSize[ 0 ] ) }, - { "VertexWaterAttenuationA1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationA[ 0 ] ) }, - { "VertexWaterAttenuationB1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationB[ 0 ] ) }, - { "VertexWaterAttenuationC1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationC[ 0 ] ) }, - { "VertexWaterAttenuationRange1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationRange[ 0 ] ) }, - - // nasty ick, we need to save this data with a map and not hard code INI values - { "VertexWaterAvailableMaps2", INI::parseAsciiString, NULL, offsetof( GlobalData, m_vertexWaterAvailableMaps[ 1 ] ) }, - { "VertexWaterHeightClampLow2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampLow[ 1 ] ) }, - { "VertexWaterHeightClampHi2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampHi[ 1 ] ) }, - { "VertexWaterAngle2", INI::parseAngleReal, NULL, offsetof( GlobalData, m_vertexWaterAngle[ 1 ] ) }, - { "VertexWaterXPosition2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterXPosition[ 1 ] ) }, - { "VertexWaterYPosition2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterYPosition[ 1 ] ) }, - { "VertexWaterZPosition2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterZPosition[ 1 ] ) }, - { "VertexWaterXGridCells2", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterXGridCells[ 1 ] ) }, - { "VertexWaterYGridCells2", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterYGridCells[ 1 ] ) }, - { "VertexWaterGridSize2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterGridSize[ 1 ] ) }, - { "VertexWaterAttenuationA2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationA[ 1 ] ) }, - { "VertexWaterAttenuationB2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationB[ 1 ] ) }, - { "VertexWaterAttenuationC2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationC[ 1 ] ) }, - { "VertexWaterAttenuationRange2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationRange[ 1 ] ) }, - - // nasty ick, we need to save this data with a map and not hard code INI values - { "VertexWaterAvailableMaps3", INI::parseAsciiString, NULL, offsetof( GlobalData, m_vertexWaterAvailableMaps[ 2 ] ) }, - { "VertexWaterHeightClampLow3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampLow[ 2 ] ) }, - { "VertexWaterHeightClampHi3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampHi[ 2 ] ) }, - { "VertexWaterAngle3", INI::parseAngleReal, NULL, offsetof( GlobalData, m_vertexWaterAngle[ 2 ] ) }, - { "VertexWaterXPosition3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterXPosition[ 2 ] ) }, - { "VertexWaterYPosition3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterYPosition[ 2 ] ) }, - { "VertexWaterZPosition3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterZPosition[ 2 ] ) }, - { "VertexWaterXGridCells3", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterXGridCells[ 2 ] ) }, - { "VertexWaterYGridCells3", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterYGridCells[ 2 ] ) }, - { "VertexWaterGridSize3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterGridSize[ 2 ] ) }, - { "VertexWaterAttenuationA3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationA[ 2 ] ) }, - { "VertexWaterAttenuationB3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationB[ 2 ] ) }, - { "VertexWaterAttenuationC3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationC[ 2 ] ) }, - { "VertexWaterAttenuationRange3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationRange[ 2 ] ) }, - - // nasty ick, we need to save this data with a map and not hard code INI values - { "VertexWaterAvailableMaps4", INI::parseAsciiString, NULL, offsetof( GlobalData, m_vertexWaterAvailableMaps[ 3 ] ) }, - { "VertexWaterHeightClampLow4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampLow[ 3 ] ) }, - { "VertexWaterHeightClampHi4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampHi[ 3 ] ) }, - { "VertexWaterAngle4", INI::parseAngleReal, NULL, offsetof( GlobalData, m_vertexWaterAngle[ 3 ] ) }, - { "VertexWaterXPosition4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterXPosition[ 3 ] ) }, - { "VertexWaterYPosition4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterYPosition[ 3 ] ) }, - { "VertexWaterZPosition4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterZPosition[ 3 ] ) }, - { "VertexWaterXGridCells4", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterXGridCells[ 3 ] ) }, - { "VertexWaterYGridCells4", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterYGridCells[ 3 ] ) }, - { "VertexWaterGridSize4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterGridSize[ 3 ] ) }, - { "VertexWaterAttenuationA4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationA[ 3 ] ) }, - { "VertexWaterAttenuationB4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationB[ 3 ] ) }, - { "VertexWaterAttenuationC4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationC[ 3 ] ) }, - { "VertexWaterAttenuationRange4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationRange[ 3 ] ) }, - - { "SkyBoxPositionZ", INI::parseReal, NULL, offsetof( GlobalData, m_skyBoxPositionZ ) }, - { "SkyBoxScale", INI::parseReal, NULL, offsetof( GlobalData, m_skyBoxScale ) }, - { "DrawSkyBox", INI::parseBool, NULL, offsetof( GlobalData, m_drawSkyBox ) }, - { "CameraPitch", INI::parseReal, NULL, offsetof( GlobalData, m_cameraPitch ) }, - { "CameraYaw", INI::parseReal, NULL, offsetof( GlobalData, m_cameraYaw ) }, - { "CameraHeight", INI::parseReal, NULL, offsetof( GlobalData, m_cameraHeight ) }, - { "MaxCameraHeight", INI::parseReal, NULL, offsetof( GlobalData, m_maxCameraHeight ) }, - { "MinCameraHeight", INI::parseReal, NULL, offsetof( GlobalData, m_minCameraHeight ) }, - { "TerrainHeightAtEdgeOfMap", INI::parseReal, NULL, offsetof( GlobalData, m_terrainHeightAtEdgeOfMap ) }, - { "UnitDamagedThreshold", INI::parseReal, NULL, offsetof( GlobalData, m_unitDamagedThresh ) }, - { "UnitReallyDamagedThreshold", INI::parseReal, NULL, offsetof( GlobalData, m_unitReallyDamagedThresh ) }, - { "GroundStiffness", INI::parseReal, NULL, offsetof( GlobalData, m_groundStiffness ) }, - { "StructureStiffness", INI::parseReal, NULL, offsetof( GlobalData, m_structureStiffness ) }, - { "Gravity", INI::parseAccelerationReal, NULL, offsetof( GlobalData, m_gravity ) }, - { "StealthFriendlyOpacity", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_stealthFriendlyOpacity ) }, - { "DefaultOcclusionDelay", INI::parseDurationUnsignedInt, NULL, offsetof( GlobalData, m_defaultOcclusionDelay ) }, - - { "PartitionCellSize", INI::parseReal, NULL, offsetof( GlobalData, m_partitionCellSize ) }, - - { "AmmoPipScaleFactor", INI::parseReal, NULL, offsetof( GlobalData, m_ammoPipScaleFactor ) }, - { "ContainerPipScaleFactor", INI::parseReal, NULL, offsetof( GlobalData, m_containerPipScaleFactor ) }, - { "AmmoPipWorldOffset", INI::parseCoord3D, NULL, offsetof( GlobalData, m_ammoPipWorldOffset ) }, - { "ContainerPipWorldOffset", INI::parseCoord3D, NULL, offsetof( GlobalData, m_containerPipWorldOffset ) }, - { "AmmoPipScreenOffset", INI::parseCoord2D, NULL, offsetof( GlobalData, m_ammoPipScreenOffset ) }, - { "ContainerPipScreenOffset", INI::parseCoord2D, NULL, offsetof( GlobalData, m_containerPipScreenOffset ) }, - - { "HistoricDamageLimit", INI::parseDurationUnsignedInt, NULL, offsetof( GlobalData, m_historicDamageLimit ) }, - - { "MaxTerrainTracks", INI::parseInt, NULL, offsetof( GlobalData, m_maxTerrainTracks ) }, - { "TimeOfDay", INI::parseIndexList, TimeOfDayNames, offsetof( GlobalData, m_timeOfDay ) }, - { "Weather", INI::parseIndexList, WeatherNames, offsetof( GlobalData, m_weather ) }, - { "MakeTrackMarks", INI::parseBool, NULL, offsetof( GlobalData, m_makeTrackMarks ) }, - { "HideGarrisonFlags", INI::parseBool, NULL, offsetof( GlobalData, m_hideGarrisonFlags ) }, - { "ForceModelsToFollowTimeOfDay", INI::parseBool, NULL, offsetof( GlobalData, m_forceModelsToFollowTimeOfDay ) }, - { "ForceModelsToFollowWeather", INI::parseBool, NULL, offsetof( GlobalData, m_forceModelsToFollowWeather ) }, - - { "LevelGainAnimationName", INI::parseAsciiString, NULL, offsetof( GlobalData, m_levelGainAnimationName ) }, - { "LevelGainAnimationTime", INI::parseReal, NULL, offsetof( GlobalData, m_levelGainAnimationDisplayTimeInSeconds ) }, - { "LevelGainAnimationZRise", INI::parseReal, NULL, offsetof( GlobalData, m_levelGainAnimationZRisePerSecond ) }, - - { "GetHealedAnimationName", INI::parseAsciiString, NULL, offsetof( GlobalData, m_getHealedAnimationName ) }, - { "GetHealedAnimationTime", INI::parseReal, NULL, offsetof( GlobalData, m_getHealedAnimationDisplayTimeInSeconds ) }, - { "GetHealedAnimationZRise", INI::parseReal, NULL, offsetof( GlobalData, m_getHealedAnimationZRisePerSecond ) }, - - { "TerrainLightingMorningAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][0].ambient ) }, - { "TerrainLightingMorningDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][0].diffuse ) }, - { "TerrainLightingMorningLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][0].lightPos ) }, - { "TerrainLightingAfternoonAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][0].ambient ) }, - { "TerrainLightingAfternoonDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][0].diffuse ) }, - { "TerrainLightingAfternoonLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][0].lightPos ) }, - { "TerrainLightingEveningAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][0].ambient ) }, - { "TerrainLightingEveningDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][0].diffuse ) }, - { "TerrainLightingEveningLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][0].lightPos ) }, - { "TerrainLightingNightAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][0].ambient ) }, - { "TerrainLightingNightDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][0].diffuse ) }, - { "TerrainLightingNightLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][0].lightPos ) }, - - { "TerrainObjectsLightingMorningAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][0].ambient ) }, - { "TerrainObjectsLightingMorningDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][0].diffuse ) }, - { "TerrainObjectsLightingMorningLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][0].lightPos ) }, - { "TerrainObjectsLightingAfternoonAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][0].ambient ) }, - { "TerrainObjectsLightingAfternoonDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][0].diffuse ) }, - { "TerrainObjectsLightingAfternoonLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][0].lightPos ) }, - { "TerrainObjectsLightingEveningAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][0].ambient ) }, - { "TerrainObjectsLightingEveningDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][0].diffuse ) }, - { "TerrainObjectsLightingEveningLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][0].lightPos ) }, - { "TerrainObjectsLightingNightAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][0].ambient ) }, - { "TerrainObjectsLightingNightDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][0].diffuse ) }, - { "TerrainObjectsLightingNightLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][0].lightPos ) }, - - //Secondary global light - { "TerrainLightingMorningAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][1].ambient ) }, - { "TerrainLightingMorningDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][1].diffuse ) }, - { "TerrainLightingMorningLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][1].lightPos ) }, - { "TerrainLightingAfternoonAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][1].ambient ) }, - { "TerrainLightingAfternoonDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][1].diffuse ) }, - { "TerrainLightingAfternoonLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][1].lightPos ) }, - { "TerrainLightingEveningAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][1].ambient ) }, - { "TerrainLightingEveningDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][1].diffuse ) }, - { "TerrainLightingEveningLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][1].lightPos ) }, - { "TerrainLightingNightAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][1].ambient ) }, - { "TerrainLightingNightDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][1].diffuse ) }, - { "TerrainLightingNightLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][1].lightPos ) }, - - { "TerrainObjectsLightingMorningAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][1].ambient ) }, - { "TerrainObjectsLightingMorningDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][1].diffuse ) }, - { "TerrainObjectsLightingMorningLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][1].lightPos ) }, - { "TerrainObjectsLightingAfternoonAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][1].ambient ) }, - { "TerrainObjectsLightingAfternoonDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][1].diffuse ) }, - { "TerrainObjectsLightingAfternoonLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][1].lightPos ) }, - { "TerrainObjectsLightingEveningAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][1].ambient ) }, - { "TerrainObjectsLightingEveningDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][1].diffuse ) }, - { "TerrainObjectsLightingEveningLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][1].lightPos ) }, - { "TerrainObjectsLightingNightAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][1].ambient ) }, - { "TerrainObjectsLightingNightDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][1].diffuse ) }, - { "TerrainObjectsLightingNightLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][1].lightPos ) }, - - //Third global light - { "TerrainLightingMorningAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][2].ambient ) }, - { "TerrainLightingMorningDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][2].diffuse ) }, - { "TerrainLightingMorningLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][2].lightPos ) }, - { "TerrainLightingAfternoonAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][2].ambient ) }, - { "TerrainLightingAfternoonDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][2].diffuse ) }, - { "TerrainLightingAfternoonLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][2].lightPos ) }, - { "TerrainLightingEveningAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][2].ambient ) }, - { "TerrainLightingEveningDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][2].diffuse ) }, - { "TerrainLightingEveningLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][2].lightPos ) }, - { "TerrainLightingNightAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][2].ambient ) }, - { "TerrainLightingNightDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][2].diffuse ) }, - { "TerrainLightingNightLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][2].lightPos ) }, - - { "TerrainObjectsLightingMorningAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][2].ambient ) }, - { "TerrainObjectsLightingMorningDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][2].diffuse ) }, - { "TerrainObjectsLightingMorningLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][2].lightPos ) }, - { "TerrainObjectsLightingAfternoonAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][2].ambient ) }, - { "TerrainObjectsLightingAfternoonDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][2].diffuse ) }, - { "TerrainObjectsLightingAfternoonLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][2].lightPos ) }, - { "TerrainObjectsLightingEveningAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][2].ambient ) }, - { "TerrainObjectsLightingEveningDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][2].diffuse ) }, - { "TerrainObjectsLightingEveningLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][2].lightPos ) }, - { "TerrainObjectsLightingNightAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][2].ambient ) }, - { "TerrainObjectsLightingNightDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][2].diffuse ) }, - { "TerrainObjectsLightingNightLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][2].lightPos ) }, - - - { "NumberGlobalLights", INI::parseInt, NULL, offsetof( GlobalData, m_numGlobalLights)}, - { "InfantryLightMorningScale", INI::parseReal, NULL, offsetof( GlobalData, m_infantryLightScale[TIME_OF_DAY_MORNING] ) }, - { "InfantryLightAfternoonScale", INI::parseReal, NULL, offsetof( GlobalData, m_infantryLightScale[TIME_OF_DAY_AFTERNOON] ) }, - { "InfantryLightEveningScale", INI::parseReal, NULL, offsetof( GlobalData, m_infantryLightScale[TIME_OF_DAY_EVENING] ) }, - { "InfantryLightNightScale", INI::parseReal, NULL, offsetof( GlobalData, m_infantryLightScale[TIME_OF_DAY_NIGHT] ) }, - - { "MaxTranslucentObjects", INI::parseInt, NULL, offsetof( GlobalData, m_maxVisibleTranslucentObjects) }, - { "OccludedColorLuminanceScale", INI::parseReal, NULL, offsetof( GlobalData, m_occludedLuminanceScale) }, - -/* These are internal use only, they do not need file definitons - { "TerrainAmbientRGB", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainAmbient ) }, - { "TerrainDiffuseRGB", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainDiffuse ) }, - { "TerrainLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLightPos ) }, -*/ - { "MaxRoadSegments", INI::parseInt, NULL, offsetof( GlobalData, m_maxRoadSegments ) }, - { "MaxRoadVertex", INI::parseInt, NULL, offsetof( GlobalData, m_maxRoadVertex ) }, - { "MaxRoadIndex", INI::parseInt, NULL, offsetof( GlobalData, m_maxRoadIndex ) }, - { "MaxRoadTypes", INI::parseInt, NULL, offsetof( GlobalData, m_maxRoadTypes ) }, - - { "ValuePerSupplyBox", INI::parseInt, NULL, offsetof( GlobalData, m_baseValuePerSupplyBox ) }, - - { "AudioOn", INI::parseBool, NULL, offsetof( GlobalData, m_audioOn ) }, - { "MusicOn", INI::parseBool, NULL, offsetof( GlobalData, m_musicOn ) }, - { "SoundsOn", INI::parseBool, NULL, offsetof( GlobalData, m_soundsOn ) }, - { "Sounds3DOn", INI::parseBool, NULL, offsetof( GlobalData, m_sounds3DOn ) }, - { "SpeechOn", INI::parseBool, NULL, offsetof( GlobalData, m_speechOn ) }, - { "VideoOn", INI::parseBool, NULL, offsetof( GlobalData, m_videoOn ) }, - { "DisableCameraMovements", INI::parseBool, NULL, offsetof( GlobalData, m_disableCameraMovement ) }, - -/* These are internal use only, they do not need file definitons - /// @todo remove this hack - { "InGame", INI::parseBool, NULL, offsetof( GlobalData, m_inGame ) }, -*/ - - { "DebugAI", INI::parseBool, NULL, offsetof( GlobalData, m_debugAI ) }, - { "DebugAIObstacles", INI::parseBool, NULL, offsetof( GlobalData, m_debugAIObstacles ) }, - { "ShowClientPhysics", INI::parseBool, NULL, offsetof( GlobalData, m_showClientPhysics ) }, - { "ShowTerrainNormals", INI::parseBool, NULL, offsetof( GlobalData, m_showTerrainNormals ) }, - { "ShowObjectHealth", INI::parseBool, NULL, offsetof( GlobalData, m_showObjectHealth ) }, - - { "ParticleScale", INI::parseReal, NULL, offsetof( GlobalData, m_particleScale ) }, - { "AutoFireParticleSmallPrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleSmallPrefix ) }, - { "AutoFireParticleSmallSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleSmallSystem ) }, - { "AutoFireParticleSmallMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoFireParticleSmallMax ) }, - { "AutoFireParticleMediumPrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleMediumPrefix ) }, - { "AutoFireParticleMediumSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleMediumSystem ) }, - { "AutoFireParticleMediumMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoFireParticleMediumMax ) }, - { "AutoFireParticleLargePrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleLargePrefix ) }, - { "AutoFireParticleLargeSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleLargeSystem ) }, - { "AutoFireParticleLargeMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoFireParticleLargeMax ) }, - { "AutoSmokeParticleSmallPrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleSmallPrefix ) }, - { "AutoSmokeParticleSmallSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleSmallSystem ) }, - { "AutoSmokeParticleSmallMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoSmokeParticleSmallMax ) }, - { "AutoSmokeParticleMediumPrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleMediumPrefix ) }, - { "AutoSmokeParticleMediumSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleMediumSystem ) }, - { "AutoSmokeParticleMediumMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoSmokeParticleMediumMax ) }, - { "AutoSmokeParticleLargePrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleLargePrefix ) }, - { "AutoSmokeParticleLargeSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleLargeSystem ) }, - { "AutoSmokeParticleLargeMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoSmokeParticleLargeMax ) }, - { "AutoAflameParticlePrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoAflameParticlePrefix ) }, - { "AutoAflameParticleSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoAflameParticleSystem ) }, - { "AutoAflameParticleMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoAflameParticleMax ) }, - -/* These are internal use only, they do not need file definitons - { "LatencyAverage", INI::parseInt, NULL, offsetof( GlobalData, m_latencyAverage ) }, - { "LatencyAmplitude", INI::parseInt, NULL, offsetof( GlobalData, m_latencyAmplitude ) }, - { "LatencyPeriod", INI::parseInt, NULL, offsetof( GlobalData, m_latencyPeriod ) }, - { "LatencyNoise", INI::parseInt, NULL, offsetof( GlobalData, m_latencyNoise ) }, - { "PacketLoss", INI::parseInt, NULL, offsetof( GlobalData, m_packetLoss ) }, -*/ - - { "BuildSpeed", INI::parseReal, NULL, offsetof( GlobalData, m_BuildSpeed ) }, - { "MinDistFromEdgeOfMapForBuild", INI::parseReal, NULL, offsetof( GlobalData, m_MinDistFromEdgeOfMapForBuild ) }, - { "SupplyBuildBorder", INI::parseReal, NULL, offsetof( GlobalData, m_SupplyBuildBorder ) }, - { "AllowedHeightVariationForBuilding", INI::parseReal,NULL, offsetof( GlobalData, m_allowedHeightVariationForBuilding ) }, - { "MinLowEnergyProductionSpeed",INI::parseReal, NULL, offsetof( GlobalData, m_MinLowEnergyProductionSpeed ) }, - { "MaxLowEnergyProductionSpeed",INI::parseReal, NULL, offsetof( GlobalData, m_MaxLowEnergyProductionSpeed ) }, - { "LowEnergyPenaltyModifier", INI::parseReal, NULL, offsetof( GlobalData, m_LowEnergyPenaltyModifier ) }, - { "MultipleFactory", INI::parseReal, NULL, offsetof( GlobalData, m_MultipleFactory ) }, - { "RefundPercent", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_RefundPercent ) }, - - { "CommandCenterHealRange", INI::parseReal, NULL, offsetof( GlobalData, m_commandCenterHealRange ) }, - { "CommandCenterHealAmount", INI::parseReal, NULL, offsetof( GlobalData, m_commandCenterHealAmount ) }, - - { "StandardMinefieldDensity", INI::parseReal, NULL, offsetof( GlobalData, m_standardMinefieldDensity ) }, - { "StandardMinefieldDistance", INI::parseReal, NULL, offsetof( GlobalData, m_standardMinefieldDistance ) }, - - { "MaxLineBuildObjects", INI::parseInt, NULL, offsetof( GlobalData, m_maxLineBuildObjects ) }, - { "MaxTunnelCapacity", INI::parseInt, NULL, offsetof( GlobalData, m_maxTunnelCapacity ) }, - - { "MaxParticleCount", INI::parseInt, NULL, offsetof( GlobalData, m_maxParticleCount ) }, - { "MaxFieldParticleCount", INI::parseInt, NULL, offsetof( GlobalData, m_maxFieldParticleCount ) }, - { "HorizontalScrollSpeedFactor",INI::parseReal, NULL, offsetof( GlobalData, m_horizontalScrollSpeedFactor ) }, - { "VerticalScrollSpeedFactor", INI::parseReal, NULL, offsetof( GlobalData, m_verticalScrollSpeedFactor ) }, - { "ScrollAmountCutoff", INI::parseReal, NULL, offsetof( GlobalData, m_scrollAmountCutoff ) }, - { "CameraAdjustSpeed", INI::parseReal, NULL, offsetof( GlobalData, m_cameraAdjustSpeed ) }, - { "EnforceMaxCameraHeight", INI::parseBool, NULL, offsetof( GlobalData, m_enforceMaxCameraHeight ) }, - { "KeyboardScrollSpeedFactor", INI::parseReal, NULL, offsetof( GlobalData, m_keyboardScrollFactor ) }, - { "KeyboardDefaultScrollSpeedFactor", INI::parseReal, NULL, offsetof( GlobalData, m_keyboardDefaultScrollFactor ) }, - { "MovementPenaltyDamageState", INI::parseIndexList, TheBodyDamageTypeNames, offsetof( GlobalData, m_movementPenaltyDamageState ) }, - -// you cannot set this; it always has a value of 100%. -//{ "HealthBonus_Regular", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_healthBonus[LEVEL_REGULAR]) }, - { "HealthBonus_Veteran", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_healthBonus[LEVEL_VETERAN]) }, - { "HealthBonus_Elite", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_healthBonus[LEVEL_ELITE]) }, - { "HealthBonus_Heroic", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_healthBonus[LEVEL_HEROIC]) }, - - { "HumanSoloPlayerHealthBonus_Easy", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_HUMAN][DIFFICULTY_EASY] ) }, - { "HumanSoloPlayerHealthBonus_Normal", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_HUMAN][DIFFICULTY_NORMAL] ) }, - { "HumanSoloPlayerHealthBonus_Hard", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_HUMAN][DIFFICULTY_HARD] ) }, - - { "AISoloPlayerHealthBonus_Easy", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_COMPUTER][DIFFICULTY_EASY] ) }, - { "AISoloPlayerHealthBonus_Normal", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_COMPUTER][DIFFICULTY_NORMAL] ) }, - { "AISoloPlayerHealthBonus_Hard", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_COMPUTER][DIFFICULTY_HARD] ) }, - - { "WeaponBonus", WeaponBonusSet::parseWeaponBonusSetPtr, NULL, offsetof( GlobalData, m_weaponBonusSet ) }, - - { "DefaultStructureRubbleHeight", INI::parseReal, NULL, offsetof( GlobalData, m_defaultStructureRubbleHeight ) }, - - { "FixedSeed", INI::parseInt, NULL, offsetof( GlobalData, m_fixedSeed ) }, - - { "ShellMapName", INI::parseAsciiString,NULL, offsetof( GlobalData, m_shellMapName ) }, - { "ShellMapOn", INI::parseBool, NULL, offsetof( GlobalData, m_shellMapOn ) }, - { "PlayIntro", INI::parseBool, NULL, offsetof( GlobalData, m_playIntro ) }, - - { "FirewallBehavior", INI::parseInt, NULL, offsetof( GlobalData, m_firewallBehavior ) }, - { "FirewallPortOverride", INI::parseInt, NULL, offsetof( GlobalData, m_firewallPortOverride ) }, - { "FirewallPortAllocationDelta",INI::parseInt, NULL, offsetof( GlobalData, m_firewallPortAllocationDelta) }, - - { "GroupSelectMinSelectSize", INI::parseInt, NULL, offsetof( GlobalData, m_groupSelectMinSelectSize ) }, - { "GroupSelectVolumeBase", INI::parseReal, NULL, offsetof( GlobalData, m_groupSelectVolumeBase ) }, - { "GroupSelectVolumeIncrement", INI::parseReal, NULL, offsetof( GlobalData, m_groupSelectVolumeIncrement ) }, - { "MaxUnitSelectSounds", INI::parseInt, NULL, offsetof( GlobalData, m_maxUnitSelectSounds ) }, - - { "SelectionFlashSaturationFactor", INI::parseReal, NULL, offsetof( GlobalData, m_selectionFlashSaturationFactor ) }, - { "SelectionFlashHouseColor", INI::parseBool, NULL, offsetof( GlobalData, m_selectionFlashHouseColor ) }, - - { "CameraAudibleRadius", INI::parseReal, NULL, offsetof( GlobalData, m_cameraAudibleRadius ) }, - { "GroupMoveClickToGatherAreaFactor", INI::parseReal, NULL, offsetof( GlobalData, m_groupMoveClickToGatherFactor ) }, - { "ShakeSubtleIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeSubtleIntensity ) }, - { "ShakeNormalIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeNormalIntensity ) }, - { "ShakeStrongIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeStrongIntensity ) }, - { "ShakeSevereIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeSevereIntensity ) }, - { "ShakeCineExtremeIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeCineExtremeIntensity ) }, - { "ShakeCineInsaneIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeCineInsaneIntensity ) }, - { "MaxShakeIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_maxShakeIntensity ) }, - { "MaxShakeRange", INI::parseReal, NULL, offsetof( GlobalData, m_maxShakeRange) }, - { "SellPercentage", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_sellPercentage ) }, - { "BaseRegenHealthPercentPerSecond", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_baseRegenHealthPercentPerSecond ) }, - { "BaseRegenDelay", INI::parseDurationUnsignedInt, NULL,offsetof( GlobalData, m_baseRegenDelay ) }, - -#ifdef ALLOW_SURRENDER - { "PrisonBountyMultiplier", INI::parseReal, NULL, offsetof( GlobalData, m_prisonBountyMultiplier ) }, - { "PrisonBountyTextColor", INI::parseColorInt, NULL, offsetof( GlobalData, m_prisonBountyTextColor ) }, -#endif - - { "SpecialPowerViewObject", INI::parseAsciiString, NULL, offsetof( GlobalData, m_specialPowerViewObjectName ) }, - - { "StandardPublicBone", INI::parseAsciiStringVectorAppend, NULL, offsetof(GlobalData, m_standardPublicBones) }, - { "ShowMetrics", INI::parseBool, NULL, offsetof( GlobalData, m_showMetrics ) }, - { "DefaultStartingCash", Money::parseMoneyAmount, NULL, offsetof( GlobalData, m_defaultStartingCash ) }, - -// NOTE: m_doubleClickTimeMS is still in use, but we disallow setting it from the GameData.ini file. It is now set in the constructor according to the windows parameter. -// { "DoubleClickTimeMS", INI::parseUnsignedInt, NULL, offsetof( GlobalData, m_doubleClickTimeMS ) }, - - { "ShroudColor", INI::parseRGBColor, NULL, offsetof( GlobalData, m_shroudColor) }, - { "ClearAlpha", INI::parseUnsignedByte, NULL, offsetof( GlobalData, m_clearAlpha) }, - { "FogAlpha", INI::parseUnsignedByte, NULL, offsetof( GlobalData, m_fogAlpha) }, - { "ShroudAlpha", INI::parseUnsignedByte, NULL, offsetof( GlobalData, m_shroudAlpha) }, - - { "HotKeyTextColor", INI::parseColorInt, NULL, offsetof( GlobalData, m_hotKeyTextColor ) }, - - { "PowerBarBase", INI::parseInt, NULL, offsetof( GlobalData, m_powerBarBase) }, - { "PowerBarIntervals", INI::parseReal, NULL, offsetof( GlobalData, m_powerBarIntervals) }, - { "PowerBarYellowRange", INI::parseInt, NULL, offsetof( GlobalData, m_powerBarYellowRange) }, - { "UnlookPersistDuration", INI::parseDurationUnsignedInt, NULL, offsetof( GlobalData, m_unlookPersistDuration) }, - - { "NetworkFPSHistoryLength", INI::parseInt, NULL, offsetof(GlobalData, m_networkFPSHistoryLength) }, - { "NetworkLatencyHistoryLength", INI::parseInt, NULL, offsetof(GlobalData, m_networkLatencyHistoryLength) }, - { "NetworkRunAheadMetricsTime", INI::parseInt, NULL, offsetof(GlobalData, m_networkRunAheadMetricsTime) }, - { "NetworkCushionHistoryLength", INI::parseInt, NULL, offsetof(GlobalData, m_networkCushionHistoryLength) }, - { "NetworkRunAheadSlack", INI::parseInt, NULL, offsetof(GlobalData, m_networkRunAheadSlack) }, - { "NetworkKeepAliveDelay", INI::parseInt, NULL, offsetof(GlobalData, m_networkKeepAliveDelay) }, - { "NetworkDisconnectTime", INI::parseInt, NULL, offsetof(GlobalData, m_networkDisconnectTime) }, - { "NetworkPlayerTimeoutTime", INI::parseInt, NULL, offsetof(GlobalData, m_networkPlayerTimeoutTime) }, - { "NetworkDisconnectScreenNotifyTime", INI::parseInt, NULL, offsetof(GlobalData, m_networkDisconnectScreenNotifyTime) }, - - { "KeyboardCameraRotateSpeed", INI::parseReal, NULL, offsetof( GlobalData, m_keyboardCameraRotateSpeed ) }, - { "PlayStats", INI::parseInt, NULL, offsetof( GlobalData, m_playStats ) }, - -#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) - { "DisableCameraFade", INI::parseBool, NULL, offsetof( GlobalData, m_disableCameraFade ) }, - { "DisableScriptedInputDisabling", INI::parseBool, NULL, offsetof( GlobalData, m_disableScriptedInputDisabling ) }, - { "DisableMilitaryCaption", INI::parseBool, NULL, offsetof( GlobalData, m_disableMilitaryCaption ) }, - { "BenchmarkTimer", INI::parseInt, NULL, offsetof( GlobalData, m_benchmarkTimer ) }, - { "CheckMemoryLeaks", INI::parseBool, NULL, offsetof(GlobalData, m_checkForLeaks) }, - { "Wireframe", INI::parseBool, NULL, offsetof( GlobalData, m_wireframe ) }, - { "StateMachineDebug", INI::parseBool, NULL, offsetof( GlobalData, m_stateMachineDebug ) }, - { "UseCameraConstraints", INI::parseBool, NULL, offsetof( GlobalData, m_useCameraConstraints ) }, - { "ShroudOn", INI::parseBool, NULL, offsetof( GlobalData, m_shroudOn ) }, - { "FogOfWarOn", INI::parseBool, NULL, offsetof( GlobalData, m_fogOfWarOn ) }, - { "ShowCollisionExtents", INI::parseBool, NULL, offsetof( GlobalData, m_showCollisionExtents ) }, - { "ShowAudioLocations", INI::parseBool, NULL, offsetof( GlobalData, m_showAudioLocations ) }, - { "DebugProjectileTileWidth", INI::parseReal, NULL, offsetof( GlobalData, m_debugProjectileTileWidth) }, - { "DebugProjectileTileDuration",INI::parseInt, NULL, offsetof( GlobalData, m_debugProjectileTileDuration) }, - { "DebugProjectileTileColor", INI::parseRGBColor, NULL, offsetof( GlobalData, m_debugProjectileTileColor) }, - { "DebugVisibilityTileCount", INI::parseInt, NULL, offsetof( GlobalData, m_debugVisibilityTileCount) }, - { "DebugVisibilityTileWidth", INI::parseReal, NULL, offsetof( GlobalData, m_debugVisibilityTileWidth) }, - { "DebugVisibilityTileDuration",INI::parseInt, NULL, offsetof( GlobalData, m_debugVisibilityTileDuration) }, - { "DebugVisibilityTileTargettableColor",INI::parseRGBColor, NULL, offsetof( GlobalData, m_debugVisibilityTargettableColor) }, - { "DebugVisibilityTileDeshroudColor", INI::parseRGBColor, NULL, offsetof( GlobalData, m_debugVisibilityDeshroudColor) }, - { "DebugVisibilityTileGapColor", INI::parseRGBColor, NULL, offsetof( GlobalData, m_debugVisibilityGapColor) }, - { "DebugThreatMapTileDuration", INI::parseInt, NULL, offsetof( GlobalData, m_debugThreatMapTileDuration) }, - { "MaxDebugThreatMapValue", INI::parseUnsignedInt, NULL, offsetof( GlobalData, m_maxDebugThreat) }, - { "DebugCashValueMapTileDuration", INI::parseInt, NULL, offsetof( GlobalData, m_debugCashValueMapTileDuration) }, - { "MaxDebugCashValueMapValue", INI::parseUnsignedInt, NULL, offsetof( GlobalData, m_maxDebugValue) }, - { "VTune", INI::parseBool, NULL, offsetof( GlobalData, m_vTune ) }, - { "SaveStats", INI::parseBool, NULL, offsetof( GlobalData, m_saveStats ) }, - { "UseLocalMOTD", INI::parseBool, NULL, offsetof( GlobalData, m_useLocalMOTD ) }, - { "BaseStatsDir", INI::parseAsciiString,NULL, offsetof( GlobalData, m_baseStatsDir ) }, - { "LocalMOTDPath", INI::parseAsciiString,NULL, offsetof( GlobalData, m_MOTDPath ) }, - { "ExtraLogging", INI::parseBool, NULL, offsetof( GlobalData, m_extraLogging ) }, -#endif - - { "UseVanillaDiagonalMoveSpeed", INI::parseBool, NULL, offsetof(GlobalData, m_useOldMoveSpeed) }, - { "TintStatus", GlobalData::parseTintStatusType, NULL, offsetof(GlobalData, m_colorTintTypes) }, - { NULL, NULL, NULL, 0 } // keep this last - -}; - - - -// Helper function -/*static*/ void GlobalData::setColorTintEntry(DrawableColorTint* arr, int index, RGBColor color, RGBColor colorInfantry, UnsignedInt attackFrames, UnsignedInt decayFrames) -{ - arr[index].color = color; - arr[index].colorInfantry = colorInfantry; - arr[index].attackFrames = attackFrames; - arr[index].decayFrames = decayFrames; -} - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -GlobalData::GlobalData() -{ - Int i, j; - - // - // we have now instanced a global data instance, if theOriginal is NULL, this is - // *the* very first instance and it shall be recorded. This way, when we load - // overrides of the global data, we can revert to the common, original data - // in m_theOriginal - // - if( m_theOriginal == NULL ) - m_theOriginal = this; - m_next = NULL; - -#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) || defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE) - m_specialPowerUsesDelay = TRUE; -#endif - m_TiVOFastMode = FALSE; - -#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) - m_wireframe = 0; - m_stateMachineDebug = FALSE; - m_useCameraConstraints = TRUE; - m_shroudOn = TRUE; - m_fogOfWarOn = FALSE; - m_jabberOn = FALSE; - m_munkeeOn = FALSE; - m_showCollisionExtents = FALSE; - m_showAudioLocations = FALSE; - m_debugCamera = FALSE; - m_debugVisibility = FALSE; - m_debugVisibilityTileCount = 32; // default to 32. - m_debugVisibilityTileDuration = LOGICFRAMES_PER_SECOND; - m_debugProjectilePath = FALSE; - m_debugProjectileTileWidth = 10; - m_debugProjectileTileDuration = LOGICFRAMES_PER_SECOND; // Changed By Sadullah Nader - m_debugThreatMap = FALSE; - m_maxDebugThreat = 5000; - m_debugThreatMapTileDuration = LOGICFRAMES_PER_SECOND; // Changed By Sadullah Nader - m_debugCashValueMap = FALSE; - m_maxDebugValue = 10000; - m_debugCashValueMapTileDuration = LOGICFRAMES_PER_SECOND; // Changed By Sadullah Nader - m_vTune = false; - m_checkForLeaks = TRUE; - m_benchmarkTimer = -1; - - - m_allowUnselectableSelection = FALSE; - m_disableCameraFade = false; - m_disableScriptedInputDisabling = false; - m_disableMilitaryCaption = false; - m_latencyAverage = 0; - m_latencyAmplitude = 0; - m_latencyPeriod = 0; - m_latencyNoise = 0; - m_packetLoss = 0; - m_saveStats = FALSE; - m_saveAllStats = FALSE; - m_useLocalMOTD = FALSE; - m_baseStatsDir = ".\\"; - m_MOTDPath = "MOTD.txt"; - m_extraLogging = FALSE; -#endif - -#ifdef DEBUG_CRASHING - m_debugIgnoreAsserts = FALSE; -#endif - -#ifdef DEBUG_STACKTRACE - m_debugIgnoreStackTrace = FALSE; -#endif - - m_playStats = -1; - m_incrementalAGPBuf = FALSE; - m_mapName.clear(); - m_moveHintName.clear(); - m_useTrees = 0; - m_useTreeSway = TRUE; - m_useDrawModuleLOD = FALSE; - m_useHeatEffects = TRUE; - m_useFpsLimit = FALSE; - m_dumpAssetUsage = FALSE; - m_framesPerSecondLimit = 0; - m_chipSetType = 0; - m_windowed = 0; - m_xResolution = 800; - m_yResolution = 600; - m_maxShellScreens = 0; - m_useCloudMap = FALSE; - m_use3WayTerrainBlends = 1; - m_useLightMap = FALSE; - m_bilinearTerrainTex = FALSE; - m_trilinearTerrainTex = FALSE; - m_multiPassTerrain = FALSE; - m_adjustCliffTextures = FALSE; - m_stretchTerrain = FALSE; - m_useHalfHeightMap = FALSE; - m_terrainLOD = TERRAIN_LOD_AUTOMATIC; - m_terrainLODTargetTimeMS = 0; - m_enableDynamicLOD = TRUE; - m_enableStaticLOD = TRUE; - m_rightMouseAlwaysScrolls = FALSE; - m_useWaterPlane = FALSE; - m_useCloudPlane = FALSE; - m_downwindAngle = ( -0.785f );//Northeast! - m_useShadowVolumes = FALSE; - m_useShadowDecals = FALSE; - m_textureReductionFactor = -1; - m_enableBehindBuildingMarkers = TRUE; - m_scriptDebug = FALSE; - m_particleEdit = FALSE; - m_displayDebug = FALSE; - m_winCursors = TRUE; - m_constantDebugUpdate = FALSE; - m_showTeamDot = FALSE; - m_fixedSeed = -1; // disabled - m_horizontalScrollSpeedFactor = 1.0; - m_verticalScrollSpeedFactor = 1.0; - - m_waterPositionX = 0.0f; - m_waterPositionY = 0.0f; - m_waterPositionZ = 0.0f; - m_waterExtentX = 0.0f; - m_waterExtentY = 0.0f; - m_waterType = 0; - m_featherWater = FALSE; - m_showSoftWaterEdge = TRUE; //display soft water edge - m_usingWaterTrackEditor = FALSE; - m_isWorldBuilder = FALSE; - - m_showMetrics = false; - - for( i = 0; i < MAX_WATER_GRID_SETTINGS; i++ ) - { - - m_vertexWaterHeightClampLow[ i ] = 0.0f; - m_vertexWaterHeightClampHi[ i ] = 0.0f; - m_vertexWaterAngle[ i ] = 0.0f; - m_vertexWaterXPosition[ i ] = 0.0f; - m_vertexWaterYPosition[ i ] = 0.0f; - m_vertexWaterZPosition[ i ] = 0.0f; - m_vertexWaterXGridCells[ i ] = 0; - m_vertexWaterYGridCells[ i ] = 0; - m_vertexWaterGridSize[ i ] = 0.0f; - m_vertexWaterAttenuationA[ i ] = 0.0f; - m_vertexWaterAttenuationB[ i ] = 0.0f; - m_vertexWaterAttenuationC[ i ] = 0.0f; - m_vertexWaterAttenuationRange[ i ] = 0.0f; - //Added By Sadullah Nader - //Initializations missing and needed - m_vertexWaterAvailableMaps[i].clear(); - } // end for i - - m_skyBoxPositionZ = 0.0f; - m_drawSkyBox = FALSE; - m_skyBoxScale = 4.5f; - - m_historicDamageLimit = 0; - m_maxTerrainTracks = 0; - - m_levelGainAnimationDisplayTimeInSeconds = 0.0f; - m_levelGainAnimationZRisePerSecond = 0.0f; - - m_getHealedAnimationDisplayTimeInSeconds = 0.0f; - m_getHealedAnimationZRisePerSecond = 0.0f; - - m_maxTankTrackEdges=100; - m_maxTankTrackOpaqueEdges=25; - m_maxTankTrackFadeDelay=300000; - - m_timeOfDay = TIME_OF_DAY_AFTERNOON; - m_weather = WEATHER_NORMAL; - m_makeTrackMarks = FALSE; - m_hideGarrisonFlags = FALSE; - m_forceModelsToFollowTimeOfDay = true; - m_forceModelsToFollowWeather = true; - - m_partitionCellSize = 0.0f; - m_ammoPipScaleFactor = 1.0f; - m_containerPipScaleFactor = 1.0f; - m_ammoPipWorldOffset.zero(); - m_containerPipWorldOffset.zero(); - m_ammoPipScreenOffset.x = m_ammoPipScreenOffset.y = 0; - m_containerPipScreenOffset.x = m_containerPipScreenOffset.y = 0; - - for (i=0; iopenFile(buffer, File::READ | File::BINARY); - if (fp != NULL) { - unsigned char crcBlock[blockSize]; - Int amtRead = 0; - while ( (amtRead=fp->read(crcBlock, blockSize)) > 0 ) - { - exeCRC.computeCRC(crcBlock, amtRead); - } - fp->close(); - fp = NULL; - } - if (TheVersion) - { - UnsignedInt version = TheVersion->getVersionNumber(); - exeCRC.computeCRC( &version, sizeof(UnsignedInt) ); - } - // Add in MP scripts to the EXE CRC, since the game will go out of sync if they change - fp = TheFileSystem->openFile("Data\\Scripts\\SkirmishScripts.scb", File::READ | File::BINARY); - if (fp != NULL) { - unsigned char crcBlock[blockSize]; - Int amtRead = 0; - while ( (amtRead=fp->read(crcBlock, blockSize)) > 0 ) - { - exeCRC.computeCRC(crcBlock, amtRead); - } - fp->close(); - fp = NULL; - } - fp = TheFileSystem->openFile("Data\\Scripts\\MultiplayerScripts.scb", File::READ | File::BINARY); - if (fp != NULL) { - unsigned char crcBlock[blockSize]; - Int amtRead = 0; - while ( (amtRead=fp->read(crcBlock, blockSize)) > 0 ) - { - exeCRC.computeCRC(crcBlock, amtRead); - } - fp->close(); - fp = NULL; - } - - m_exeCRC = exeCRC.get(); - DEBUG_LOG(("EXE CRC: 0x%8.8X\n", m_exeCRC)); - - m_movementPenaltyDamageState = BODY_REALLYDAMAGED; - - m_shouldUpdateTGAToDDS = FALSE; - - // Default DoubleClickTime to System double click time. - m_doubleClickTimeMS = GetDoubleClickTime(); // Note: This is actual MS, not frames. - -#ifdef DUMP_PERF_STATS - m_dumpPerformanceStatistics = FALSE; - m_dumpStatsAtInterval = FALSE; - m_statsInterval = 30; -#endif - - m_forceBenchmark = FALSE; ///> GLOBAL_DATA: m_colorTintTypes[%d] = {(%f, %f, %f), (%f, %f, %f), %d, %d}\n", - i, tc.color.red, tc.color.green, tc.color.blue, tc.colorInfantry.red, tc.colorInfantry.green, tc.colorInfantry.blue, - tc.attackFrames, tc.decayFrames)); - } - // ------------------------------------------------------------------------------ - - -} // end GlobalData - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -GlobalData::~GlobalData( void ) -{ - DEBUG_ASSERTCRASH( TheWritableGlobalData->m_next == NULL, ("~GlobalData: theOriginal is not original\n") ); - - if (m_weaponBonusSet) - m_weaponBonusSet->deleteInstance(); - - if( m_theOriginal == this ) { - m_theOriginal = NULL; - TheWritableGlobalData = NULL; - } - -} // end ~GlobalData - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Bool GlobalData::setTimeOfDay( TimeOfDay tod ) -{ - if( tod >= TIME_OF_DAY_COUNT || tod < TIME_OF_DAY_FIRST ) - { - return FALSE; - } - - m_timeOfDay = tod; - for (Int i=0; im_next = TheWritableGlobalData; - - // set this new instance as the 'most current override' where we will access all data from - TheWritableGlobalData = override; - - return override; - -} // end newOveride - -//------------------------------------------------------------------------------------------------- -void GlobalData::init( void ) -{ - // nothing -} - -//------------------------------------------------------------------------------------------------- -/** Reset, remove any override data instances and return to just the initial one - */ -//------------------------------------------------------------------------------------------------- -void GlobalData::reset( void ) -{ - DEBUG_ASSERTCRASH(this == TheWritableGlobalData, ("calling reset on wrong GlobalData")); - - // - // delete any data instances that were loaded as an override and set the original - // global data instance as the singleton TheWritableGlobalData once again - // - while (TheWritableGlobalData != GlobalData::m_theOriginal) - { - - // get next instance - GlobalData* next = TheWritableGlobalData->m_next; - - // delete the head of the global data list (the latest override) - delete TheWritableGlobalData; - - // set next as top - TheWritableGlobalData = next; - - } // end while - - // - // we now have the one single global data in TheWritableGlobalData singleton, lets sanity check - // some of all that - // - DEBUG_ASSERTCRASH( TheWritableGlobalData->m_next == NULL, ("ResetGlobalData: theOriginal is not original\n") ); - DEBUG_ASSERTCRASH( TheWritableGlobalData == GlobalData::m_theOriginal, ("ResetGlobalData: oops\n") ); - -} // end ResetGlobalData - -//------------------------------------------------------------------------------------------------- -/** Parse GameData entry */ -//------------------------------------------------------------------------------------------------- -void GlobalData::parseGameDataDefinition( INI* ini ) -{ - if( TheWritableGlobalData && ini->getLoadType() != INI_LOAD_MULTIFILE) - { - - // - // if the type of loading we're doing creates override data, we need to - // be loading into a new override item - // - if( ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES ) - TheWritableGlobalData->newOverride(); - - } // end if - else if (!TheWritableGlobalData) - { - - // we don't have any global data instance at all yet, create one - TheWritableGlobalData = NEW GlobalData; - - } // end else - // If we're multifile, then continue loading stuff into the Global Data as normal. - - // parse the ini weapon definition - ini->initFromINI( TheWritableGlobalData, s_GlobalDataFieldParseTable ); - - - // override INI values with user preferences - OptionPreferences optionPref; - TheWritableGlobalData->m_useAlternateMouse = optionPref.getAlternateMouseModeEnabled(); - TheWritableGlobalData->m_clientRetaliationModeEnabled = optionPref.getRetaliationModeEnabled(); - TheWritableGlobalData->m_doubleClickAttackMove = optionPref.getDoubleClickAttackMoveEnabled(); - TheWritableGlobalData->m_keyboardScrollFactor = optionPref.getScrollFactor(); - TheWritableGlobalData->m_defaultIP = optionPref.getLANIPAddress(); - TheWritableGlobalData->m_firewallSendDelay = optionPref.getSendDelay(); - TheWritableGlobalData->m_firewallBehavior = optionPref.getFirewallBehavior(); - TheWritableGlobalData->m_firewallPortAllocationDelta = optionPref.getFirewallPortAllocationDelta(); - TheWritableGlobalData->m_firewallPortOverride = optionPref.getFirewallPortOverride(); - - TheWritableGlobalData->m_saveCameraInReplay = optionPref.saveCameraInReplays(); - TheWritableGlobalData->m_useCameraInReplay = optionPref.useCameraInReplays(); - - Int val=optionPref.getGammaValue(); - //generate a value between 0.6 and 2.0. - if (val < 50) - { //darker gamma - if (val <= 0) - TheWritableGlobalData->m_displayGamma = 0.6f; - else - TheWritableGlobalData->m_displayGamma=1.0f-(0.4f) * (Real)(50-val)/50.0f; - } - else - if (val > 50) - TheWritableGlobalData->m_displayGamma=1.0f+(1.0f) * (Real)(val-50)/50.0f; - - Int xres,yres; - optionPref.getResolution(&xres, &yres); - - TheWritableGlobalData->m_xResolution = xres; - TheWritableGlobalData->m_yResolution = yres; -} - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: GlobalData.cpp /////////////////////////////////////////////////////////////////////////// +// The GameLogicData object +// Author: trolfs, Michael Booth, Colin Day, April 2001 +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//#pragma once + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine + +#define DEFINE_TERRAIN_LOD_NAMES +#define DEFINE_TIME_OF_DAY_NAMES +#define DEFINE_WEATHER_NAMES +#define DEFINE_BODYDAMAGETYPE_NAMES +#define DEFINE_PANNING_NAMES + +#include "Common/crc.h" +#include "Common/file.h" +#include "Common/FileSystem.h" +#include "Common/GameAudio.h" +#include "Common/INI.h" +#include "Common/Registry.h" +#include "Common/UserPreferences.h" +#include "Common/version.h" + +#include "GameLogic/AI.h" +#include "GameLogic/Weapon.h" +#include "GameLogic/Module/BodyModule.h" + +#include "GameClient/Color.h" +#include "GameClient/TerrainVisual.h" +#include "GameClient/TintStatus.h" + +#include "GameNetwork/FirewallHelper.h" + +// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// +GlobalData* TheWritableGlobalData = NULL; ///< The global data singleton + +//------------------------------------------------------------------------------------------------- +GlobalData* GlobalData::m_theOriginal = NULL; + + + +//------------------------------------------------------------------------------------------------- +/*static*/ void GlobalData::parseTintStatusType(INI* ini, void* instance, void* store, const void* userData) +{ + TintStatus tintType = (TintStatus)INI::scanIndexList(ini->getNextToken(), TintStatusFlags::getBitNames()); + + DrawableColorTint* colorTintTypes = (DrawableColorTint*)(store); + DrawableColorTint* tintEntry = &colorTintTypes[tintType]; + + INI::parseRGBColorReal(ini, instance, &tintEntry->color, NULL); + INI::parseRGBColorReal(ini, instance, &tintEntry->colorInfantry, NULL); + + INI::parseUnsignedInt(ini, instance, &tintEntry->attackFrames, NULL); + INI::parseUnsignedInt(ini, instance, &tintEntry->decayFrames, NULL); +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +/*static*/ const FieldParse GlobalData::s_GlobalDataFieldParseTable[] = +{ + { "Windowed", INI::parseBool, NULL, offsetof( GlobalData, m_windowed ) }, + { "XResolution", INI::parseInt, NULL, offsetof( GlobalData, m_xResolution ) }, + { "YResolution", INI::parseInt, NULL, offsetof( GlobalData, m_yResolution ) }, + { "MapName", INI::parseAsciiString,NULL, offsetof( GlobalData, m_mapName ) }, + { "MoveHintName", INI::parseAsciiString,NULL, offsetof( GlobalData, m_moveHintName ) }, + { "UseTrees", INI::parseBool, NULL, offsetof( GlobalData, m_useTrees ) }, + { "UseFPSLimit", INI::parseBool, NULL, offsetof( GlobalData, m_useFpsLimit ) }, + { "DumpAssetUsage", INI::parseBool, NULL, offsetof( GlobalData, m_dumpAssetUsage ) }, + { "FramesPerSecondLimit", INI::parseInt, NULL, offsetof( GlobalData, m_framesPerSecondLimit ) }, + { "ChipsetType", INI::parseInt, NULL, offsetof( GlobalData, m_chipSetType ) }, + { "MaxShellScreens", INI::parseInt, NULL, offsetof( GlobalData, m_maxShellScreens ) }, + { "UseCloudMap", INI::parseBool, NULL, offsetof( GlobalData, m_useCloudMap ) }, + { "UseLightMap", INI::parseBool, NULL, offsetof( GlobalData, m_useLightMap ) }, + { "BilinearTerrainTex", INI::parseBool, NULL, offsetof( GlobalData, m_bilinearTerrainTex ) }, + { "TrilinearTerrainTex", INI::parseBool, NULL, offsetof( GlobalData, m_trilinearTerrainTex ) }, + { "MultiPassTerrain", INI::parseBool, NULL, offsetof( GlobalData, m_multiPassTerrain ) }, + { "AdjustCliffTextures", INI::parseBool, NULL, offsetof( GlobalData, m_adjustCliffTextures ) }, + { "Use3WayTerrainBlends", INI::parseInt, NULL, offsetof( GlobalData, m_use3WayTerrainBlends ) }, + { "StretchTerrain", INI::parseBool, NULL, offsetof( GlobalData, m_stretchTerrain ) }, + { "UseHalfHeightMap", INI::parseBool, NULL, offsetof( GlobalData, m_useHalfHeightMap ) }, + + + { "DrawEntireTerrain", INI::parseBool, NULL, offsetof( GlobalData, m_drawEntireTerrain ) }, + { "TerrainLOD", INI::parseIndexList, TerrainLODNames, offsetof( GlobalData, m_terrainLOD ) }, + { "TerrainLODTargetTimeMS", INI::parseInt, NULL, offsetof( GlobalData, m_terrainLODTargetTimeMS ) }, + { "RightMouseAlwaysScrolls", INI::parseBool, NULL, offsetof( GlobalData, m_rightMouseAlwaysScrolls ) }, + { "UseWaterPlane", INI::parseBool, NULL, offsetof( GlobalData, m_useWaterPlane ) }, + { "UseCloudPlane", INI::parseBool, NULL, offsetof( GlobalData, m_useCloudPlane ) }, + { "DownwindAngle", INI::parseReal, NULL, offsetof( GlobalData, m_downwindAngle ) }, + { "UseShadowVolumes", INI::parseBool, NULL, offsetof( GlobalData, m_useShadowVolumes ) }, + { "UseShadowDecals", INI::parseBool, NULL, offsetof( GlobalData, m_useShadowDecals ) }, + { "TextureReductionFactor", INI::parseInt, NULL, offsetof( GlobalData, m_textureReductionFactor ) }, + { "UseBehindBuildingMarker", INI::parseBool, NULL, offsetof( GlobalData, m_enableBehindBuildingMarkers ) }, + { "WaterPositionX", INI::parseReal, NULL, offsetof( GlobalData, m_waterPositionX ) }, + { "WaterPositionY", INI::parseReal, NULL, offsetof( GlobalData, m_waterPositionY ) }, + { "WaterPositionZ", INI::parseReal, NULL, offsetof( GlobalData, m_waterPositionZ ) }, + { "WaterExtentX", INI::parseReal, NULL, offsetof( GlobalData, m_waterExtentX ) }, + { "WaterExtentY", INI::parseReal, NULL, offsetof( GlobalData, m_waterExtentY ) }, + { "WaterType", INI::parseInt, NULL, offsetof( GlobalData, m_waterType ) }, + { "FeatherWater", INI::parseInt, NULL, offsetof( GlobalData, m_featherWater ) }, + { "ShowSoftWaterEdge", INI::parseBool, NULL, offsetof( GlobalData, m_showSoftWaterEdge ) }, + + // nasty ick, we need to save this data with a map and not hard code INI values + { "VertexWaterAvailableMaps1", INI::parseAsciiString, NULL, offsetof( GlobalData, m_vertexWaterAvailableMaps[ 0 ] ) }, + { "VertexWaterHeightClampLow1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampLow[ 0 ] ) }, + { "VertexWaterHeightClampHi1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampHi[ 0 ] ) }, + { "VertexWaterAngle1", INI::parseAngleReal, NULL, offsetof( GlobalData, m_vertexWaterAngle[ 0 ] ) }, + { "VertexWaterXPosition1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterXPosition[ 0 ] ) }, + { "VertexWaterYPosition1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterYPosition[ 0 ] ) }, + { "VertexWaterZPosition1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterZPosition[ 0 ] ) }, + { "VertexWaterXGridCells1", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterXGridCells[ 0 ] ) }, + { "VertexWaterYGridCells1", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterYGridCells[ 0 ] ) }, + { "VertexWaterGridSize1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterGridSize[ 0 ] ) }, + { "VertexWaterAttenuationA1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationA[ 0 ] ) }, + { "VertexWaterAttenuationB1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationB[ 0 ] ) }, + { "VertexWaterAttenuationC1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationC[ 0 ] ) }, + { "VertexWaterAttenuationRange1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationRange[ 0 ] ) }, + + // nasty ick, we need to save this data with a map and not hard code INI values + { "VertexWaterAvailableMaps2", INI::parseAsciiString, NULL, offsetof( GlobalData, m_vertexWaterAvailableMaps[ 1 ] ) }, + { "VertexWaterHeightClampLow2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampLow[ 1 ] ) }, + { "VertexWaterHeightClampHi2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampHi[ 1 ] ) }, + { "VertexWaterAngle2", INI::parseAngleReal, NULL, offsetof( GlobalData, m_vertexWaterAngle[ 1 ] ) }, + { "VertexWaterXPosition2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterXPosition[ 1 ] ) }, + { "VertexWaterYPosition2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterYPosition[ 1 ] ) }, + { "VertexWaterZPosition2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterZPosition[ 1 ] ) }, + { "VertexWaterXGridCells2", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterXGridCells[ 1 ] ) }, + { "VertexWaterYGridCells2", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterYGridCells[ 1 ] ) }, + { "VertexWaterGridSize2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterGridSize[ 1 ] ) }, + { "VertexWaterAttenuationA2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationA[ 1 ] ) }, + { "VertexWaterAttenuationB2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationB[ 1 ] ) }, + { "VertexWaterAttenuationC2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationC[ 1 ] ) }, + { "VertexWaterAttenuationRange2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationRange[ 1 ] ) }, + + // nasty ick, we need to save this data with a map and not hard code INI values + { "VertexWaterAvailableMaps3", INI::parseAsciiString, NULL, offsetof( GlobalData, m_vertexWaterAvailableMaps[ 2 ] ) }, + { "VertexWaterHeightClampLow3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampLow[ 2 ] ) }, + { "VertexWaterHeightClampHi3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampHi[ 2 ] ) }, + { "VertexWaterAngle3", INI::parseAngleReal, NULL, offsetof( GlobalData, m_vertexWaterAngle[ 2 ] ) }, + { "VertexWaterXPosition3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterXPosition[ 2 ] ) }, + { "VertexWaterYPosition3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterYPosition[ 2 ] ) }, + { "VertexWaterZPosition3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterZPosition[ 2 ] ) }, + { "VertexWaterXGridCells3", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterXGridCells[ 2 ] ) }, + { "VertexWaterYGridCells3", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterYGridCells[ 2 ] ) }, + { "VertexWaterGridSize3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterGridSize[ 2 ] ) }, + { "VertexWaterAttenuationA3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationA[ 2 ] ) }, + { "VertexWaterAttenuationB3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationB[ 2 ] ) }, + { "VertexWaterAttenuationC3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationC[ 2 ] ) }, + { "VertexWaterAttenuationRange3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationRange[ 2 ] ) }, + + // nasty ick, we need to save this data with a map and not hard code INI values + { "VertexWaterAvailableMaps4", INI::parseAsciiString, NULL, offsetof( GlobalData, m_vertexWaterAvailableMaps[ 3 ] ) }, + { "VertexWaterHeightClampLow4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampLow[ 3 ] ) }, + { "VertexWaterHeightClampHi4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampHi[ 3 ] ) }, + { "VertexWaterAngle4", INI::parseAngleReal, NULL, offsetof( GlobalData, m_vertexWaterAngle[ 3 ] ) }, + { "VertexWaterXPosition4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterXPosition[ 3 ] ) }, + { "VertexWaterYPosition4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterYPosition[ 3 ] ) }, + { "VertexWaterZPosition4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterZPosition[ 3 ] ) }, + { "VertexWaterXGridCells4", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterXGridCells[ 3 ] ) }, + { "VertexWaterYGridCells4", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterYGridCells[ 3 ] ) }, + { "VertexWaterGridSize4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterGridSize[ 3 ] ) }, + { "VertexWaterAttenuationA4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationA[ 3 ] ) }, + { "VertexWaterAttenuationB4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationB[ 3 ] ) }, + { "VertexWaterAttenuationC4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationC[ 3 ] ) }, + { "VertexWaterAttenuationRange4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationRange[ 3 ] ) }, + + { "SkyBoxPositionZ", INI::parseReal, NULL, offsetof( GlobalData, m_skyBoxPositionZ ) }, + { "SkyBoxScale", INI::parseReal, NULL, offsetof( GlobalData, m_skyBoxScale ) }, + { "DrawSkyBox", INI::parseBool, NULL, offsetof( GlobalData, m_drawSkyBox ) }, + { "CameraPitch", INI::parseReal, NULL, offsetof( GlobalData, m_cameraPitch ) }, + { "CameraYaw", INI::parseReal, NULL, offsetof( GlobalData, m_cameraYaw ) }, + { "CameraHeight", INI::parseReal, NULL, offsetof( GlobalData, m_cameraHeight ) }, + { "MaxCameraHeight", INI::parseReal, NULL, offsetof( GlobalData, m_maxCameraHeight ) }, + { "MinCameraHeight", INI::parseReal, NULL, offsetof( GlobalData, m_minCameraHeight ) }, + { "TerrainHeightAtEdgeOfMap", INI::parseReal, NULL, offsetof( GlobalData, m_terrainHeightAtEdgeOfMap ) }, + { "UnitDamagedThreshold", INI::parseReal, NULL, offsetof( GlobalData, m_unitDamagedThresh ) }, + { "UnitReallyDamagedThreshold", INI::parseReal, NULL, offsetof( GlobalData, m_unitReallyDamagedThresh ) }, + { "GroundStiffness", INI::parseReal, NULL, offsetof( GlobalData, m_groundStiffness ) }, + { "StructureStiffness", INI::parseReal, NULL, offsetof( GlobalData, m_structureStiffness ) }, + { "Gravity", INI::parseAccelerationReal, NULL, offsetof( GlobalData, m_gravity ) }, + { "StealthFriendlyOpacity", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_stealthFriendlyOpacity ) }, + { "DefaultOcclusionDelay", INI::parseDurationUnsignedInt, NULL, offsetof( GlobalData, m_defaultOcclusionDelay ) }, + + { "PartitionCellSize", INI::parseReal, NULL, offsetof( GlobalData, m_partitionCellSize ) }, + + { "AmmoPipScaleFactor", INI::parseReal, NULL, offsetof( GlobalData, m_ammoPipScaleFactor ) }, + { "ContainerPipScaleFactor", INI::parseReal, NULL, offsetof( GlobalData, m_containerPipScaleFactor ) }, + { "AmmoPipWorldOffset", INI::parseCoord3D, NULL, offsetof( GlobalData, m_ammoPipWorldOffset ) }, + { "ContainerPipWorldOffset", INI::parseCoord3D, NULL, offsetof( GlobalData, m_containerPipWorldOffset ) }, + { "AmmoPipScreenOffset", INI::parseCoord2D, NULL, offsetof( GlobalData, m_ammoPipScreenOffset ) }, + { "ContainerPipScreenOffset", INI::parseCoord2D, NULL, offsetof( GlobalData, m_containerPipScreenOffset ) }, + + { "HistoricDamageLimit", INI::parseDurationUnsignedInt, NULL, offsetof( GlobalData, m_historicDamageLimit ) }, + + { "MaxTerrainTracks", INI::parseInt, NULL, offsetof( GlobalData, m_maxTerrainTracks ) }, + { "TimeOfDay", INI::parseIndexList, TimeOfDayNames, offsetof( GlobalData, m_timeOfDay ) }, + { "Weather", INI::parseIndexList, WeatherNames, offsetof( GlobalData, m_weather ) }, + { "MakeTrackMarks", INI::parseBool, NULL, offsetof( GlobalData, m_makeTrackMarks ) }, + { "HideGarrisonFlags", INI::parseBool, NULL, offsetof( GlobalData, m_hideGarrisonFlags ) }, + { "ForceModelsToFollowTimeOfDay", INI::parseBool, NULL, offsetof( GlobalData, m_forceModelsToFollowTimeOfDay ) }, + { "ForceModelsToFollowWeather", INI::parseBool, NULL, offsetof( GlobalData, m_forceModelsToFollowWeather ) }, + + { "LevelGainAnimationName", INI::parseAsciiString, NULL, offsetof( GlobalData, m_levelGainAnimationName ) }, + { "LevelGainAnimationTime", INI::parseReal, NULL, offsetof( GlobalData, m_levelGainAnimationDisplayTimeInSeconds ) }, + { "LevelGainAnimationZRise", INI::parseReal, NULL, offsetof( GlobalData, m_levelGainAnimationZRisePerSecond ) }, + + { "GetHealedAnimationName", INI::parseAsciiString, NULL, offsetof( GlobalData, m_getHealedAnimationName ) }, + { "GetHealedAnimationTime", INI::parseReal, NULL, offsetof( GlobalData, m_getHealedAnimationDisplayTimeInSeconds ) }, + { "GetHealedAnimationZRise", INI::parseReal, NULL, offsetof( GlobalData, m_getHealedAnimationZRisePerSecond ) }, + + { "TerrainLightingMorningAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][0].ambient ) }, + { "TerrainLightingMorningDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][0].diffuse ) }, + { "TerrainLightingMorningLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][0].lightPos ) }, + { "TerrainLightingAfternoonAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][0].ambient ) }, + { "TerrainLightingAfternoonDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][0].diffuse ) }, + { "TerrainLightingAfternoonLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][0].lightPos ) }, + { "TerrainLightingEveningAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][0].ambient ) }, + { "TerrainLightingEveningDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][0].diffuse ) }, + { "TerrainLightingEveningLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][0].lightPos ) }, + { "TerrainLightingNightAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][0].ambient ) }, + { "TerrainLightingNightDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][0].diffuse ) }, + { "TerrainLightingNightLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][0].lightPos ) }, + + { "TerrainObjectsLightingMorningAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][0].ambient ) }, + { "TerrainObjectsLightingMorningDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][0].diffuse ) }, + { "TerrainObjectsLightingMorningLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][0].lightPos ) }, + { "TerrainObjectsLightingAfternoonAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][0].ambient ) }, + { "TerrainObjectsLightingAfternoonDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][0].diffuse ) }, + { "TerrainObjectsLightingAfternoonLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][0].lightPos ) }, + { "TerrainObjectsLightingEveningAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][0].ambient ) }, + { "TerrainObjectsLightingEveningDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][0].diffuse ) }, + { "TerrainObjectsLightingEveningLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][0].lightPos ) }, + { "TerrainObjectsLightingNightAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][0].ambient ) }, + { "TerrainObjectsLightingNightDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][0].diffuse ) }, + { "TerrainObjectsLightingNightLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][0].lightPos ) }, + + //Secondary global light + { "TerrainLightingMorningAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][1].ambient ) }, + { "TerrainLightingMorningDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][1].diffuse ) }, + { "TerrainLightingMorningLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][1].lightPos ) }, + { "TerrainLightingAfternoonAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][1].ambient ) }, + { "TerrainLightingAfternoonDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][1].diffuse ) }, + { "TerrainLightingAfternoonLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][1].lightPos ) }, + { "TerrainLightingEveningAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][1].ambient ) }, + { "TerrainLightingEveningDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][1].diffuse ) }, + { "TerrainLightingEveningLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][1].lightPos ) }, + { "TerrainLightingNightAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][1].ambient ) }, + { "TerrainLightingNightDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][1].diffuse ) }, + { "TerrainLightingNightLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][1].lightPos ) }, + + { "TerrainObjectsLightingMorningAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][1].ambient ) }, + { "TerrainObjectsLightingMorningDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][1].diffuse ) }, + { "TerrainObjectsLightingMorningLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][1].lightPos ) }, + { "TerrainObjectsLightingAfternoonAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][1].ambient ) }, + { "TerrainObjectsLightingAfternoonDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][1].diffuse ) }, + { "TerrainObjectsLightingAfternoonLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][1].lightPos ) }, + { "TerrainObjectsLightingEveningAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][1].ambient ) }, + { "TerrainObjectsLightingEveningDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][1].diffuse ) }, + { "TerrainObjectsLightingEveningLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][1].lightPos ) }, + { "TerrainObjectsLightingNightAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][1].ambient ) }, + { "TerrainObjectsLightingNightDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][1].diffuse ) }, + { "TerrainObjectsLightingNightLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][1].lightPos ) }, + + //Third global light + { "TerrainLightingMorningAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][2].ambient ) }, + { "TerrainLightingMorningDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][2].diffuse ) }, + { "TerrainLightingMorningLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][2].lightPos ) }, + { "TerrainLightingAfternoonAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][2].ambient ) }, + { "TerrainLightingAfternoonDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][2].diffuse ) }, + { "TerrainLightingAfternoonLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][2].lightPos ) }, + { "TerrainLightingEveningAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][2].ambient ) }, + { "TerrainLightingEveningDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][2].diffuse ) }, + { "TerrainLightingEveningLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][2].lightPos ) }, + { "TerrainLightingNightAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][2].ambient ) }, + { "TerrainLightingNightDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][2].diffuse ) }, + { "TerrainLightingNightLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][2].lightPos ) }, + + { "TerrainObjectsLightingMorningAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][2].ambient ) }, + { "TerrainObjectsLightingMorningDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][2].diffuse ) }, + { "TerrainObjectsLightingMorningLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][2].lightPos ) }, + { "TerrainObjectsLightingAfternoonAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][2].ambient ) }, + { "TerrainObjectsLightingAfternoonDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][2].diffuse ) }, + { "TerrainObjectsLightingAfternoonLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][2].lightPos ) }, + { "TerrainObjectsLightingEveningAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][2].ambient ) }, + { "TerrainObjectsLightingEveningDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][2].diffuse ) }, + { "TerrainObjectsLightingEveningLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][2].lightPos ) }, + { "TerrainObjectsLightingNightAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][2].ambient ) }, + { "TerrainObjectsLightingNightDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][2].diffuse ) }, + { "TerrainObjectsLightingNightLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][2].lightPos ) }, + + + { "NumberGlobalLights", INI::parseInt, NULL, offsetof( GlobalData, m_numGlobalLights)}, + { "InfantryLightMorningScale", INI::parseReal, NULL, offsetof( GlobalData, m_infantryLightScale[TIME_OF_DAY_MORNING] ) }, + { "InfantryLightAfternoonScale", INI::parseReal, NULL, offsetof( GlobalData, m_infantryLightScale[TIME_OF_DAY_AFTERNOON] ) }, + { "InfantryLightEveningScale", INI::parseReal, NULL, offsetof( GlobalData, m_infantryLightScale[TIME_OF_DAY_EVENING] ) }, + { "InfantryLightNightScale", INI::parseReal, NULL, offsetof( GlobalData, m_infantryLightScale[TIME_OF_DAY_NIGHT] ) }, + + { "MaxTranslucentObjects", INI::parseInt, NULL, offsetof( GlobalData, m_maxVisibleTranslucentObjects) }, + { "OccludedColorLuminanceScale", INI::parseReal, NULL, offsetof( GlobalData, m_occludedLuminanceScale) }, + +/* These are internal use only, they do not need file definitons + { "TerrainAmbientRGB", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainAmbient ) }, + { "TerrainDiffuseRGB", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainDiffuse ) }, + { "TerrainLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLightPos ) }, +*/ + { "MaxRoadSegments", INI::parseInt, NULL, offsetof( GlobalData, m_maxRoadSegments ) }, + { "MaxRoadVertex", INI::parseInt, NULL, offsetof( GlobalData, m_maxRoadVertex ) }, + { "MaxRoadIndex", INI::parseInt, NULL, offsetof( GlobalData, m_maxRoadIndex ) }, + { "MaxRoadTypes", INI::parseInt, NULL, offsetof( GlobalData, m_maxRoadTypes ) }, + + { "ValuePerSupplyBox", INI::parseInt, NULL, offsetof( GlobalData, m_baseValuePerSupplyBox ) }, + + { "AudioOn", INI::parseBool, NULL, offsetof( GlobalData, m_audioOn ) }, + { "MusicOn", INI::parseBool, NULL, offsetof( GlobalData, m_musicOn ) }, + { "SoundsOn", INI::parseBool, NULL, offsetof( GlobalData, m_soundsOn ) }, + { "Sounds3DOn", INI::parseBool, NULL, offsetof( GlobalData, m_sounds3DOn ) }, + { "SpeechOn", INI::parseBool, NULL, offsetof( GlobalData, m_speechOn ) }, + { "VideoOn", INI::parseBool, NULL, offsetof( GlobalData, m_videoOn ) }, + { "DisableCameraMovements", INI::parseBool, NULL, offsetof( GlobalData, m_disableCameraMovement ) }, + +/* These are internal use only, they do not need file definitons + /// @todo remove this hack + { "InGame", INI::parseBool, NULL, offsetof( GlobalData, m_inGame ) }, +*/ + + { "DebugAI", INI::parseBool, NULL, offsetof( GlobalData, m_debugAI ) }, + { "DebugAIObstacles", INI::parseBool, NULL, offsetof( GlobalData, m_debugAIObstacles ) }, + { "ShowClientPhysics", INI::parseBool, NULL, offsetof( GlobalData, m_showClientPhysics ) }, + { "ShowTerrainNormals", INI::parseBool, NULL, offsetof( GlobalData, m_showTerrainNormals ) }, + { "ShowObjectHealth", INI::parseBool, NULL, offsetof( GlobalData, m_showObjectHealth ) }, + + { "ParticleScale", INI::parseReal, NULL, offsetof( GlobalData, m_particleScale ) }, + { "AutoFireParticleSmallPrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleSmallPrefix ) }, + { "AutoFireParticleSmallSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleSmallSystem ) }, + { "AutoFireParticleSmallMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoFireParticleSmallMax ) }, + { "AutoFireParticleMediumPrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleMediumPrefix ) }, + { "AutoFireParticleMediumSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleMediumSystem ) }, + { "AutoFireParticleMediumMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoFireParticleMediumMax ) }, + { "AutoFireParticleLargePrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleLargePrefix ) }, + { "AutoFireParticleLargeSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleLargeSystem ) }, + { "AutoFireParticleLargeMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoFireParticleLargeMax ) }, + { "AutoSmokeParticleSmallPrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleSmallPrefix ) }, + { "AutoSmokeParticleSmallSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleSmallSystem ) }, + { "AutoSmokeParticleSmallMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoSmokeParticleSmallMax ) }, + { "AutoSmokeParticleMediumPrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleMediumPrefix ) }, + { "AutoSmokeParticleMediumSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleMediumSystem ) }, + { "AutoSmokeParticleMediumMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoSmokeParticleMediumMax ) }, + { "AutoSmokeParticleLargePrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleLargePrefix ) }, + { "AutoSmokeParticleLargeSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleLargeSystem ) }, + { "AutoSmokeParticleLargeMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoSmokeParticleLargeMax ) }, + { "AutoAflameParticlePrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoAflameParticlePrefix ) }, + { "AutoAflameParticleSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoAflameParticleSystem ) }, + { "AutoAflameParticleMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoAflameParticleMax ) }, + +/* These are internal use only, they do not need file definitons + { "LatencyAverage", INI::parseInt, NULL, offsetof( GlobalData, m_latencyAverage ) }, + { "LatencyAmplitude", INI::parseInt, NULL, offsetof( GlobalData, m_latencyAmplitude ) }, + { "LatencyPeriod", INI::parseInt, NULL, offsetof( GlobalData, m_latencyPeriod ) }, + { "LatencyNoise", INI::parseInt, NULL, offsetof( GlobalData, m_latencyNoise ) }, + { "PacketLoss", INI::parseInt, NULL, offsetof( GlobalData, m_packetLoss ) }, +*/ + + { "BuildSpeed", INI::parseReal, NULL, offsetof( GlobalData, m_BuildSpeed ) }, + { "MinDistFromEdgeOfMapForBuild", INI::parseReal, NULL, offsetof( GlobalData, m_MinDistFromEdgeOfMapForBuild ) }, + { "SupplyBuildBorder", INI::parseReal, NULL, offsetof( GlobalData, m_SupplyBuildBorder ) }, + { "AllowedHeightVariationForBuilding", INI::parseReal,NULL, offsetof( GlobalData, m_allowedHeightVariationForBuilding ) }, + { "MinLowEnergyProductionSpeed",INI::parseReal, NULL, offsetof( GlobalData, m_MinLowEnergyProductionSpeed ) }, + { "MaxLowEnergyProductionSpeed",INI::parseReal, NULL, offsetof( GlobalData, m_MaxLowEnergyProductionSpeed ) }, + { "LowEnergyPenaltyModifier", INI::parseReal, NULL, offsetof( GlobalData, m_LowEnergyPenaltyModifier ) }, + { "MultipleFactory", INI::parseReal, NULL, offsetof( GlobalData, m_MultipleFactory ) }, + { "RefundPercent", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_RefundPercent ) }, + + { "CommandCenterHealRange", INI::parseReal, NULL, offsetof( GlobalData, m_commandCenterHealRange ) }, + { "CommandCenterHealAmount", INI::parseReal, NULL, offsetof( GlobalData, m_commandCenterHealAmount ) }, + + { "StandardMinefieldDensity", INI::parseReal, NULL, offsetof( GlobalData, m_standardMinefieldDensity ) }, + { "StandardMinefieldDistance", INI::parseReal, NULL, offsetof( GlobalData, m_standardMinefieldDistance ) }, + + { "MaxLineBuildObjects", INI::parseInt, NULL, offsetof( GlobalData, m_maxLineBuildObjects ) }, + { "MaxTunnelCapacity", INI::parseInt, NULL, offsetof( GlobalData, m_maxTunnelCapacity ) }, + + { "MaxParticleCount", INI::parseInt, NULL, offsetof( GlobalData, m_maxParticleCount ) }, + { "MaxFieldParticleCount", INI::parseInt, NULL, offsetof( GlobalData, m_maxFieldParticleCount ) }, + { "HorizontalScrollSpeedFactor",INI::parseReal, NULL, offsetof( GlobalData, m_horizontalScrollSpeedFactor ) }, + { "VerticalScrollSpeedFactor", INI::parseReal, NULL, offsetof( GlobalData, m_verticalScrollSpeedFactor ) }, + { "ScrollAmountCutoff", INI::parseReal, NULL, offsetof( GlobalData, m_scrollAmountCutoff ) }, + { "CameraAdjustSpeed", INI::parseReal, NULL, offsetof( GlobalData, m_cameraAdjustSpeed ) }, + { "EnforceMaxCameraHeight", INI::parseBool, NULL, offsetof( GlobalData, m_enforceMaxCameraHeight ) }, + { "KeyboardScrollSpeedFactor", INI::parseReal, NULL, offsetof( GlobalData, m_keyboardScrollFactor ) }, + { "KeyboardDefaultScrollSpeedFactor", INI::parseReal, NULL, offsetof( GlobalData, m_keyboardDefaultScrollFactor ) }, + { "MovementPenaltyDamageState", INI::parseIndexList, TheBodyDamageTypeNames, offsetof( GlobalData, m_movementPenaltyDamageState ) }, + +// you cannot set this; it always has a value of 100%. +//{ "HealthBonus_Regular", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_healthBonus[LEVEL_REGULAR]) }, + { "HealthBonus_Veteran", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_healthBonus[LEVEL_VETERAN]) }, + { "HealthBonus_Elite", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_healthBonus[LEVEL_ELITE]) }, + { "HealthBonus_Heroic", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_healthBonus[LEVEL_HEROIC]) }, + + { "HumanSoloPlayerHealthBonus_Easy", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_HUMAN][DIFFICULTY_EASY] ) }, + { "HumanSoloPlayerHealthBonus_Normal", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_HUMAN][DIFFICULTY_NORMAL] ) }, + { "HumanSoloPlayerHealthBonus_Hard", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_HUMAN][DIFFICULTY_HARD] ) }, + + { "AISoloPlayerHealthBonus_Easy", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_COMPUTER][DIFFICULTY_EASY] ) }, + { "AISoloPlayerHealthBonus_Normal", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_COMPUTER][DIFFICULTY_NORMAL] ) }, + { "AISoloPlayerHealthBonus_Hard", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_COMPUTER][DIFFICULTY_HARD] ) }, + + { "WeaponBonus", WeaponBonusSet::parseWeaponBonusSetPtr, NULL, offsetof( GlobalData, m_weaponBonusSet ) }, + + { "DefaultStructureRubbleHeight", INI::parseReal, NULL, offsetof( GlobalData, m_defaultStructureRubbleHeight ) }, + + { "FixedSeed", INI::parseInt, NULL, offsetof( GlobalData, m_fixedSeed ) }, + + { "ShellMapName", INI::parseAsciiString,NULL, offsetof( GlobalData, m_shellMapName ) }, + { "ShellMapOn", INI::parseBool, NULL, offsetof( GlobalData, m_shellMapOn ) }, + { "PlayIntro", INI::parseBool, NULL, offsetof( GlobalData, m_playIntro ) }, + + { "FirewallBehavior", INI::parseInt, NULL, offsetof( GlobalData, m_firewallBehavior ) }, + { "FirewallPortOverride", INI::parseInt, NULL, offsetof( GlobalData, m_firewallPortOverride ) }, + { "FirewallPortAllocationDelta",INI::parseInt, NULL, offsetof( GlobalData, m_firewallPortAllocationDelta) }, + + { "GroupSelectMinSelectSize", INI::parseInt, NULL, offsetof( GlobalData, m_groupSelectMinSelectSize ) }, + { "GroupSelectVolumeBase", INI::parseReal, NULL, offsetof( GlobalData, m_groupSelectVolumeBase ) }, + { "GroupSelectVolumeIncrement", INI::parseReal, NULL, offsetof( GlobalData, m_groupSelectVolumeIncrement ) }, + { "MaxUnitSelectSounds", INI::parseInt, NULL, offsetof( GlobalData, m_maxUnitSelectSounds ) }, + + { "SelectionFlashSaturationFactor", INI::parseReal, NULL, offsetof( GlobalData, m_selectionFlashSaturationFactor ) }, + { "SelectionFlashHouseColor", INI::parseBool, NULL, offsetof( GlobalData, m_selectionFlashHouseColor ) }, + + { "CameraAudibleRadius", INI::parseReal, NULL, offsetof( GlobalData, m_cameraAudibleRadius ) }, + { "GroupMoveClickToGatherAreaFactor", INI::parseReal, NULL, offsetof( GlobalData, m_groupMoveClickToGatherFactor ) }, + { "ShakeSubtleIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeSubtleIntensity ) }, + { "ShakeNormalIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeNormalIntensity ) }, + { "ShakeStrongIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeStrongIntensity ) }, + { "ShakeSevereIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeSevereIntensity ) }, + { "ShakeCineExtremeIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeCineExtremeIntensity ) }, + { "ShakeCineInsaneIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeCineInsaneIntensity ) }, + { "MaxShakeIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_maxShakeIntensity ) }, + { "MaxShakeRange", INI::parseReal, NULL, offsetof( GlobalData, m_maxShakeRange) }, + { "SellPercentage", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_sellPercentage ) }, + { "BaseRegenHealthPercentPerSecond", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_baseRegenHealthPercentPerSecond ) }, + { "BaseRegenDelay", INI::parseDurationUnsignedInt, NULL,offsetof( GlobalData, m_baseRegenDelay ) }, + +#ifdef ALLOW_SURRENDER + { "PrisonBountyMultiplier", INI::parseReal, NULL, offsetof( GlobalData, m_prisonBountyMultiplier ) }, + { "PrisonBountyTextColor", INI::parseColorInt, NULL, offsetof( GlobalData, m_prisonBountyTextColor ) }, +#endif + + { "SpecialPowerViewObject", INI::parseAsciiString, NULL, offsetof( GlobalData, m_specialPowerViewObjectName ) }, + + { "StandardPublicBone", INI::parseAsciiStringVectorAppend, NULL, offsetof(GlobalData, m_standardPublicBones) }, + { "ShowMetrics", INI::parseBool, NULL, offsetof( GlobalData, m_showMetrics ) }, + { "DefaultStartingCash", Money::parseMoneyAmount, NULL, offsetof( GlobalData, m_defaultStartingCash ) }, + +// NOTE: m_doubleClickTimeMS is still in use, but we disallow setting it from the GameData.ini file. It is now set in the constructor according to the windows parameter. +// { "DoubleClickTimeMS", INI::parseUnsignedInt, NULL, offsetof( GlobalData, m_doubleClickTimeMS ) }, + + { "ShroudColor", INI::parseRGBColor, NULL, offsetof( GlobalData, m_shroudColor) }, + { "ClearAlpha", INI::parseUnsignedByte, NULL, offsetof( GlobalData, m_clearAlpha) }, + { "FogAlpha", INI::parseUnsignedByte, NULL, offsetof( GlobalData, m_fogAlpha) }, + { "ShroudAlpha", INI::parseUnsignedByte, NULL, offsetof( GlobalData, m_shroudAlpha) }, + + { "HotKeyTextColor", INI::parseColorInt, NULL, offsetof( GlobalData, m_hotKeyTextColor ) }, + + { "PowerBarBase", INI::parseInt, NULL, offsetof( GlobalData, m_powerBarBase) }, + { "PowerBarIntervals", INI::parseReal, NULL, offsetof( GlobalData, m_powerBarIntervals) }, + { "PowerBarYellowRange", INI::parseInt, NULL, offsetof( GlobalData, m_powerBarYellowRange) }, + { "UnlookPersistDuration", INI::parseDurationUnsignedInt, NULL, offsetof( GlobalData, m_unlookPersistDuration) }, + + { "NetworkFPSHistoryLength", INI::parseInt, NULL, offsetof(GlobalData, m_networkFPSHistoryLength) }, + { "NetworkLatencyHistoryLength", INI::parseInt, NULL, offsetof(GlobalData, m_networkLatencyHistoryLength) }, + { "NetworkRunAheadMetricsTime", INI::parseInt, NULL, offsetof(GlobalData, m_networkRunAheadMetricsTime) }, + { "NetworkCushionHistoryLength", INI::parseInt, NULL, offsetof(GlobalData, m_networkCushionHistoryLength) }, + { "NetworkRunAheadSlack", INI::parseInt, NULL, offsetof(GlobalData, m_networkRunAheadSlack) }, + { "NetworkKeepAliveDelay", INI::parseInt, NULL, offsetof(GlobalData, m_networkKeepAliveDelay) }, + { "NetworkDisconnectTime", INI::parseInt, NULL, offsetof(GlobalData, m_networkDisconnectTime) }, + { "NetworkPlayerTimeoutTime", INI::parseInt, NULL, offsetof(GlobalData, m_networkPlayerTimeoutTime) }, + { "NetworkDisconnectScreenNotifyTime", INI::parseInt, NULL, offsetof(GlobalData, m_networkDisconnectScreenNotifyTime) }, + + { "KeyboardCameraRotateSpeed", INI::parseReal, NULL, offsetof( GlobalData, m_keyboardCameraRotateSpeed ) }, + { "PlayStats", INI::parseInt, NULL, offsetof( GlobalData, m_playStats ) }, + +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + { "DisableCameraFade", INI::parseBool, NULL, offsetof( GlobalData, m_disableCameraFade ) }, + { "DisableScriptedInputDisabling", INI::parseBool, NULL, offsetof( GlobalData, m_disableScriptedInputDisabling ) }, + { "DisableMilitaryCaption", INI::parseBool, NULL, offsetof( GlobalData, m_disableMilitaryCaption ) }, + { "BenchmarkTimer", INI::parseInt, NULL, offsetof( GlobalData, m_benchmarkTimer ) }, + { "CheckMemoryLeaks", INI::parseBool, NULL, offsetof(GlobalData, m_checkForLeaks) }, + { "Wireframe", INI::parseBool, NULL, offsetof( GlobalData, m_wireframe ) }, + { "StateMachineDebug", INI::parseBool, NULL, offsetof( GlobalData, m_stateMachineDebug ) }, + { "UseCameraConstraints", INI::parseBool, NULL, offsetof( GlobalData, m_useCameraConstraints ) }, + { "ShroudOn", INI::parseBool, NULL, offsetof( GlobalData, m_shroudOn ) }, + { "FogOfWarOn", INI::parseBool, NULL, offsetof( GlobalData, m_fogOfWarOn ) }, + { "ShowCollisionExtents", INI::parseBool, NULL, offsetof( GlobalData, m_showCollisionExtents ) }, + { "ShowAudioLocations", INI::parseBool, NULL, offsetof( GlobalData, m_showAudioLocations ) }, + { "DebugProjectileTileWidth", INI::parseReal, NULL, offsetof( GlobalData, m_debugProjectileTileWidth) }, + { "DebugProjectileTileDuration",INI::parseInt, NULL, offsetof( GlobalData, m_debugProjectileTileDuration) }, + { "DebugProjectileTileColor", INI::parseRGBColor, NULL, offsetof( GlobalData, m_debugProjectileTileColor) }, + { "DebugVisibilityTileCount", INI::parseInt, NULL, offsetof( GlobalData, m_debugVisibilityTileCount) }, + { "DebugVisibilityTileWidth", INI::parseReal, NULL, offsetof( GlobalData, m_debugVisibilityTileWidth) }, + { "DebugVisibilityTileDuration",INI::parseInt, NULL, offsetof( GlobalData, m_debugVisibilityTileDuration) }, + { "DebugVisibilityTileTargettableColor",INI::parseRGBColor, NULL, offsetof( GlobalData, m_debugVisibilityTargettableColor) }, + { "DebugVisibilityTileDeshroudColor", INI::parseRGBColor, NULL, offsetof( GlobalData, m_debugVisibilityDeshroudColor) }, + { "DebugVisibilityTileGapColor", INI::parseRGBColor, NULL, offsetof( GlobalData, m_debugVisibilityGapColor) }, + { "DebugThreatMapTileDuration", INI::parseInt, NULL, offsetof( GlobalData, m_debugThreatMapTileDuration) }, + { "MaxDebugThreatMapValue", INI::parseUnsignedInt, NULL, offsetof( GlobalData, m_maxDebugThreat) }, + { "DebugCashValueMapTileDuration", INI::parseInt, NULL, offsetof( GlobalData, m_debugCashValueMapTileDuration) }, + { "MaxDebugCashValueMapValue", INI::parseUnsignedInt, NULL, offsetof( GlobalData, m_maxDebugValue) }, + { "VTune", INI::parseBool, NULL, offsetof( GlobalData, m_vTune ) }, + { "SaveStats", INI::parseBool, NULL, offsetof( GlobalData, m_saveStats ) }, + { "UseLocalMOTD", INI::parseBool, NULL, offsetof( GlobalData, m_useLocalMOTD ) }, + { "BaseStatsDir", INI::parseAsciiString,NULL, offsetof( GlobalData, m_baseStatsDir ) }, + { "LocalMOTDPath", INI::parseAsciiString,NULL, offsetof( GlobalData, m_MOTDPath ) }, + { "ExtraLogging", INI::parseBool, NULL, offsetof( GlobalData, m_extraLogging ) }, +#endif + + { "UseVanillaDiagonalMoveSpeed", INI::parseBool, NULL, offsetof(GlobalData, m_useOldMoveSpeed) }, + { "TintStatus", GlobalData::parseTintStatusType, NULL, offsetof(GlobalData, m_colorTintTypes) }, + + {"ChronoDamageDisableThreshold", INI::parsePercentToReal, NULL, offsetof(GlobalData, m_chronoDamageDisableThreshold)}, + {"ChronoDamageHealRate", INI::parseDurationUnsignedInt, NULL, offsetof(GlobalData, m_chronoDamageHealRate)}, + {"ChronoDamageHealAmountPercent", INI::parsePercentToReal, NULL, offsetof(GlobalData, m_chronoDamageHealAmount) }, + {"DefaultExcludedDeathTypes", INI::parseDeathTypeFlagsList, NULL, offsetof(GlobalData, m_defaultExcludedDeathTypes) }, + + { NULL, NULL, NULL, 0 } // keep this last + +}; + + + +// Helper function +/*static*/ void GlobalData::setColorTintEntry(DrawableColorTint* arr, int index, RGBColor color, RGBColor colorInfantry, UnsignedInt attackFrames, UnsignedInt decayFrames) +{ + arr[index].color = color; + arr[index].colorInfantry = colorInfantry; + arr[index].attackFrames = attackFrames; + arr[index].decayFrames = decayFrames; +} + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +GlobalData::GlobalData() +{ + Int i, j; + + // + // we have now instanced a global data instance, if theOriginal is NULL, this is + // *the* very first instance and it shall be recorded. This way, when we load + // overrides of the global data, we can revert to the common, original data + // in m_theOriginal + // + if( m_theOriginal == NULL ) + m_theOriginal = this; + m_next = NULL; + +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) || defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE) + m_specialPowerUsesDelay = TRUE; +#endif + m_TiVOFastMode = FALSE; + +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + m_wireframe = 0; + m_stateMachineDebug = FALSE; + m_useCameraConstraints = TRUE; + m_shroudOn = TRUE; + m_fogOfWarOn = FALSE; + m_jabberOn = FALSE; + m_munkeeOn = FALSE; + m_showCollisionExtents = FALSE; + m_showAudioLocations = FALSE; + m_debugCamera = FALSE; + m_debugVisibility = FALSE; + m_debugVisibilityTileCount = 32; // default to 32. + m_debugVisibilityTileDuration = LOGICFRAMES_PER_SECOND; + m_debugProjectilePath = FALSE; + m_debugProjectileTileWidth = 10; + m_debugProjectileTileDuration = LOGICFRAMES_PER_SECOND; // Changed By Sadullah Nader + m_debugThreatMap = FALSE; + m_maxDebugThreat = 5000; + m_debugThreatMapTileDuration = LOGICFRAMES_PER_SECOND; // Changed By Sadullah Nader + m_debugCashValueMap = FALSE; + m_maxDebugValue = 10000; + m_debugCashValueMapTileDuration = LOGICFRAMES_PER_SECOND; // Changed By Sadullah Nader + m_vTune = false; + m_checkForLeaks = TRUE; + m_benchmarkTimer = -1; + + + m_allowUnselectableSelection = FALSE; + m_disableCameraFade = false; + m_disableScriptedInputDisabling = false; + m_disableMilitaryCaption = false; + m_latencyAverage = 0; + m_latencyAmplitude = 0; + m_latencyPeriod = 0; + m_latencyNoise = 0; + m_packetLoss = 0; + m_saveStats = FALSE; + m_saveAllStats = FALSE; + m_useLocalMOTD = FALSE; + m_baseStatsDir = ".\\"; + m_MOTDPath = "MOTD.txt"; + m_extraLogging = FALSE; +#endif + +#ifdef DEBUG_CRASHING + m_debugIgnoreAsserts = FALSE; +#endif + +#ifdef DEBUG_STACKTRACE + m_debugIgnoreStackTrace = FALSE; +#endif + + m_playStats = -1; + m_incrementalAGPBuf = FALSE; + m_mapName.clear(); + m_moveHintName.clear(); + m_useTrees = 0; + m_useTreeSway = TRUE; + m_useDrawModuleLOD = FALSE; + m_useHeatEffects = TRUE; + m_useFpsLimit = FALSE; + m_dumpAssetUsage = FALSE; + m_framesPerSecondLimit = 0; + m_chipSetType = 0; + m_windowed = 0; + m_xResolution = 800; + m_yResolution = 600; + m_maxShellScreens = 0; + m_useCloudMap = FALSE; + m_use3WayTerrainBlends = 1; + m_useLightMap = FALSE; + m_bilinearTerrainTex = FALSE; + m_trilinearTerrainTex = FALSE; + m_multiPassTerrain = FALSE; + m_adjustCliffTextures = FALSE; + m_stretchTerrain = FALSE; + m_useHalfHeightMap = FALSE; + m_terrainLOD = TERRAIN_LOD_AUTOMATIC; + m_terrainLODTargetTimeMS = 0; + m_enableDynamicLOD = TRUE; + m_enableStaticLOD = TRUE; + m_rightMouseAlwaysScrolls = FALSE; + m_useWaterPlane = FALSE; + m_useCloudPlane = FALSE; + m_downwindAngle = ( -0.785f );//Northeast! + m_useShadowVolumes = FALSE; + m_useShadowDecals = FALSE; + m_textureReductionFactor = -1; + m_enableBehindBuildingMarkers = TRUE; + m_scriptDebug = FALSE; + m_particleEdit = FALSE; + m_displayDebug = FALSE; + m_winCursors = TRUE; + m_constantDebugUpdate = FALSE; + m_showTeamDot = FALSE; + m_fixedSeed = -1; // disabled + m_horizontalScrollSpeedFactor = 1.0; + m_verticalScrollSpeedFactor = 1.0; + + m_waterPositionX = 0.0f; + m_waterPositionY = 0.0f; + m_waterPositionZ = 0.0f; + m_waterExtentX = 0.0f; + m_waterExtentY = 0.0f; + m_waterType = 0; + m_featherWater = FALSE; + m_showSoftWaterEdge = TRUE; //display soft water edge + m_usingWaterTrackEditor = FALSE; + m_isWorldBuilder = FALSE; + + m_showMetrics = false; + + for( i = 0; i < MAX_WATER_GRID_SETTINGS; i++ ) + { + + m_vertexWaterHeightClampLow[ i ] = 0.0f; + m_vertexWaterHeightClampHi[ i ] = 0.0f; + m_vertexWaterAngle[ i ] = 0.0f; + m_vertexWaterXPosition[ i ] = 0.0f; + m_vertexWaterYPosition[ i ] = 0.0f; + m_vertexWaterZPosition[ i ] = 0.0f; + m_vertexWaterXGridCells[ i ] = 0; + m_vertexWaterYGridCells[ i ] = 0; + m_vertexWaterGridSize[ i ] = 0.0f; + m_vertexWaterAttenuationA[ i ] = 0.0f; + m_vertexWaterAttenuationB[ i ] = 0.0f; + m_vertexWaterAttenuationC[ i ] = 0.0f; + m_vertexWaterAttenuationRange[ i ] = 0.0f; + //Added By Sadullah Nader + //Initializations missing and needed + m_vertexWaterAvailableMaps[i].clear(); + } // end for i + + m_skyBoxPositionZ = 0.0f; + m_drawSkyBox = FALSE; + m_skyBoxScale = 4.5f; + + m_historicDamageLimit = 0; + m_maxTerrainTracks = 0; + + m_levelGainAnimationDisplayTimeInSeconds = 0.0f; + m_levelGainAnimationZRisePerSecond = 0.0f; + + m_getHealedAnimationDisplayTimeInSeconds = 0.0f; + m_getHealedAnimationZRisePerSecond = 0.0f; + + m_maxTankTrackEdges=100; + m_maxTankTrackOpaqueEdges=25; + m_maxTankTrackFadeDelay=300000; + + m_timeOfDay = TIME_OF_DAY_AFTERNOON; + m_weather = WEATHER_NORMAL; + m_makeTrackMarks = FALSE; + m_hideGarrisonFlags = FALSE; + m_forceModelsToFollowTimeOfDay = true; + m_forceModelsToFollowWeather = true; + + m_partitionCellSize = 0.0f; + m_ammoPipScaleFactor = 1.0f; + m_containerPipScaleFactor = 1.0f; + m_ammoPipWorldOffset.zero(); + m_containerPipWorldOffset.zero(); + m_ammoPipScreenOffset.x = m_ammoPipScreenOffset.y = 0; + m_containerPipScreenOffset.x = m_containerPipScreenOffset.y = 0; + + for (i=0; iopenFile(buffer, File::READ | File::BINARY); + if (fp != NULL) { + unsigned char crcBlock[blockSize]; + Int amtRead = 0; + while ( (amtRead=fp->read(crcBlock, blockSize)) > 0 ) + { + exeCRC.computeCRC(crcBlock, amtRead); + } + fp->close(); + fp = NULL; + } + if (TheVersion) + { + UnsignedInt version = TheVersion->getVersionNumber(); + exeCRC.computeCRC( &version, sizeof(UnsignedInt) ); + } + // Add in MP scripts to the EXE CRC, since the game will go out of sync if they change + fp = TheFileSystem->openFile("Data\\Scripts\\SkirmishScripts.scb", File::READ | File::BINARY); + if (fp != NULL) { + unsigned char crcBlock[blockSize]; + Int amtRead = 0; + while ( (amtRead=fp->read(crcBlock, blockSize)) > 0 ) + { + exeCRC.computeCRC(crcBlock, amtRead); + } + fp->close(); + fp = NULL; + } + fp = TheFileSystem->openFile("Data\\Scripts\\MultiplayerScripts.scb", File::READ | File::BINARY); + if (fp != NULL) { + unsigned char crcBlock[blockSize]; + Int amtRead = 0; + while ( (amtRead=fp->read(crcBlock, blockSize)) > 0 ) + { + exeCRC.computeCRC(crcBlock, amtRead); + } + fp->close(); + fp = NULL; + } + + m_exeCRC = exeCRC.get(); + DEBUG_LOG(("EXE CRC: 0x%8.8X\n", m_exeCRC)); + + m_movementPenaltyDamageState = BODY_REALLYDAMAGED; + + m_shouldUpdateTGAToDDS = FALSE; + + // Default DoubleClickTime to System double click time. + m_doubleClickTimeMS = GetDoubleClickTime(); // Note: This is actual MS, not frames. + +#ifdef DUMP_PERF_STATS + m_dumpPerformanceStatistics = FALSE; + m_dumpStatsAtInterval = FALSE; + m_statsInterval = 30; +#endif + + m_forceBenchmark = FALSE; ///> GLOBAL_DATA: m_colorTintTypes[%d] = {(%f, %f, %f), (%f, %f, %f), %d, %d}\n", + i, tc.color.red, tc.color.green, tc.color.blue, tc.colorInfantry.red, tc.colorInfantry.green, tc.colorInfantry.blue, + tc.attackFrames, tc.decayFrames)); + } + // ------------------------------------------------------------------------------ + + m_chronoDamageDisableThreshold = 0.1; + m_chronoDamageHealRate = 15; + m_chronoDamageHealAmount = 0.1; + + m_defaultExcludedDeathTypes = DEATH_TYPE_FLAGS_NONE; + +} // end GlobalData + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +GlobalData::~GlobalData( void ) +{ + DEBUG_ASSERTCRASH( TheWritableGlobalData->m_next == NULL, ("~GlobalData: theOriginal is not original\n") ); + + if (m_weaponBonusSet) + m_weaponBonusSet->deleteInstance(); + + if( m_theOriginal == this ) { + m_theOriginal = NULL; + TheWritableGlobalData = NULL; + } + +} // end ~GlobalData + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool GlobalData::setTimeOfDay( TimeOfDay tod ) +{ + if( tod >= TIME_OF_DAY_COUNT || tod < TIME_OF_DAY_FIRST ) + { + return FALSE; + } + + m_timeOfDay = tod; + for (Int i=0; im_next = TheWritableGlobalData; + + // set this new instance as the 'most current override' where we will access all data from + TheWritableGlobalData = override; + + return override; + +} // end newOveride + +//------------------------------------------------------------------------------------------------- +void GlobalData::init( void ) +{ + // nothing +} + +//------------------------------------------------------------------------------------------------- +/** Reset, remove any override data instances and return to just the initial one + */ +//------------------------------------------------------------------------------------------------- +void GlobalData::reset( void ) +{ + DEBUG_ASSERTCRASH(this == TheWritableGlobalData, ("calling reset on wrong GlobalData")); + + // + // delete any data instances that were loaded as an override and set the original + // global data instance as the singleton TheWritableGlobalData once again + // + while (TheWritableGlobalData != GlobalData::m_theOriginal) + { + + // get next instance + GlobalData* next = TheWritableGlobalData->m_next; + + // delete the head of the global data list (the latest override) + delete TheWritableGlobalData; + + // set next as top + TheWritableGlobalData = next; + + } // end while + + // + // we now have the one single global data in TheWritableGlobalData singleton, lets sanity check + // some of all that + // + DEBUG_ASSERTCRASH( TheWritableGlobalData->m_next == NULL, ("ResetGlobalData: theOriginal is not original\n") ); + DEBUG_ASSERTCRASH( TheWritableGlobalData == GlobalData::m_theOriginal, ("ResetGlobalData: oops\n") ); + +} // end ResetGlobalData + +//------------------------------------------------------------------------------------------------- +/** Parse GameData entry */ +//------------------------------------------------------------------------------------------------- +void GlobalData::parseGameDataDefinition( INI* ini ) +{ + if( TheWritableGlobalData && ini->getLoadType() != INI_LOAD_MULTIFILE) + { + + // + // if the type of loading we're doing creates override data, we need to + // be loading into a new override item + // + if( ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES ) + TheWritableGlobalData->newOverride(); + + } // end if + else if (!TheWritableGlobalData) + { + + // we don't have any global data instance at all yet, create one + TheWritableGlobalData = NEW GlobalData; + + } // end else + // If we're multifile, then continue loading stuff into the Global Data as normal. + + // parse the ini weapon definition + ini->initFromINI( TheWritableGlobalData, s_GlobalDataFieldParseTable ); + + + // override INI values with user preferences + OptionPreferences optionPref; + TheWritableGlobalData->m_useAlternateMouse = optionPref.getAlternateMouseModeEnabled(); + TheWritableGlobalData->m_clientRetaliationModeEnabled = optionPref.getRetaliationModeEnabled(); + TheWritableGlobalData->m_doubleClickAttackMove = optionPref.getDoubleClickAttackMoveEnabled(); + TheWritableGlobalData->m_keyboardScrollFactor = optionPref.getScrollFactor(); + TheWritableGlobalData->m_defaultIP = optionPref.getLANIPAddress(); + TheWritableGlobalData->m_firewallSendDelay = optionPref.getSendDelay(); + TheWritableGlobalData->m_firewallBehavior = optionPref.getFirewallBehavior(); + TheWritableGlobalData->m_firewallPortAllocationDelta = optionPref.getFirewallPortAllocationDelta(); + TheWritableGlobalData->m_firewallPortOverride = optionPref.getFirewallPortOverride(); + + TheWritableGlobalData->m_saveCameraInReplay = optionPref.saveCameraInReplays(); + TheWritableGlobalData->m_useCameraInReplay = optionPref.useCameraInReplays(); + + Int val=optionPref.getGammaValue(); + //generate a value between 0.6 and 2.0. + if (val < 50) + { //darker gamma + if (val <= 0) + TheWritableGlobalData->m_displayGamma = 0.6f; + else + TheWritableGlobalData->m_displayGamma=1.0f-(0.4f) * (Real)(50-val)/50.0f; + } + else + if (val > 50) + TheWritableGlobalData->m_displayGamma=1.0f+(1.0f) * (Real)(val-50)/50.0f; + + Int xres,yres; + optionPref.getResolution(&xres, &yres); + + TheWritableGlobalData->m_xResolution = xres; + TheWritableGlobalData->m_yResolution = yres; +} + diff --git a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp index f83ba505995..127b8d9cbe0 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp @@ -1,2080 +1,2116 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: INI.cpp ////////////////////////////////////////////////////////////////////////////////// -// Author: Colin Day, November 2001 -// Desc: INI Reader -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine -#define DEFINE_DEATH_NAMES -#define DEFINE_WEAPONBONUSCONDITION_NAMES - -#include "Common/INI.h" -#include "Common/INIException.h" - -#include "Common/DamageFX.h" -#include "Common/file.h" -#include "Common/FileSystem.h" -#include "Common/GameAudio.h" -#include "Common/Science.h" -#include "Common/SpecialPower.h" -#include "Common/ThingFactory.h" -#include "Common/ThingTemplate.h" -#include "Common/Upgrade.h" -#include "Common/Xfer.h" -#include "Common/XferCRC.h" - -#include "GameClient/Anim2D.h" -#include "GameClient/Color.h" -#include "GameClient/FXList.h" -#include "GameClient/GameText.h" -#include "GameClient/Image.h" -#include "GameClient/ParticleSys.h" -#include "GameLogic/Armor.h" -#include "GameLogic/ExperienceTracker.h" -#include "GameLogic/FPUControl.h" -#include "GameLogic/ObjectCreationList.h" -#include "GameLogic/ScriptEngine.h" -#include "GameLogic/Weapon.h" - -#ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -static Xfer *s_xfer = NULL; - -//------------------------------------------------------------------------------------------------- -/** This is the table of data types we can have in INI files. To add a new data type - * block make a new entry in this table and add an appropriate parsing function */ -//------------------------------------------------------------------------------------------------- -extern void parseReallyLowMHz( INI* ini); // yeah, so sue me (srj) -struct BlockParse -{ - const char *token; - INIBlockParse parse; -}; -static const BlockParse theTypeTable[] = -{ - { "AIData", INI::parseAIDataDefinition }, - { "Animation", INI::parseAnim2DDefinition }, - { "Armor", INI::parseArmorDefinition }, - { "ArmorExtend", INI::parseArmorExtendDefinition }, - { "AudioEvent", INI::parseAudioEventDefinition }, - { "AudioSettings", INI::parseAudioSettingsDefinition }, - { "Bridge", INI::parseTerrainBridgeDefinition }, - { "Campaign", INI::parseCampaignDefinition }, - { "ChallengeGenerals", INI::parseChallengeModeDefinition }, - { "CommandButton", INI::parseCommandButtonDefinition }, - { "CommandMap", INI::parseMetaMapDefinition }, - { "CommandSet", INI::parseCommandSetDefinition }, - { "ControlBarScheme", INI::parseControlBarSchemeDefinition }, - { "ControlBarResizer", INI::parseControlBarResizerDefinition }, - { "CrateData", INI::parseCrateTemplateDefinition }, - { "Credits", INI::parseCredits}, - { "WindowTransition", INI::parseWindowTransitions}, - { "DamageFX", INI::parseDamageFXDefinition }, - { "DialogEvent", INI::parseDialogDefinition }, - { "DrawGroupInfo", INI::parseDrawGroupNumberDefinition }, - { "EvaEvent", INI::parseEvaEvent }, - { "FXList", INI::parseFXListDefinition }, - { "GameData", INI::parseGameDataDefinition }, - { "InGameUI", INI::parseInGameUIDefinition }, - { "Locomotor", INI::parseLocomotorTemplateDefinition }, - { "Language", INI::parseLanguageDefinition }, - { "MapCache", INI::parseMapCacheDefinition }, - { "MapData", INI::parseMapDataDefinition }, - { "MappedImage", INI::parseMappedImageDefinition }, - { "MiscAudio", INI::parseMiscAudio}, - { "Mouse", INI::parseMouseDefinition }, - { "MouseCursor", INI::parseMouseCursorDefinition }, - { "MultiplayerColor", INI::parseMultiplayerColorDefinition }, - { "MultiplayerStartingMoneyChoice", INI::parseMultiplayerStartingMoneyChoiceDefinition }, - { "OnlineChatColors", INI::parseOnlineChatColorDefinition }, - { "MultiplayerSettings",INI::parseMultiplayerSettingsDefinition }, - { "MusicTrack", INI::parseMusicTrackDefinition }, - { "Object", INI::parseObjectDefinition }, - { "ObjectCreationList", INI::parseObjectCreationListDefinition }, - { "ObjectReskin", INI::parseObjectReskinDefinition }, - { "ObjectExtend", INI::parseObjectExtendDefinition }, - { "ParticleSystem", INI::parseParticleSystemDefinition }, - { "PlayerTemplate", INI::parsePlayerTemplateDefinition }, - { "Road", INI::parseTerrainRoadDefinition }, - { "Science", INI::parseScienceDefinition }, - { "Rank", INI::parseRankDefinition }, - { "SpecialPower", INI::parseSpecialPowerDefinition }, - { "ShellMenuScheme", INI::parseShellMenuSchemeDefinition }, - { "Terrain", INI::parseTerrainDefinition }, - { "Upgrade", INI::parseUpgradeDefinition }, - { "Video", INI::parseVideoDefinition }, - { "WaterSet", INI::parseWaterSettingDefinition }, - { "WaterTransparency", INI::parseWaterTransparencyDefinition}, - { "Weather", INI::parseWeatherDefinition}, - { "Weapon", INI::parseWeaponTemplateDefinition }, - { "WebpageURL", INI::parseWebpageURLDefinition }, - { "HeaderTemplate", INI::parseHeaderTemplateDefinition }, - { "StaticGameLOD", INI::parseStaticGameLODDefinition }, - { "DynamicGameLOD", INI::parseDynamicGameLODDefinition }, - { "LODPreset", INI::parseLODPreset }, - { "BenchProfile", INI::parseBenchProfile }, - { "ReallyLowMHz", parseReallyLowMHz }, - { "ScriptAction", ScriptEngine::parseScriptAction }, - { "ScriptCondition", ScriptEngine::parseScriptCondition }, - - { NULL, NULL }, // keep this last! -}; - - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -Bool INI::isValidINIFilename( const char *filename ) -{ - if( filename == NULL ) - return FALSE; - - Int len = strlen( filename ); - if( len < 3 ) - return FALSE; - - if( filename[ len - 1 ] != 'I' && filename[ len - 1 ] != 'i' ) - return FALSE; - - if( filename[ len - 2 ] != 'N' && filename[ len - 2 ] != 'n' ) - return FALSE; - - if( filename[ len - 3 ] != 'I' && filename[ len - 3 ] != 'i' ) - return FALSE; - - return TRUE; - -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -INI::INI( void ) -{ - - m_file = NULL; - m_readBufferNext=m_readBufferUsed=0; - m_filename = "None"; - m_loadType = INI_LOAD_INVALID; - m_lineNum = 0; - m_seps = " \n\r\t="; ///< make sure you update m_sepsPercent/m_sepsColon as well - m_sepsPercent = " \n\r\t=%%"; - m_sepsColon = " \n\r\t=:"; - m_sepsQuote = "\"\n="; ///< stop at " = EOL - m_blockEndToken = "END"; - m_endOfFile = FALSE; - m_buffer[0] = 0; -#ifdef DEBUG_CRASHING - m_curBlockStart[0] = 0; -#endif - -} // end INI - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -INI::~INI( void ) -{ - -} // end ~INI - -//------------------------------------------------------------------------------------------------- -/** Load all INI files in the specified directory (and subdirectories if indicated). - * If we are to load subdirectories, we will load them *after* we load all the - * files in the current directory */ -//------------------------------------------------------------------------------------------------- -void INI::loadDirectory( AsciiString dirName, Bool subdirs, INILoadType loadType, Xfer *pXfer ) -{ - // sanity - if( dirName.isEmpty() ) - throw INI_INVALID_DIRECTORY; - - try - { - FilenameList filenameList; - dirName.concat('\\'); - TheFileSystem->getFileListInDirectory(dirName, "*.ini", filenameList, TRUE); - // Load the INI files in the dir now, in a sorted order. This keeps things the same between machines - // in a network game. - FilenameList::const_iterator it = filenameList.begin(); - while (it != filenameList.end()) - { - AsciiString tempname; - tempname = (*it).str() + dirName.getLength(); - - if ((tempname.find('\\') == NULL) && (tempname.find('/') == NULL)) { - // this file doesn't reside in a subdirectory, load it first. - load( *it, loadType, pXfer ); - } - ++it; - } - - it = filenameList.begin(); - while (it != filenameList.end()) - { - AsciiString tempname; - tempname = (*it).str() + dirName.getLength(); - - if ((tempname.find('\\') != NULL) || (tempname.find('/') != NULL)) { - load( *it, loadType, pXfer ); - } - ++it; - } - } - catch (...) - { - // propagate the exception - throw; - } - -} // end loadDirectory - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void INI::prepFile( AsciiString filename, INILoadType loadType ) -{ - // if we have a file open already -- we can't do another one - if( m_file != NULL ) - { - - DEBUG_CRASH(( "INI::load, cannot open file '%s', file already open\n", filename.str() )); - throw INI_FILE_ALREADY_OPEN; - - } // end if - - // open the file - m_file = TheFileSystem->openFile(filename.str(), File::READ); - if( m_file == NULL ) - { - - DEBUG_CRASH(( "INI::load, cannot open file '%s'\n", filename.str() )); - throw INI_CANT_OPEN_FILE; - - } // end if - - m_file = m_file->convertToRAMFile(); - - // save our filename - m_filename = filename; - - // save our load time - m_loadType = loadType; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void INI::unPrepFile() -{ - // close the file - m_file->close(); - m_file = NULL; - m_readBufferUsed=m_readBufferNext=0; - m_filename = "None"; - m_loadType = INI_LOAD_INVALID; - m_lineNum = 0; - m_endOfFile = FALSE; - s_xfer = NULL; -} - -//------------------------------------------------------------------------------------------------- -static INIBlockParse findBlockParse(const char* token) -{ - for (const BlockParse* parse = theTypeTable; parse->token; ++parse) - { - if (strcmp( parse->token, token ) == 0) - { - return parse->parse; - } - } - return NULL; -} - -//------------------------------------------------------------------------------------------------- -static INIFieldParseProc findFieldParse(const FieldParse* parseTable, const char* token, int& offset, const void*& userData) -{ - const FieldParse* parse = parseTable; - for (; parse->token; ++parse) - { - if (strcmp( parse->token, token ) == 0) - { - offset = parse->offset; - userData = parse->userData; - return parse->parse; - } - } - - if (!parse->token && parse->parse) - { - offset = parse->offset; - userData = token; - return parse->parse; - } - else - { - return NULL; - } -} - -//------------------------------------------------------------------------------------------------- -/** Load and parse an INI file */ -//------------------------------------------------------------------------------------------------- -void INI::load( AsciiString filename, INILoadType loadType, Xfer *pXfer ) -{ - setFPMode(); // so we have consistent Real values for GameLogic -MDC - - s_xfer = pXfer; - prepFile(filename, loadType); - - try - { - - // read all lines in the file - DEBUG_ASSERTCRASH( m_endOfFile == FALSE, ("INI::load, EOF at the beginning!\n") ); - while( m_endOfFile == FALSE ) - { - // read this line - readLine(); - - AsciiString currentLine = m_buffer; - - // the first word is the type of data we're processing - const char *token = strtok( m_buffer, m_seps ); - if( token ) - { - INIBlockParse parse = findBlockParse(token); - if (parse) - { - #ifdef DEBUG_CRASHING - strcpy(m_curBlockStart, m_buffer); - #endif - try { - (*parse)( this ); - - } catch (...) { - DEBUG_CRASH(("Error parsing block '%s' in INI file '%s'\n", token, m_filename.str()) ); - char buff[1024]; - sprintf(buff, "Error parsing INI file '%s' (Line: '%s')\n", m_filename.str(), currentLine.str()); - - throw INIException(buff); - } - #ifdef DEBUG_CRASHING - strcpy(m_curBlockStart, "NO_BLOCK"); - #endif - } - else - { - DEBUG_ASSERTCRASH( 0, ("[LINE: %d - FILE: '%s'] Unknown block '%s'\n", - getLineNum(), getFilename().str(), token ) ); - throw INI_UNKNOWN_TOKEN; - } - - } // end if - - } // end while - } - catch (...) - { - unPrepFile(); - - // propagate the exception. - throw; - } - - unPrepFile(); - -} // end load - -//------------------------------------------------------------------------------------------------- -/** Read a line from the already open file. Any comments will be remved and - * therefore ignored from any given line */ -//------------------------------------------------------------------------------------------------- -void INI::readLine( void ) -{ - // sanity - DEBUG_ASSERTCRASH( m_file, ("readLine(), file pointer is NULL\n") ); - - if (m_endOfFile) - *m_buffer=0; - else - { - char *p=m_buffer; - while (p!=m_buffer+INI_MAX_CHARS_PER_LINE) - { - // get next character - if (m_readBufferNext==m_readBufferUsed) - { - // refill buffer - m_readBufferNext=0; - m_readBufferUsed=m_file->read(m_readBuffer,INI_READ_BUFFER); - - // EOF? - if (!m_readBufferUsed) - { - m_endOfFile=true; - *p=0; - break; - } - } - *p=m_readBuffer[m_readBufferNext++]; - - // CR? - if (*p=='\n') - { - *p=0; - break; - } - - DEBUG_ASSERTCRASH(*p != '\t', ("tab characters are not allowed in INI files (%s). please check your editor settings. Line Number %d\n",m_filename.str(), getLineNum())); - - // comment? - if (*p==';') - *p=0; - // whitespace? - else if (*p>0&&*p<32) - *p=' '; - p++; - } - *p=0; - - // increase our line count - m_lineNum++; - - // check for at the max - if ( p == m_buffer+INI_MAX_CHARS_PER_LINE ) - { - - DEBUG_ASSERTCRASH( 0, ("Buffer too small (%d) and was truncated, increase INI_MAX_CHARS_PER_LINE\n", - INI_MAX_CHARS_PER_LINE) ); - - } // end if - } - - if (s_xfer) - { - s_xfer->xferUser( m_buffer, sizeof( char ) * strlen( m_buffer ) ); - //DEBUG_LOG(("Xfer val is now 0x%8.8X in %s, line %s\n", ((XferCRC *)s_xfer)->getCRC(), - //m_filename.str(), m_buffer)); - } -} - -//------------------------------------------------------------------------------------------------- -/** Parse UnsignedByte from buffer and assign at location 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parseUnsignedByte( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - Int value = scanInt(token); - if (value < 0 || value > 255) - { - DEBUG_CRASH(("Bad value INI::parseUnsignedByte")); - throw ERROR_BUG; - } - *(Byte *)store = (Byte)value; -} - -//------------------------------------------------------------------------------------------------- -/** Parse signed short from buffer and assign at location 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parseShort( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - Int value = scanInt(token); - if (value < -32768 || value > 32767) - { - DEBUG_CRASH(("Bad value INI::parseShort")); - throw ERROR_BUG; - } - *(Short *)store = (Short)value; -} - -//------------------------------------------------------------------------------------------------- -/** Parse unsigned short from buffer and assign at location 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parseUnsignedShort( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - Int value = scanInt(token); - if (value < 0 || value > 65535) - { - DEBUG_CRASH(("Bad value INI::parseUnsignedShort")); - throw ERROR_BUG; - } - *(UnsignedShort *)store = (UnsignedShort)value; -} - -//------------------------------------------------------------------------------------------------- -/** Parse integer from buffer and assign at location 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parseInt( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - *(Int *)store = scanInt(token); - -} - -//------------------------------------------------------------------------------------------------- -/** Parse unsigned integer from buffer and assign at location 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parseUnsignedInt( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - *(UnsignedInt *)store = scanUnsignedInt(token); - -} - -//------------------------------------------------------------------------------------------------- -/** Parse real from buffer and assign at location 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parseReal( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - *(Real *)store = scanReal(token); - -} - -//------------------------------------------------------------------------------------------------- -/** Parse real from buffer and assign at location 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parsePositiveNonZeroReal( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - *(Real *)store = scanReal(token); - if (*(Real *)store <= 0.0f) - { - DEBUG_CRASH(("invalid Real value %f -- expected > 0\n",*(Real*)store)); - throw INI_INVALID_DATA; - } - -} - -//------------------------------------------------------------------------------------------------- -/** Parse a degree value (0 to 360) and store the radian value of that degree - * in a Real */ -//------------------------------------------------------------------------------------------------- -void INI::parseAngleReal( INI *ini, void * /*instance*/, - void *store, const void *userData ) -{ - const char *token = ini->getNextToken(); - - const Real RADS_PER_DEGREE = PI / 180.0f; - *(Real *)store = scanReal( token ) * RADS_PER_DEGREE; - -} - -//------------------------------------------------------------------------------------------------- -/** Parse an angular velocity in degrees-per-sec and store the rads-per-frame value of that degree - * in a Real */ -//------------------------------------------------------------------------------------------------- -void INI::parseAngularVelocityReal( INI *ini, void * /*instance*/, - void *store, const void *userData ) -{ - const char *token = ini->getNextToken(); - - // scan the int and convert to radian and store as a real - *(Real *)store = ConvertAngularVelocityInDegreesPerSecToRadsPerFrame(scanReal( token )); - -} - -//------------------------------------------------------------------------------------------------- -/** Parse Bool from buffer and assign at location 'store'. The buffer token must - * be in the form of a string "Yes" or "No" (case is ignored) */ -//------------------------------------------------------------------------------------------------- -void INI::parseBool( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - *(Bool*)store = INI::scanBool(ini->getNextToken()); -} - -//------------------------------------------------------------------------------------------------- -/** Parse Bool from buffer; if true, or in MASK, otherwise and out MASK. The buffer token must - * be in the form of a string "Yes" or "No" (case is ignored) */ -//------------------------------------------------------------------------------------------------- -void INI::parseBitInInt32( INI *ini, void *instance, void *store, const void* userData ) -{ - UnsignedInt* s = (UnsignedInt*)store; - UnsignedInt mask = (UnsignedInt)userData; - - if (INI::scanBool(ini->getNextToken())) - *s |= mask; - else - *s &= ~mask; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -/*static*/ Bool INI::scanBool(const char* token) -{ - // translate string yes/no into TRUE/FALSE - if( stricmp( token, "yes" ) == 0 ) - return TRUE; - else if( stricmp( token, "no" ) == 0 ) - return FALSE; - else - { - DEBUG_CRASH(("invalid boolean token %s -- expected Yes or No\n",token)); - throw INI_INVALID_DATA; - return false; // keep compiler happy - } - -} - -//------------------------------------------------------------------------------------------------- -/** Parse an *ASCII* string from buffer and assign at location 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parseAsciiString( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - AsciiString* asciiString = (AsciiString *)store; - *asciiString = ini->getNextAsciiString(); -} - -//------------------------------------------------------------------------------------------------- -/** Parse an *ASCII* string from buffer and assign at location 'store'. Has better support for quoted strings. -We don't really need this function, but parseString() is broken and we want to leave it broken to -maintain existing code. - */ -//------------------------------------------------------------------------------------------------- -void INI::parseQuotedAsciiString( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - AsciiString* asciiString = (AsciiString *)store; - *asciiString = ini->getNextQuotedAsciiString(); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void INI::parseAsciiStringVector( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - std::vector* asv = (std::vector*)store; - asv->clear(); - for (const char *token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) - { - asv->push_back(token); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void INI::parseAsciiStringVectorAppend( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - std::vector* asv = (std::vector*)store; - // nope, don't clear. duh. - // asv->clear(); - for (const char *token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) - { - asv->push_back(token); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -/* static */void INI::parseScienceVector( INI *ini, void * /*instance*/, void *store, const void *userData ) -{ - ScienceVec* asv = (ScienceVec*)store; - asv->clear(); - for (const char *token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) - { - if (stricmp(token, "None") == 0) - { - asv->clear(); - return; - } - asv->push_back(INI::scanScience( token )); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -/* static */void INI::parseWeaponBonusVector( INI *ini, void * /*instance*/, void *store, const void *userData ) -{ - WeaponBonusConditionTypeVec* asv = (WeaponBonusConditionTypeVec*)store; - asv->clear(); - for (const char *token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) - { - if (stricmp(token, "None") == 0) - { - asv->clear(); - return; - } - asv->push_back((WeaponBonusConditionType)INI::scanIndexList(token, TheWeaponBonusNames)); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -/* static */void INI::parseWeaponBonusVectorKeepDefault(INI* ini, void* /*instance*/, void* store, const void* userData) -{ - WeaponBonusConditionTypeVec* asv = (WeaponBonusConditionTypeVec*)store; - // asv->clear(); - for (const char* token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) - { - if (stricmp(token, "None") == 0) - { - asv->clear(); - return; - } - asv->push_back((WeaponBonusConditionType)INI::scanIndexList(token, TheWeaponBonusNames)); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -AsciiString INI::getNextQuotedAsciiString() -{ - AsciiString result; - char buff[INI_MAX_CHARS_PER_LINE]; - - const char *token = getNextTokenOrNull(); // if null, just leave an empty string - if (token != NULL) - { - if (token[0] != '\"') - { - // if token is simply " - result.set( token ); // Start following the " - } - else - { int strLen=0; - Bool done=FALSE; - if ((strLen=strlen(token)) > 1) - { - strcpy(buff, &token[1]); //skip the starting quote - //Check for end of quoted string. Checking here fixes cases where quoted string on same line with other data. - if (buff[strLen-2]=='"') //skip ending quote if present - { buff[strLen-2]='\0'; - done=TRUE; - } - } - - if (!done) - { - token = getNextToken(getSepsQuote()); - - if (strlen(token) > 1 && token[1] != '\t') - { - strcat(buff, " "); - strcat(buff, token); - } - else - { Int buflen=strlen(buff); - if (buff[buflen-1]=='\"') - buff[buflen-1]='\0'; - } - } - result.set(buff); - } - } - return result; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -AsciiString INI::getNextAsciiString() -{ - AsciiString result; - - const char *token = getNextTokenOrNull(); // if null, just leave an empty string - if (token != NULL) - { - if (token[0] != '\"') - { - // if token is simply " - result.set( token ); // Start following the " - } - else - { - static char buff[INI_MAX_CHARS_PER_LINE]; - buff[0] = 0; - if (strlen(token) > 1) - { - strcpy(buff, &token[1]); - } - - token = getNextTokenOrNull(getSepsQuote()); - if (token) { - if (strlen(token) > 1 && token[1] != '\t') - { - strcat(buff, " "); - } - strcat(buff, token); - result.set(buff); - } else { - Int len = strlen(buff); - if (len && buff[len-1] == '"') { // strip off trailing quote jba. [2/12/2003] - buff[len-1] = 0; - } - result.set(buff); - } - } - } - return result; -} - -//------------------------------------------------------------------------------------------------- -/** Parse a string label, get the *translated* actual text from the label and store - * into a *UNICODE* string. */ -//------------------------------------------------------------------------------------------------- -void INI::parseAndTranslateLabel( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - - // translate - UnicodeString translated = TheGameText->fetch( token ); - if( translated.isEmpty() ) - throw INI_INVALID_DATA; - - // save the translated text - UnicodeString *theString = (UnicodeString *)store; - theString->set( translated.str() ); - -} // end parseAndTranslateLabel - -//------------------------------------------------------------------------------------------------- -/** Parse a string label assumed as an image as part of the image collection. Translate - * to an image pointer for storage */ -//------------------------------------------------------------------------------------------------- -void INI::parseMappedImage( INI *ini, void * /*instance*/, void *store, const void *userData ) -{ - const char *token = ini->getNextToken(); - - if( TheMappedImageCollection ) - { - typedef const Image* ConstImagePtr; - *(ConstImagePtr*)store = TheMappedImageCollection->findImageByName( AsciiString( token ) ); - } - - //KM: If we are in the worldbuilder, we want to parse commandbuttons for informational purposes, - //but we don't care about the images -- because we never access them. In RTS/GUIEdit, they always - //exist -- and in those cases, it will never call this code anyways because it'll throw long before. - //else - // throw INI_UNKNOWN_ERROR; - -} // end parseMappedImage - -// ------------------------------------------------------------------------------------------------ -/** Parse a string label assumed as a Anim2D template name. Translate that name to an - * actual template pointer for storage */ -// ------------------------------------------------------------------------------------------------ -/*static*/ void INI::parseAnim2DTemplate( INI *ini, void *instance, void *store, const void *userData ) -{ - const char *token = ini->getNextToken(); - - if( TheAnim2DCollection ) - { - Anim2DTemplate **anim2DTemplate = (Anim2DTemplate **)store; - *anim2DTemplate = TheAnim2DCollection->findTemplate( AsciiString( token ) ); - } // end if - else - { - - DEBUG_CRASH(( "INI::parseAnim2DTemplate - TheAnim2DCollection is NULL\n" )); - throw INI_UNKNOWN_ERROR; - - } // end else - -} // end parseAnim2DTemplate - -//------------------------------------------------------------------------------------------------- -/** Parse a percent in int or real form such as "23%" or "95.4%" and assign - * to location 'store' as a number from 0.0 to 1.0 */ -//------------------------------------------------------------------------------------------------- -void INI::parsePercentToReal( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(ini->getSepsPercent()); - Real *theReal = (Real *)store; - *theReal = scanPercentToReal(token); - -} // end parsePercentToReal - -//------------------------------------------------------------------------------------------------- -/** 'store' points to an 32 bit unsigned integer. We will zero that integer, parse each token - * in the buffer, if the token is in the userData table of strings, we will set the - * according bit flag for it */ -//------------------------------------------------------------------------------------------------- -void INI::parseBitString8( INI* ini, void * /*instance*/, void *store, const void* userData ) -{ - UnsignedInt tmp; - INI::parseBitString32(ini, NULL, &tmp, userData); - if (tmp & 0xffffff00) - { - DEBUG_CRASH(("Bad bitstring list INI::parseBitString8")); - throw ERROR_BUG; - } - *(Byte*)store = (Byte)tmp; -} - -//------------------------------------------------------------------------------------------------- -/** 'store' points to an 32 bit unsigned integer. We will zero that integer, parse each token - * in the buffer, if the token is in the userData table of strings, we will set the - * according bit flag for it */ -//------------------------------------------------------------------------------------------------- -void INI::parseBitString32( INI* ini, void * /*instance*/, void *store, const void* userData ) -{ - ConstCharPtrArray flagList = (ConstCharPtrArray)userData; - UnsignedInt *bits = (UnsignedInt *)store; - - if( flagList == NULL || flagList[ 0 ] == NULL) - { - DEBUG_ASSERTCRASH( flagList, ("INTERNAL ERROR! parseBitString32: No flag list provided!\n") ); - throw INI_INVALID_NAME_LIST; - } - - Bool foundNormal = false; - Bool foundAddOrSub = false; - - // loop through all tokens - for (const char *token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) - { - if (stricmp(token, "NONE") == 0) - { - if (foundNormal || foundAddOrSub) - { - DEBUG_CRASH(("you may not mix normal and +- ops in bitstring lists")); - throw INI_INVALID_NAME_LIST; - } - *bits = 0; - break; - } - - if (token[0] == '+') - { - if (foundNormal) - { - DEBUG_CRASH(("you may not mix normal and +- ops in bitstring lists")); - throw INI_INVALID_NAME_LIST; - } - Int bitIndex = INI::scanIndexList(token+1, flagList); // this throws if the token is not found - *bits |= (1 << bitIndex); - foundAddOrSub = true; - } - else if (token[0] == '-') - { - if (foundNormal) - { - DEBUG_CRASH(("you may not mix normal and +- ops in bitstring lists")); - throw INI_INVALID_NAME_LIST; - } - Int bitIndex = INI::scanIndexList(token+1, flagList); // this throws if the token is not found - *bits &= ~(1 << bitIndex); - foundAddOrSub = true; - } - else - { - if (foundAddOrSub) - { - DEBUG_CRASH(("you may not mix normal and +- ops in bitstring lists")); - throw INI_INVALID_NAME_LIST; - } - - if (!foundNormal) - *bits = 0; - - Int bitIndex = INI::scanIndexList(token, flagList); // this throws if the token is not found - *bits |= (1 << bitIndex); - foundNormal = true; - } - } -} - -//------------------------------------------------------------------------------------------------- -/** Parse a color in the form of - * - * RGB_COLOR = R:100 G:114 B:245 - * and store in "RGBColor" structure pointed to by 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parseRGBColor( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char* names[3] = { "R", "G", "B" }; - Int colors[3]; - for( Int i = 0; i < 3; i++ ) - { - colors[i] = scanInt(ini->getNextSubToken(names[i])); - if( colors[ i ] < 0 ) - throw INI_INVALID_DATA; - if( colors[ i ] > 255 ) - throw INI_INVALID_DATA; - } - - // assign the color components to the "RGBColor" pointer at 'store' - RGBColor *theColor = (RGBColor *)store; - theColor->red = (Real)colors[ 0 ] / 255.0f; - theColor->green = (Real)colors[ 1 ] / 255.0f; - theColor->blue = (Real)colors[ 2 ] / 255.0f; - -} - - -//------------------------------------------------------------------------------------------------- -/** Parse a color in the form of - * - * RGB_COLOR = R:0.5 G:0.3 B:0.6 - * and store in "RGBColor" structure pointed to by 'store' - * Negative numbers, and values greater 1 are allowed! */ - //------------------------------------------------------------------------------------------------- -void INI::parseRGBColorReal(INI* ini, void* /*instance*/, void* store, const void* /*userData*/) -{ - const char* names[3] = { "R", "G", "B" }; - Real colors[3]; - for (Int i = 0; i < 3; i++) - { - colors[i] = scanReal(ini->getNextSubToken(names[i])); - //if (colors[i] < -255) - // throw INI_INVALID_DATA; - //if (colors[i] > 255) - // throw INI_INVALID_DATA; - } - - // assign the color components to the "RGBColor" pointer at 'store' - RGBColor* theColor = (RGBColor*)store; - theColor->red = colors[0]; - theColor->green = colors[1]; - theColor->blue = colors[2]; - -} - - -//------------------------------------------------------------------------------------------------- -/** Parse a color in the form of - * - * RGB_COLOR = R:100 G:114 B:245 [A:233] - * and store in "RGBAColorInt" structure pointed to by 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parseRGBAColorInt( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char* names[4] = { "R", "G", "B", "A" }; - Int colors[4]; - for( Int i = 0; i < 4; i++ ) - { - const char* token = ini->getNextTokenOrNull(ini->getSepsColon()); - if (token == NULL) - { - if (i < 3) - { - throw INI_INVALID_DATA; - } - else - { - // it's ok for A to be omitted. - colors[i] = 255; - } - } - else - { - // if present, the token must match. - if (stricmp(token, names[i]) != 0) - { - throw INI_INVALID_DATA; - } - colors[i] = scanInt(ini->getNextToken(ini->getSepsColon())); - } - if( colors[ i ] < 0 ) - throw INI_INVALID_DATA; - if( colors[ i ] > 255 ) - throw INI_INVALID_DATA; - } - - // - // assign the color components to the "RGBColorInt" pointer at 'store', keep - // the numbers as between 0 and 255 - // - RGBAColorInt *theColor = (RGBAColorInt *)store; - theColor->red = colors[ 0 ]; - theColor->green = colors[ 1 ]; - theColor->blue = colors[ 2 ]; - theColor->alpha = colors[ 3 ]; - -} // end parseRGBAColorInt - -//------------------------------------------------------------------------------------------------- -/** Parse a color in the form of - * - * RGB_COLOR = R:100 G:114 B:245 [A:233] - * and store in "Color" structure pointed to by 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parseColorInt( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char* names[4] = { "R", "G", "B", "A" }; - Int colors[4]; - for( Int i = 0; i < 4; i++ ) - { - const char* token = ini->getNextTokenOrNull(ini->getSepsColon()); - if (token == NULL) - { - if (i < 3) - { - throw INI_INVALID_DATA; - } - else - { - // it's ok for A to be omitted. - colors[i] = 255; - } - } - else - { - // if present, the token must match. - if (stricmp(token, names[i]) != 0) - { - throw INI_INVALID_DATA; - } - colors[i] = scanInt(ini->getNextToken(ini->getSepsColon())); - } - if( colors[ i ] < 0 ) - throw INI_INVALID_DATA; - if( colors[ i ] > 255 ) - throw INI_INVALID_DATA; - } - - // - // assign the color components to the "Color" pointer at 'store', keep - // the numbers as between 0 and 255 - // - Color *theColor = (Color *)store; - *theColor = GameMakeColor(colors[0], colors[1], colors[2], colors[3]); - -} // end parseColorInt - -//------------------------------------------------------------------------------------------------- -/** Parse a 3D coordinate of reals in the form of: - * FIELD_NAME = X:400 Y:-214.3 Z:8.6 */ -//------------------------------------------------------------------------------------------------- -void INI::parseCoord3D( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - Coord3D *theCoord = (Coord3D *)store; - - theCoord->x = scanReal(ini->getNextSubToken("X")); - theCoord->y = scanReal(ini->getNextSubToken("Y")); - theCoord->z = scanReal(ini->getNextSubToken("Z")); - -} // end parseCoord3D - -//------------------------------------------------------------------------------------------------- -/** Parse a 2D coordinate of reals in the form of: - * FIELD_NAME = X:400 Y:-214.3 */ -//------------------------------------------------------------------------------------------------- -void INI::parseCoord2D( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - Coord2D *theCoord = (Coord2D *)store; - - theCoord->x = scanReal(ini->getNextSubToken("X")); - theCoord->y = scanReal(ini->getNextSubToken("Y")); - -} // end parseCoord2D - -//------------------------------------------------------------------------------------------------- -/** Parse a 2D coordinate of Ints in the form of: - * FIELD_NAME = X:400 Y:-214 */ -//------------------------------------------------------------------------------------------------- -void INI::parseICoord2D( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - ICoord2D *theCoord = (ICoord2D *)store; - - theCoord->x = scanInt(ini->getNextSubToken("X")); - theCoord->y = scanInt(ini->getNextSubToken("Y")); - -} // end parseICoord2D - -//------------------------------------------------------------------------------------------------- -/** Parse an audio event and assign to the 'AudioEventRTS*' at store */ -//------------------------------------------------------------------------------------------------- -void INI::parseDynamicAudioEventRTS( INI *ini, void * /*instance*/, void *store, const void* userData ) -{ - const char *token = ini->getNextToken(); - DynamicAudioEventRTS** theSound = (DynamicAudioEventRTS**)store; - - // translate the string into a sound - if (stricmp(token, "NoSound") == 0) - { - if (*theSound) - { - (*theSound)->deleteInstance(); - *theSound = NULL; - } - } - else - { - if (*theSound == NULL) - *theSound = newInstance(DynamicAudioEventRTS); - (*theSound)->m_event.setEventName(AsciiString(token)); - } - - if (*theSound) - TheAudio->getInfoForAudioEvent(&(*theSound)->m_event); -} - -//------------------------------------------------------------------------------------------------- -/** Parse an audio event and assign to the 'AudioEventRTS*' at store */ -//------------------------------------------------------------------------------------------------- -void INI::parseAudioEventRTS( INI *ini, void * /*instance*/, void *store, const void* userData ) -{ - const char *token = ini->getNextToken(); - - AudioEventRTS *theSound = (AudioEventRTS*)store; - - // translate the string into a sound - if (stricmp(token, "NoSound") != 0) { - theSound->setEventName(AsciiString(token)); - } - - TheAudio->getInfoForAudioEvent(theSound); -} - -//------------------------------------------------------------------------------------------------- -/** Parse an ThingTemplate and assign to the 'ThingTemplate *' at store */ -//------------------------------------------------------------------------------------------------- -void INI::parseThingTemplate( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - - if (!TheThingFactory) - { - DEBUG_CRASH(("TheThingFactory not inited yet")); - throw ERROR_BUG; - } - - typedef const ThingTemplate *ConstThingTemplatePtr; - ConstThingTemplatePtr* theThingTemplate = (ConstThingTemplatePtr*)store; - - if (stricmp(token, "None") == 0) - { - *theThingTemplate = NULL; - } - else - { - const ThingTemplate *tt = TheThingFactory->findTemplate(token); // could be null! - DEBUG_ASSERTCRASH(tt, ("ThingTemplate %s not found!\n",token)); - // assign it, even if null! - *theThingTemplate = tt; - } - -} - -//------------------------------------------------------------------------------------------------- -/** Parse an ArmorTemplate and assign to the 'ArmorTemplate *' at store */ -//------------------------------------------------------------------------------------------------- -void INI::parseArmorTemplate( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - - typedef const ArmorTemplate *ConstArmorTemplatePtr; - ConstArmorTemplatePtr* theArmorTemplate = (ConstArmorTemplatePtr*)store; - - if (stricmp(token, "None") == 0) - { - *theArmorTemplate = NULL; - } - else - { - const ArmorTemplate *tt = TheArmorStore->findArmorTemplate(token); // could be null! - DEBUG_ASSERTCRASH(tt, ("ArmorTemplate %s not found!\n",token)); - // assign it, even if null! - *theArmorTemplate = tt; - } - -} - -//------------------------------------------------------------------------------------------------- -/** Parse an WeaponTemplate and assign to the 'WeaponTemplate *' at store */ -//------------------------------------------------------------------------------------------------- -void INI::parseWeaponTemplate( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - - typedef const WeaponTemplate *ConstWeaponTemplatePtr; - ConstWeaponTemplatePtr* theWeaponTemplate = (ConstWeaponTemplatePtr*)store; - - const WeaponTemplate *tt = TheWeaponStore->findWeaponTemplate(token); // could be null! - DEBUG_ASSERTCRASH(tt || stricmp(token, "None") == 0, ("WeaponTemplate %s not found!\n",token)); - // assign it, even if null! - *theWeaponTemplate = tt; - -} - -//------------------------------------------------------------------------------------------------- -/** Parse an FXList and assign to the 'FXList *' at store */ -//------------------------------------------------------------------------------------------------- -void INI::parseFXList( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - - typedef const FXList *ConstFXListPtr; - ConstFXListPtr* theFXList = (ConstFXListPtr*)store; - - const FXList *fxl = TheFXListStore->findFXList(token); // could be null! - DEBUG_ASSERTCRASH(fxl != NULL || stricmp(token, "None") == 0, ("FXList %s not found!\n",token)); - // assign it, even if null! - *theFXList = fxl; - -} - -//------------------------------------------------------------------------------------------------- -/** Parse a particle system and assign to 'ParticleSystemTemplate *' at store */ -//------------------------------------------------------------------------------------------------- -void INI::parseParticleSystemTemplate( INI *ini, void * /*instance*/, void *store, const void *userData ) -{ - const char *token = ini->getNextToken(); - - const ParticleSystemTemplate *pSystemT = TheParticleSystemManager->findTemplate( AsciiString( token ) ); - DEBUG_ASSERTCRASH( pSystemT || stricmp( token, "None" ) == 0, ("ParticleSystem %s not found!\n",token) ); - - typedef const ParticleSystemTemplate* ConstParticleSystemTemplatePtr; - ConstParticleSystemTemplatePtr* theParticleSystemTemplate = (ConstParticleSystemTemplatePtr*)store; - - *theParticleSystemTemplate = pSystemT; - -} // end parseParticleSystemTemplate - -//------------------------------------------------------------------------------------------------- -/** Parse an DamageFX and assign to the 'DamageFX *' at store */ -//------------------------------------------------------------------------------------------------- -void INI::parseDamageFX( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - - typedef const DamageFX *ConstDamageFXPtr; - ConstDamageFXPtr* theDamageFX = (ConstDamageFXPtr*)store; - - if (stricmp(token, "None") == 0) - { - *theDamageFX = NULL; - } - else - { - const DamageFX *fxl = TheDamageFXStore->findDamageFX(token); // could be null! - DEBUG_ASSERTCRASH(fxl, ("DamageFX %s not found!\n",token)); - // assign it, even if null! - *theDamageFX = fxl; - } - -} - -//------------------------------------------------------------------------------------------------- -/** Parse an ObjectCreationList and assign to the 'ObjectCreationList *' at store */ -//------------------------------------------------------------------------------------------------- -void INI::parseObjectCreationList( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - - typedef const ObjectCreationList *ConstObjectCreationListPtr; - ConstObjectCreationListPtr* theObjectCreationList = (ConstObjectCreationListPtr*)store; - - const ObjectCreationList *ocl = TheObjectCreationListStore->findObjectCreationList(token); // could be null! - DEBUG_ASSERTCRASH(ocl || stricmp(token, "None") == 0, ("ObjectCreationList %s not found!\n",token)); - // assign it, even if null! - *theObjectCreationList = ocl; - -} - -//------------------------------------------------------------------------------------------------- -/** Parse a upgrade template string and store as template pointer */ -//------------------------------------------------------------------------------------------------- -void INI::parseUpgradeTemplate( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - - if (!TheUpgradeCenter) - { - DEBUG_CRASH(("TheUpgradeCenter not inited yet")); - throw ERROR_BUG; - } - - const UpgradeTemplate *uu = TheUpgradeCenter->findUpgrade( AsciiString( token ) ); - DEBUG_ASSERTCRASH( uu || stricmp( token, "None" ) == 0, ("Upgrade %s not found!\n",token) ); - - typedef const UpgradeTemplate* ConstUpgradeTemplatePtr; - ConstUpgradeTemplatePtr* theUpgradeTemplate = (ConstUpgradeTemplatePtr *)store; - *theUpgradeTemplate = uu; -} - -//------------------------------------------------------------------------------------------------- -/** Parse a special power template string and store as template pointer */ -//------------------------------------------------------------------------------------------------- -void INI::parseSpecialPowerTemplate( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - - if (!TheSpecialPowerStore) - { - DEBUG_CRASH(("TheSpecialPowerStore not inited yet")); - throw ERROR_BUG; - } - - const SpecialPowerTemplate *sPowerT = TheSpecialPowerStore->findSpecialPowerTemplate( AsciiString( token ) ); - if( !sPowerT && stricmp( token, "None" ) != 0 ) - { - DEBUG_CRASH( ("[LINE: %d in '%s'] Specialpower %s not found!\n", ini->getLineNum(), ini->getFilename().str(), token) ); - } - - typedef const SpecialPowerTemplate* ConstSpecialPowerTemplatePtr; - ConstSpecialPowerTemplatePtr* theSpecialPowerTemplate = (ConstSpecialPowerTemplatePtr *)store; - *theSpecialPowerTemplate = sPowerT; -} - -//------------------------------------------------------------------------------------------------- -/** Parse a science string and store as science type */ -//------------------------------------------------------------------------------------------------- -/* static */void INI::parseScience( INI *ini, void * /*instance*/, void *store, const void *userData ) -{ - const char *token = ini->getNextToken(); - - if (!TheScienceStore) - { - DEBUG_CRASH(("TheScienceStore not inited yet")); - throw ERROR_BUG; - } - - *((ScienceType *)store) = INI::scanScience(token); - -} - -//------------------------------------------------------------------------------------------------- -/** Parse a single string token, check for that token in the index list - * of names provided and store the index into that list. - * - * NOTE: Is is assumed that we are going to store the index into - * a 4 byte integer. This works well for INT and ENUM definitions */ -//------------------------------------------------------------------------------------------------- -void INI::parseIndexList( INI* ini, void * /*instance*/, void *store, const void* userData ) -{ - ConstCharPtrArray nameList = (ConstCharPtrArray)userData; - *(Int *)store = scanIndexList(ini->getNextToken(), nameList); -} - -//------------------------------------------------------------------------------------------------- -/** returns -1 if "None", otherwise like parseIndexList **/ -//------------------------------------------------------------------------------------------------- -void INI::parseIndexListOrNone(INI* ini, void* /*instance*/, void* store, const void* userData) -{ - const char* token = ini->getNextToken(); - if (stricmp(token, "None") == 0) { - *(Int*)store = -1; - } - else { - //like parseIndexList - ConstCharPtrArray nameList = (ConstCharPtrArray)userData; - *(Int*)store = scanIndexList(token, nameList); - } -} - -//------------------------------------------------------------------------------------------------- -/** Parse a single string token, check for that token in the index list - * of names provided and store the index into that list. - * - * NOTE: Is is assumed that we are going to store the index into - * a 4 byte integer. This works well for INT and ENUM definitions */ -//------------------------------------------------------------------------------------------------- -void INI::parseByteSizedIndexList( INI* ini, void * /*instance*/, void *store, const void* userData ) -{ - ConstCharPtrArray nameList = (ConstCharPtrArray)userData; - Int value = scanIndexList(ini->getNextToken(), nameList); - if (value < 0 || value > 255) - { - DEBUG_CRASH(("Bad index list INI::parseByteSizedIndexList")); - throw ERROR_BUG; - } - *(Byte *)store = (Byte)value; -} - -//------------------------------------------------------------------------------------------------- -/** Parse a single string token, check for that token in the index list - * of names provided and store the associated value into that list. - * - * NOTE: Is is assumed that we are going to store the index into - * a 4 byte integer. This works well for INT and ENUM definitions */ -//------------------------------------------------------------------------------------------------- -void INI::parseLookupList( INI* ini, void * /*instance*/, void *store, const void* userData ) -{ - ConstLookupListRecArray lookupList = (ConstLookupListRecArray)userData; - *(Int *)store = scanLookupList(ini->getNextToken(), lookupList); -} - -//------------------------------------------------------------------------------------------------- -/** Special Handling for None = -2 (Eva_NONE), otherwise like parseIndexList **/ -//------------------------------------------------------------------------------------------------- -void INI::parseEvaNameIndexList(INI* ini, void* /*instance*/, void* store, const void* userData) -{ - const char* token = ini->getNextToken(); - if (stricmp(token, "None") == 0) { - *(Int*)store = -2; - } - else { - //like parseIndexList - ConstCharPtrArray nameList = (ConstCharPtrArray)userData; - *(Int*)store = scanIndexList(token, nameList); - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - - -//------------------------------------------------------------------------------------------------- -void MultiIniFieldParse::add(const FieldParse* f, UnsignedInt e) -{ - if (m_count < MAX_MULTI_FIELDS) - { - m_fieldParse[m_count] = f; - m_extraOffset[m_count] = e; - ++m_count; - } - else - { - DEBUG_CRASH(("too many multi-fields in INI::initFromINIMultiProc")); - throw ERROR_BUG; - } -} - -//------------------------------------------------------------------------------------------------- -void INI::initFromINI( void *what, const FieldParse* parseTable ) -{ - MultiIniFieldParse p; - p.add(parseTable); - initFromINIMulti(what, p); -} - -//------------------------------------------------------------------------------------------------- -void INI::initFromINIMultiProc( void *what, BuildMultiIniFieldProc proc ) -{ - MultiIniFieldParse p; - (*proc)(p); - initFromINIMulti(what, p); -} - -//------------------------------------------------------------------------------------------------- -void INI::initFromINIMulti( void *what, const MultiIniFieldParse& parseTableList ) -{ - Bool done = FALSE; - - if( what == NULL ) - { - DEBUG_ASSERTCRASH( 0, ("INI::initFromINI - Invalid parameters supplied!\n") ); - throw INI_INVALID_PARAMS; - } - - // read each of the data fields - while( !done ) - { - - // read next line - readLine(); - - // check for end token - const char* field = strtok( m_buffer, INI::getSeps() ); - if( field ) - { - - if( stricmp( field, m_blockEndToken ) == 0 ) - { - done = TRUE; - } - else - { - Bool found = false; - for (int ptIdx = 0; ptIdx < parseTableList.getCount(); ++ptIdx) - { - int offset = 0; - const void* userData = 0; - INIFieldParseProc parse = findFieldParse(parseTableList.getNthFieldParse(ptIdx), field, offset, userData); - if (parse) - { - // parse this block and check for parse errors - try { - - (*parse)( this, what, (char *)what + offset + parseTableList.getNthExtraOffset(ptIdx), userData ); - - } catch (...) { - DEBUG_CRASH( ("[LINE: %d - FILE: '%s'] Error reading field '%s' of block '%s'\n", - INI::getLineNum(), INI::getFilename().str(), field, m_curBlockStart) ); - - - char buff[1024]; - sprintf(buff, "[LINE: %d - FILE: '%s'] Error reading field '%s'\n", INI::getLineNum(), INI::getFilename().str(), field); - throw INIException(buff); - } - - found = true; - break; - - } - } - - if (!found) - { - DEBUG_ASSERTCRASH( 0, ("[LINE: %d - FILE: '%s'] Unknown field '%s' in block '%s'\n", - INI::getLineNum(), INI::getFilename().str(), field, m_curBlockStart) ); - throw INI_UNKNOWN_TOKEN; - } - - } // end else - - } // end if - - // sanity check for reaching end of file with no closing end token - if( done == FALSE && INI::isEOF() == TRUE ) - { - - done = TRUE; - DEBUG_ASSERTCRASH( 0, ("Error parsing block '%s', in INI file '%s'. Missing '%s' token\n", - m_curBlockStart, getFilename().str(), m_blockEndToken) ); - throw INI_MISSING_END_TOKEN; - - } // end if - - } // end while - -} - -//------------------------------------------------------------------------------------------------- -/*static*/ const char* INI::getNextToken(const char* seps) -{ - if (!seps) seps = getSeps(); - const char *token = ::strtok(NULL, seps); - if (!token) - throw INI_INVALID_DATA; - return token; -} - -//------------------------------------------------------------------------------------------------- -/*static*/ const char* INI::getNextTokenOrNull(const char* seps) -{ - if (!seps) seps = getSeps(); - const char *token = ::strtok(NULL, seps); - return token; -} - -//------------------------------------------------------------------------------------------------- -/*static*/ ScienceType INI::scanScience(const char* token) -{ - return TheScienceStore->friend_lookupScience( token ); -} - -//------------------------------------------------------------------------------------------------- -/*static*/ Int INI::scanInt(const char* token) -{ - Int value; - if (sscanf( token, "%d", &value ) != 1) - throw INI_INVALID_DATA; - return value; -} - -//------------------------------------------------------------------------------------------------- -/*static*/ UnsignedInt INI::scanUnsignedInt(const char* token) -{ - UnsignedInt value; - if (sscanf( token, "%u", &value ) != 1) // unsigned int is %u, not %d - throw INI_INVALID_DATA; - return value; -} - -//------------------------------------------------------------------------------------------------- -/*static*/ Real INI::scanReal(const char* token) -{ - Real value; - if (sscanf( token, "%f", &value ) != 1) - throw INI_INVALID_DATA; - return value; -} - -//------------------------------------------------------------------------------------------------- -/*static*/ Real INI::scanPercentToReal(const char* token) -{ - Real value; - if (sscanf( token, "%f", &value ) != 1) - throw INI_INVALID_DATA; - return value / 100.0f; -} - -//------------------------------------------------------------------------------------------------- -/*static*/ Int INI::scanIndexList(const char* token, ConstCharPtrArray nameList) -{ - if( nameList == NULL || nameList[ 0 ] == NULL ) - { - - DEBUG_ASSERTCRASH( 0, ("INTERNAL ERROR! scanIndexList, invalid name list\n") ); - throw INI_INVALID_NAME_LIST; - - } - - // search for matching name - Int count = 0; - for(ConstCharPtrArray name = nameList; *name; name++, count++ ) - { - if( stricmp( *name, token ) == 0 ) - { - return count; - } - } - - DEBUG_CRASH(("token %s is not a valid member of the index list\n",token)); - throw INI_INVALID_DATA; - return 0; // never executed, but keeps compiler happy - -} -//------------------------------------------------------------------------------------------------- -/*static*/ Int INI::scanLookupList(const char* token, ConstLookupListRecArray lookupList) -{ - if( lookupList == NULL || lookupList[ 0 ].name == NULL ) - { - DEBUG_ASSERTCRASH( 0, ("INTERNAL ERROR! scanLookupList, invalid name list\n") ); - throw INI_INVALID_NAME_LIST; - } - - // search for matching name - Bool found = false; - for( const LookupListRec* lookup = &lookupList[0]; lookup->name; lookup++ ) - { - if( stricmp( lookup->name, token ) == 0 ) - { - return lookup->value; - found = true; - break; - } - } - - DEBUG_CRASH(("token %s is not a valid member of the lookup list\n",token)); - throw INI_INVALID_DATA; - return 0; // never executed, but keeps compiler happy - -} - -//------------------------------------------------------------------------------------------------- -const char* INI::getNextSubToken(const char* expected) -{ - const char* token = getNextToken(getSepsColon()); - if (stricmp(token, expected) != 0) - throw INI_INVALID_DATA; - return getNextToken(getSepsColon()); -} - -//------------------------------------------------------------------------------------------------- -/** - * Parse a "random variable". - * The format is "FIELD = low high [distribution]". - */ -void INI::parseGameClientRandomVariable( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - GameClientRandomVariable *var = static_cast(store); - - const char* token; - - token = ini->getNextToken(); - Real low = INI::scanReal(token); - - token = ini->getNextToken(); - Real high = INI::scanReal(token); - - // if omitted, assume uniform - GameClientRandomVariable::DistributionType type = GameClientRandomVariable::UNIFORM; - token = ini->getNextTokenOrNull(); - if (token) - type = (GameClientRandomVariable::DistributionType)INI::scanIndexList(token, GameClientRandomVariable::DistributionTypeNames); - - // set the range of the random variable - var->setRange( low, high, type ); -} - -//------------------------------------------------------------------------------------------------- -// parse a duration in msec and convert to duration in frames -void INI::parseDurationReal( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - Real val = scanReal(ini->getNextToken()); - *(Real *)store = ConvertDurationFromMsecsToFrames(val); -} - -//------------------------------------------------------------------------------------------------- -// parse a duration in msec and convert to duration in integral number of frames, (unsignedint) rounding UP -void INI::parseDurationUnsignedInt( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - UnsignedInt val = scanUnsignedInt(ini->getNextToken()); - *(UnsignedInt *)store = (UnsignedInt)ceilf(ConvertDurationFromMsecsToFrames((Real)val)); -} - -// ------------------------------------------------------------------------------------------------ -// parse a duration in msec and convert to duration in integral number of frames, (unsignedshort) rounding UP -void INI::parseDurationUnsignedShort( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - UnsignedInt val = scanUnsignedInt(ini->getNextToken()); - *(UnsignedShort *)store = (UnsignedShort)ceilf(ConvertDurationFromMsecsToFrames((Real)val)); -} - -//------------------------------------------------------------------------------------------------- -// parse acceleration in (dist/sec) and convert to (dist/frame) -void INI::parseVelocityReal( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - Real val = scanReal(token); - *(Real *)store = ConvertVelocityInSecsToFrames(val); -} - -//------------------------------------------------------------------------------------------------- -// parse acceleration in (dist/sec^2) and convert to (dist/frame^2) -void INI::parseAccelerationReal( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - Real val = scanReal(token); - *(Real *)store = ConvertAccelerationInSecsToFrames(val); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void INI::parseVeterancyLevelFlags(INI* ini, void* /*instance*/, void* store, const void* /*userData*/) -{ - VeterancyLevelFlags flags = VETERANCY_LEVEL_FLAGS_ALL; - for (const char* token = ini->getNextToken(); token; token = ini->getNextTokenOrNull()) - { - if (stricmp(token, "ALL") == 0) - { - flags = VETERANCY_LEVEL_FLAGS_ALL; - continue; - } - else if (stricmp(token, "NONE") == 0) - { - flags = VETERANCY_LEVEL_FLAGS_NONE; - continue; - } - else if (token[0] == '+') - { - VeterancyLevel dt = (VeterancyLevel)INI::scanIndexList(token+1, TheVeterancyNames); - flags = setVeterancyLevelFlag(flags, dt); - continue; - } - else if (token[0] == '-') - { - VeterancyLevel dt = (VeterancyLevel)INI::scanIndexList(token+1, TheVeterancyNames); - flags = clearVeterancyLevelFlag(flags, dt); - continue; - } - else - { - throw INI_UNKNOWN_TOKEN; - } - } - *(VeterancyLevelFlags*)store = flags; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void INI::parseSoundsList( INI* ini, void *instance, void *store, const void* /*userData*/ ) -{ - std::vector *vec = (std::vector*) store; - vec->clear(); - - const char* SEPS = " \t,="; - const char *c = ini->getNextTokenOrNull(SEPS); - while ( c ) - { - vec->push_back( c ); - c = ini->getNextTokenOrNull(SEPS); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void INI::parseDamageTypeFlags(INI* ini, void* /*instance*/, void* store, const void* /*userData*/) -{ - DamageTypeFlags flags = DAMAGE_TYPE_FLAGS_NONE; - flags.flip(); - - for (const char* token = ini->getNextToken(); token; token = ini->getNextTokenOrNull()) - { - if (stricmp(token, "ALL") == 0) - { - flags = DAMAGE_TYPE_FLAGS_NONE; - flags.flip(); - continue; - } - if (stricmp(token, "NONE") == 0) - { - flags = DAMAGE_TYPE_FLAGS_NONE; - continue; - } - if (token[0] == '+') - { - DamageType dt = (DamageType)DamageTypeFlags::getSingleBitFromName(token+1); - flags = setDamageTypeFlag(flags, dt); - continue; - } - if (token[0] == '-') - { - DamageType dt = (DamageType)DamageTypeFlags::getSingleBitFromName(token+1); - flags = clearDamageTypeFlag(flags, dt); - continue; - } - throw INI_UNKNOWN_TOKEN; - } - *(DamageTypeFlags*)store = flags; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void INI::parseDeathTypeFlags(INI* ini, void* /*instance*/, void* store, const void* /*userData*/) -{ - DeathTypeFlags flags = DEATH_TYPE_FLAGS_ALL; - for (const char* token = ini->getNextToken(); token; token = ini->getNextTokenOrNull()) - { - if (stricmp(token, "ALL") == 0) - { - flags = DEATH_TYPE_FLAGS_ALL; - continue; - } - if (stricmp(token, "NONE") == 0) - { - flags = DEATH_TYPE_FLAGS_NONE; - continue; - } - if (token[0] == '+') - { - DeathType dt = (DeathType)INI::scanIndexList(token+1, TheDeathNames); - flags = setDeathTypeFlag(flags, dt); - continue; - } - if (token[0] == '-') - { - DeathType dt = (DeathType)INI::scanIndexList(token+1, TheDeathNames); - flags = clearDeathTypeFlag(flags, dt); - continue; - } - throw INI_UNKNOWN_TOKEN; - } - *(DeathTypeFlags*)store = flags; -} - -//------------------------------------------------------------------------------------------------- -// parse the line and return whether the given line is a Block declaration of the form -// [whitespace] blockType [whitespace] blockName [EOL] -// both blockType and blockName are case insensitive -Bool INI::isDeclarationOfType( AsciiString blockType, AsciiString blockName, char *bufferToCheck ) -{ - Bool retVal = true; - if (!bufferToCheck || blockType.isEmpty() || blockName.isEmpty()) { - return false; - } - // DO NOT RETURN EARLY FROM THIS FUNCTION. (beyond this point) - // we have to restore the bufferToCheck to its previous state before returning, so - // it is important to get through all the checks. - - char restoreChar; - char *tempBuff = bufferToCheck; - int blockTypeLength = blockType.getLength(); - int blockNameLength = blockName.getLength(); - - while (isspace(*tempBuff)) { - ++tempBuff; - } - - if (strlen(tempBuff) > blockTypeLength) { - restoreChar = tempBuff[blockTypeLength]; - tempBuff[blockTypeLength] = 0; - - if (stricmp(blockType.str(), tempBuff) != 0) { - retVal = false; - } - - tempBuff[blockTypeLength] = restoreChar; - tempBuff = tempBuff + blockTypeLength; - } else { - retVal = false; - } - - while (isspace(*tempBuff)) { - ++tempBuff; - } - - if (strlen(tempBuff) > blockNameLength) { - restoreChar = tempBuff[blockNameLength]; - tempBuff[blockNameLength] = 0; - - if (stricmp(blockName.str(), tempBuff) != 0) { - retVal = false; - } - - tempBuff[blockNameLength] = restoreChar; - tempBuff = tempBuff + blockNameLength; - } else { - retVal = false; - } - - while (strlen(tempBuff)) { - retVal = retVal && isspace(tempBuff[0]); - ++tempBuff; - } - - return retVal; -} - -//------------------------------------------------------------------------------------------------- -// parse the line and return whether the given line is a Block declaration of the form -// [whitespace] end [EOL] -Bool INI::isEndOfBlock( char *bufferToCheck ) -{ - Bool retVal = true; - if (!bufferToCheck) { - return false; - } - - // DO NOT RETURN EARLY FROM THIS FUNCTION (beyond this point) - // we have to restore the bufferToCheck to its previous state before returning, so - // it is important to get through all the checks. - - static const char* endString = "End"; - int endStringLength = strlen(endString); - char restoreChar; - char *tempBuff = bufferToCheck; - - - while (isspace(*tempBuff)) { - ++tempBuff; - } - - if (strlen(tempBuff) > endStringLength) { - restoreChar = tempBuff[endStringLength]; - tempBuff[endStringLength] = 0; - - if (stricmp(endString, tempBuff) != 0) { - retVal = false; - } - - tempBuff[endStringLength] = restoreChar; - tempBuff = tempBuff + endStringLength; - } else { - retVal = false; - } - - while (strlen(tempBuff)) { - retVal = retVal && isspace(tempBuff[0]); - ++tempBuff; - } - - return retVal; -} +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: INI.cpp ////////////////////////////////////////////////////////////////////////////////// +// Author: Colin Day, November 2001 +// Desc: INI Reader +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine +#define DEFINE_DEATH_NAMES +#define DEFINE_WEAPONBONUSCONDITION_NAMES + +#include "Common/INI.h" +#include "Common/INIException.h" + +#include "Common/DamageFX.h" +#include "Common/file.h" +#include "Common/FileSystem.h" +#include "Common/GameAudio.h" +#include "Common/Science.h" +#include "Common/SpecialPower.h" +#include "Common/ThingFactory.h" +#include "Common/ThingTemplate.h" +#include "Common/Upgrade.h" +#include "Common/GlobalData.h" +#include "Common/Xfer.h" +#include "Common/XferCRC.h" + +#include "GameClient/Anim2D.h" +#include "GameClient/Color.h" +#include "GameClient/FXList.h" +#include "GameClient/GameText.h" +#include "GameClient/Image.h" +#include "GameClient/ParticleSys.h" +#include "GameLogic/Armor.h" +#include "GameLogic/ExperienceTracker.h" +#include "GameLogic/FPUControl.h" +#include "GameLogic/ObjectCreationList.h" +#include "GameLogic/ScriptEngine.h" +#include "GameLogic/Weapon.h" + +#ifdef RTS_INTERNAL +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +static Xfer *s_xfer = NULL; + +//------------------------------------------------------------------------------------------------- +/** This is the table of data types we can have in INI files. To add a new data type + * block make a new entry in this table and add an appropriate parsing function */ +//------------------------------------------------------------------------------------------------- +extern void parseReallyLowMHz( INI* ini); // yeah, so sue me (srj) +struct BlockParse +{ + const char *token; + INIBlockParse parse; +}; +static const BlockParse theTypeTable[] = +{ + { "AIData", INI::parseAIDataDefinition }, + { "Animation", INI::parseAnim2DDefinition }, + { "Armor", INI::parseArmorDefinition }, + { "ArmorExtend", INI::parseArmorExtendDefinition }, + { "AudioEvent", INI::parseAudioEventDefinition }, + { "AudioSettings", INI::parseAudioSettingsDefinition }, + { "Bridge", INI::parseTerrainBridgeDefinition }, + { "Campaign", INI::parseCampaignDefinition }, + { "ChallengeGenerals", INI::parseChallengeModeDefinition }, + { "CommandButton", INI::parseCommandButtonDefinition }, + { "CommandMap", INI::parseMetaMapDefinition }, + { "CommandSet", INI::parseCommandSetDefinition }, + { "ControlBarScheme", INI::parseControlBarSchemeDefinition }, + { "ControlBarResizer", INI::parseControlBarResizerDefinition }, + { "CrateData", INI::parseCrateTemplateDefinition }, + { "Credits", INI::parseCredits}, + { "WindowTransition", INI::parseWindowTransitions}, + { "DamageFX", INI::parseDamageFXDefinition }, + { "DialogEvent", INI::parseDialogDefinition }, + { "DrawGroupInfo", INI::parseDrawGroupNumberDefinition }, + { "EvaEvent", INI::parseEvaEvent }, + { "FXList", INI::parseFXListDefinition }, + { "GameData", INI::parseGameDataDefinition }, + { "InGameUI", INI::parseInGameUIDefinition }, + { "Locomotor", INI::parseLocomotorTemplateDefinition }, + { "Language", INI::parseLanguageDefinition }, + { "MapCache", INI::parseMapCacheDefinition }, + { "MapData", INI::parseMapDataDefinition }, + { "MappedImage", INI::parseMappedImageDefinition }, + { "MiscAudio", INI::parseMiscAudio}, + { "Mouse", INI::parseMouseDefinition }, + { "MouseCursor", INI::parseMouseCursorDefinition }, + { "MultiplayerColor", INI::parseMultiplayerColorDefinition }, + { "MultiplayerStartingMoneyChoice", INI::parseMultiplayerStartingMoneyChoiceDefinition }, + { "OnlineChatColors", INI::parseOnlineChatColorDefinition }, + { "MultiplayerSettings",INI::parseMultiplayerSettingsDefinition }, + { "MusicTrack", INI::parseMusicTrackDefinition }, + { "Object", INI::parseObjectDefinition }, + { "ObjectCreationList", INI::parseObjectCreationListDefinition }, + { "ObjectReskin", INI::parseObjectReskinDefinition }, + { "ObjectExtend", INI::parseObjectExtendDefinition }, + { "ParticleSystem", INI::parseParticleSystemDefinition }, + { "PlayerTemplate", INI::parsePlayerTemplateDefinition }, + { "Road", INI::parseTerrainRoadDefinition }, + { "Science", INI::parseScienceDefinition }, + { "Rank", INI::parseRankDefinition }, + { "SpecialPower", INI::parseSpecialPowerDefinition }, + { "ShellMenuScheme", INI::parseShellMenuSchemeDefinition }, + { "Terrain", INI::parseTerrainDefinition }, + { "Upgrade", INI::parseUpgradeDefinition }, + { "Video", INI::parseVideoDefinition }, + { "WaterSet", INI::parseWaterSettingDefinition }, + { "WaterTransparency", INI::parseWaterTransparencyDefinition}, + { "Weather", INI::parseWeatherDefinition}, + { "Weapon", INI::parseWeaponTemplateDefinition }, + { "WebpageURL", INI::parseWebpageURLDefinition }, + { "HeaderTemplate", INI::parseHeaderTemplateDefinition }, + { "StaticGameLOD", INI::parseStaticGameLODDefinition }, + { "DynamicGameLOD", INI::parseDynamicGameLODDefinition }, + { "LODPreset", INI::parseLODPreset }, + { "BenchProfile", INI::parseBenchProfile }, + { "ReallyLowMHz", parseReallyLowMHz }, + { "ScriptAction", ScriptEngine::parseScriptAction }, + { "ScriptCondition", ScriptEngine::parseScriptCondition }, + + { NULL, NULL }, // keep this last! +}; + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +Bool INI::isValidINIFilename( const char *filename ) +{ + if( filename == NULL ) + return FALSE; + + Int len = strlen( filename ); + if( len < 3 ) + return FALSE; + + if( filename[ len - 1 ] != 'I' && filename[ len - 1 ] != 'i' ) + return FALSE; + + if( filename[ len - 2 ] != 'N' && filename[ len - 2 ] != 'n' ) + return FALSE; + + if( filename[ len - 3 ] != 'I' && filename[ len - 3 ] != 'i' ) + return FALSE; + + return TRUE; + +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +INI::INI( void ) +{ + + m_file = NULL; + m_readBufferNext=m_readBufferUsed=0; + m_filename = "None"; + m_loadType = INI_LOAD_INVALID; + m_lineNum = 0; + m_seps = " \n\r\t="; ///< make sure you update m_sepsPercent/m_sepsColon as well + m_sepsPercent = " \n\r\t=%%"; + m_sepsColon = " \n\r\t=:"; + m_sepsQuote = "\"\n="; ///< stop at " = EOL + m_blockEndToken = "END"; + m_endOfFile = FALSE; + m_buffer[0] = 0; +#ifdef DEBUG_CRASHING + m_curBlockStart[0] = 0; +#endif + +} // end INI + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +INI::~INI( void ) +{ + +} // end ~INI + +//------------------------------------------------------------------------------------------------- +/** Load all INI files in the specified directory (and subdirectories if indicated). + * If we are to load subdirectories, we will load them *after* we load all the + * files in the current directory */ +//------------------------------------------------------------------------------------------------- +void INI::loadDirectory( AsciiString dirName, Bool subdirs, INILoadType loadType, Xfer *pXfer ) +{ + // sanity + if( dirName.isEmpty() ) + throw INI_INVALID_DIRECTORY; + + try + { + FilenameList filenameList; + dirName.concat('\\'); + TheFileSystem->getFileListInDirectory(dirName, "*.ini", filenameList, TRUE); + // Load the INI files in the dir now, in a sorted order. This keeps things the same between machines + // in a network game. + FilenameList::const_iterator it = filenameList.begin(); + while (it != filenameList.end()) + { + AsciiString tempname; + tempname = (*it).str() + dirName.getLength(); + + if ((tempname.find('\\') == NULL) && (tempname.find('/') == NULL)) { + // this file doesn't reside in a subdirectory, load it first. + load( *it, loadType, pXfer ); + } + ++it; + } + + it = filenameList.begin(); + while (it != filenameList.end()) + { + AsciiString tempname; + tempname = (*it).str() + dirName.getLength(); + + if ((tempname.find('\\') != NULL) || (tempname.find('/') != NULL)) { + load( *it, loadType, pXfer ); + } + ++it; + } + } + catch (...) + { + // propagate the exception + throw; + } + +} // end loadDirectory + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void INI::prepFile( AsciiString filename, INILoadType loadType ) +{ + // if we have a file open already -- we can't do another one + if( m_file != NULL ) + { + + DEBUG_CRASH(( "INI::load, cannot open file '%s', file already open\n", filename.str() )); + throw INI_FILE_ALREADY_OPEN; + + } // end if + + // open the file + m_file = TheFileSystem->openFile(filename.str(), File::READ); + if( m_file == NULL ) + { + + DEBUG_CRASH(( "INI::load, cannot open file '%s'\n", filename.str() )); + throw INI_CANT_OPEN_FILE; + + } // end if + + m_file = m_file->convertToRAMFile(); + + // save our filename + m_filename = filename; + + // save our load time + m_loadType = loadType; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void INI::unPrepFile() +{ + // close the file + m_file->close(); + m_file = NULL; + m_readBufferUsed=m_readBufferNext=0; + m_filename = "None"; + m_loadType = INI_LOAD_INVALID; + m_lineNum = 0; + m_endOfFile = FALSE; + s_xfer = NULL; +} + +//------------------------------------------------------------------------------------------------- +static INIBlockParse findBlockParse(const char* token) +{ + for (const BlockParse* parse = theTypeTable; parse->token; ++parse) + { + if (strcmp( parse->token, token ) == 0) + { + return parse->parse; + } + } + return NULL; +} + +//------------------------------------------------------------------------------------------------- +static INIFieldParseProc findFieldParse(const FieldParse* parseTable, const char* token, int& offset, const void*& userData) +{ + const FieldParse* parse = parseTable; + for (; parse->token; ++parse) + { + if (strcmp( parse->token, token ) == 0) + { + offset = parse->offset; + userData = parse->userData; + return parse->parse; + } + } + + if (!parse->token && parse->parse) + { + offset = parse->offset; + userData = token; + return parse->parse; + } + else + { + return NULL; + } +} + +//------------------------------------------------------------------------------------------------- +/** Load and parse an INI file */ +//------------------------------------------------------------------------------------------------- +void INI::load( AsciiString filename, INILoadType loadType, Xfer *pXfer ) +{ + setFPMode(); // so we have consistent Real values for GameLogic -MDC + + s_xfer = pXfer; + prepFile(filename, loadType); + + try + { + + // read all lines in the file + DEBUG_ASSERTCRASH( m_endOfFile == FALSE, ("INI::load, EOF at the beginning!\n") ); + while( m_endOfFile == FALSE ) + { + // read this line + readLine(); + + AsciiString currentLine = m_buffer; + + // the first word is the type of data we're processing + const char *token = strtok( m_buffer, m_seps ); + if( token ) + { + INIBlockParse parse = findBlockParse(token); + if (parse) + { + #ifdef DEBUG_CRASHING + strcpy(m_curBlockStart, m_buffer); + #endif + try { + (*parse)( this ); + + } catch (...) { + DEBUG_CRASH(("Error parsing block '%s' in INI file '%s'\n", token, m_filename.str()) ); + char buff[1024]; + sprintf(buff, "Error parsing INI file '%s' (Line: '%s')\n", m_filename.str(), currentLine.str()); + + throw INIException(buff); + } + #ifdef DEBUG_CRASHING + strcpy(m_curBlockStart, "NO_BLOCK"); + #endif + } + else + { + DEBUG_ASSERTCRASH( 0, ("[LINE: %d - FILE: '%s'] Unknown block '%s'\n", + getLineNum(), getFilename().str(), token ) ); + throw INI_UNKNOWN_TOKEN; + } + + } // end if + + } // end while + } + catch (...) + { + unPrepFile(); + + // propagate the exception. + throw; + } + + unPrepFile(); + +} // end load + +//------------------------------------------------------------------------------------------------- +/** Read a line from the already open file. Any comments will be remved and + * therefore ignored from any given line */ +//------------------------------------------------------------------------------------------------- +void INI::readLine( void ) +{ + // sanity + DEBUG_ASSERTCRASH( m_file, ("readLine(), file pointer is NULL\n") ); + + if (m_endOfFile) + *m_buffer=0; + else + { + char *p=m_buffer; + while (p!=m_buffer+INI_MAX_CHARS_PER_LINE) + { + // get next character + if (m_readBufferNext==m_readBufferUsed) + { + // refill buffer + m_readBufferNext=0; + m_readBufferUsed=m_file->read(m_readBuffer,INI_READ_BUFFER); + + // EOF? + if (!m_readBufferUsed) + { + m_endOfFile=true; + *p=0; + break; + } + } + *p=m_readBuffer[m_readBufferNext++]; + + // CR? + if (*p=='\n') + { + *p=0; + break; + } + + DEBUG_ASSERTCRASH(*p != '\t', ("tab characters are not allowed in INI files (%s). please check your editor settings. Line Number %d\n",m_filename.str(), getLineNum())); + + // comment? + if (*p==';') + *p=0; + // whitespace? + else if (*p>0&&*p<32) + *p=' '; + p++; + } + *p=0; + + // increase our line count + m_lineNum++; + + // check for at the max + if ( p == m_buffer+INI_MAX_CHARS_PER_LINE ) + { + + DEBUG_ASSERTCRASH( 0, ("Buffer too small (%d) and was truncated, increase INI_MAX_CHARS_PER_LINE\n", + INI_MAX_CHARS_PER_LINE) ); + + } // end if + } + + if (s_xfer) + { + s_xfer->xferUser( m_buffer, sizeof( char ) * strlen( m_buffer ) ); + //DEBUG_LOG(("Xfer val is now 0x%8.8X in %s, line %s\n", ((XferCRC *)s_xfer)->getCRC(), + //m_filename.str(), m_buffer)); + } +} + +//------------------------------------------------------------------------------------------------- +/** Parse UnsignedByte from buffer and assign at location 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parseUnsignedByte( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + Int value = scanInt(token); + if (value < 0 || value > 255) + { + DEBUG_CRASH(("Bad value INI::parseUnsignedByte")); + throw ERROR_BUG; + } + *(Byte *)store = (Byte)value; +} + +//------------------------------------------------------------------------------------------------- +/** Parse signed short from buffer and assign at location 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parseShort( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + Int value = scanInt(token); + if (value < -32768 || value > 32767) + { + DEBUG_CRASH(("Bad value INI::parseShort")); + throw ERROR_BUG; + } + *(Short *)store = (Short)value; +} + +//------------------------------------------------------------------------------------------------- +/** Parse unsigned short from buffer and assign at location 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parseUnsignedShort( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + Int value = scanInt(token); + if (value < 0 || value > 65535) + { + DEBUG_CRASH(("Bad value INI::parseUnsignedShort")); + throw ERROR_BUG; + } + *(UnsignedShort *)store = (UnsignedShort)value; +} + +//------------------------------------------------------------------------------------------------- +/** Parse integer from buffer and assign at location 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parseInt( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + *(Int *)store = scanInt(token); + +} + +//------------------------------------------------------------------------------------------------- +/** Parse unsigned integer from buffer and assign at location 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parseUnsignedInt( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + *(UnsignedInt *)store = scanUnsignedInt(token); + +} + +//------------------------------------------------------------------------------------------------- +/** Parse real from buffer and assign at location 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parseReal( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + *(Real *)store = scanReal(token); + +} + +//------------------------------------------------------------------------------------------------- +/** Parse real from buffer and assign at location 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parsePositiveNonZeroReal( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + *(Real *)store = scanReal(token); + if (*(Real *)store <= 0.0f) + { + DEBUG_CRASH(("invalid Real value %f -- expected > 0\n",*(Real*)store)); + throw INI_INVALID_DATA; + } + +} + +//------------------------------------------------------------------------------------------------- +/** Parse a degree value (0 to 360) and store the radian value of that degree + * in a Real */ +//------------------------------------------------------------------------------------------------- +void INI::parseAngleReal( INI *ini, void * /*instance*/, + void *store, const void *userData ) +{ + const char *token = ini->getNextToken(); + + const Real RADS_PER_DEGREE = PI / 180.0f; + *(Real *)store = scanReal( token ) * RADS_PER_DEGREE; + +} + +//------------------------------------------------------------------------------------------------- +/** Parse an angular velocity in degrees-per-sec and store the rads-per-frame value of that degree + * in a Real */ +//------------------------------------------------------------------------------------------------- +void INI::parseAngularVelocityReal( INI *ini, void * /*instance*/, + void *store, const void *userData ) +{ + const char *token = ini->getNextToken(); + + // scan the int and convert to radian and store as a real + *(Real *)store = ConvertAngularVelocityInDegreesPerSecToRadsPerFrame(scanReal( token )); + +} + +//------------------------------------------------------------------------------------------------- +/** Parse Bool from buffer and assign at location 'store'. The buffer token must + * be in the form of a string "Yes" or "No" (case is ignored) */ +//------------------------------------------------------------------------------------------------- +void INI::parseBool( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + *(Bool*)store = INI::scanBool(ini->getNextToken()); +} + +//------------------------------------------------------------------------------------------------- +/** Parse Bool from buffer; if true, or in MASK, otherwise and out MASK. The buffer token must + * be in the form of a string "Yes" or "No" (case is ignored) */ +//------------------------------------------------------------------------------------------------- +void INI::parseBitInInt32( INI *ini, void *instance, void *store, const void* userData ) +{ + UnsignedInt* s = (UnsignedInt*)store; + UnsignedInt mask = (UnsignedInt)userData; + + if (INI::scanBool(ini->getNextToken())) + *s |= mask; + else + *s &= ~mask; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +/*static*/ Bool INI::scanBool(const char* token) +{ + // translate string yes/no into TRUE/FALSE + if( stricmp( token, "yes" ) == 0 ) + return TRUE; + else if( stricmp( token, "no" ) == 0 ) + return FALSE; + else + { + DEBUG_CRASH(("invalid boolean token %s -- expected Yes or No\n",token)); + throw INI_INVALID_DATA; + return false; // keep compiler happy + } + +} + +//------------------------------------------------------------------------------------------------- +/** Parse an *ASCII* string from buffer and assign at location 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parseAsciiString( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + AsciiString* asciiString = (AsciiString *)store; + *asciiString = ini->getNextAsciiString(); +} + +//------------------------------------------------------------------------------------------------- +/** Parse an *ASCII* string from buffer and assign at location 'store'. Has better support for quoted strings. +We don't really need this function, but parseString() is broken and we want to leave it broken to +maintain existing code. + */ +//------------------------------------------------------------------------------------------------- +void INI::parseQuotedAsciiString( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + AsciiString* asciiString = (AsciiString *)store; + *asciiString = ini->getNextQuotedAsciiString(); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void INI::parseAsciiStringVector( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + std::vector* asv = (std::vector*)store; + asv->clear(); + for (const char *token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) + { + asv->push_back(token); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void INI::parseAsciiStringVectorAppend( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + std::vector* asv = (std::vector*)store; + // nope, don't clear. duh. + // asv->clear(); + for (const char *token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) + { + asv->push_back(token); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +/* static */void INI::parseScienceVector( INI *ini, void * /*instance*/, void *store, const void *userData ) +{ + ScienceVec* asv = (ScienceVec*)store; + asv->clear(); + for (const char *token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) + { + if (stricmp(token, "None") == 0) + { + asv->clear(); + return; + } + asv->push_back(INI::scanScience( token )); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +/* static */void INI::parseWeaponBonusVector( INI *ini, void * /*instance*/, void *store, const void *userData ) +{ + WeaponBonusConditionTypeVec* asv = (WeaponBonusConditionTypeVec*)store; + asv->clear(); + for (const char *token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) + { + if (stricmp(token, "None") == 0) + { + asv->clear(); + return; + } + asv->push_back((WeaponBonusConditionType)INI::scanIndexList(token, TheWeaponBonusNames)); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +/* static */void INI::parseWeaponBonusVectorKeepDefault(INI* ini, void* /*instance*/, void* store, const void* userData) +{ + WeaponBonusConditionTypeVec* asv = (WeaponBonusConditionTypeVec*)store; + // asv->clear(); + for (const char* token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) + { + if (stricmp(token, "None") == 0) + { + asv->clear(); + return; + } + asv->push_back((WeaponBonusConditionType)INI::scanIndexList(token, TheWeaponBonusNames)); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +AsciiString INI::getNextQuotedAsciiString() +{ + AsciiString result; + char buff[INI_MAX_CHARS_PER_LINE]; + + const char *token = getNextTokenOrNull(); // if null, just leave an empty string + if (token != NULL) + { + if (token[0] != '\"') + { + // if token is simply " + result.set( token ); // Start following the " + } + else + { int strLen=0; + Bool done=FALSE; + if ((strLen=strlen(token)) > 1) + { + strcpy(buff, &token[1]); //skip the starting quote + //Check for end of quoted string. Checking here fixes cases where quoted string on same line with other data. + if (buff[strLen-2]=='"') //skip ending quote if present + { buff[strLen-2]='\0'; + done=TRUE; + } + } + + if (!done) + { + token = getNextToken(getSepsQuote()); + + if (strlen(token) > 1 && token[1] != '\t') + { + strcat(buff, " "); + strcat(buff, token); + } + else + { Int buflen=strlen(buff); + if (buff[buflen-1]=='\"') + buff[buflen-1]='\0'; + } + } + result.set(buff); + } + } + return result; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +AsciiString INI::getNextAsciiString() +{ + AsciiString result; + + const char *token = getNextTokenOrNull(); // if null, just leave an empty string + if (token != NULL) + { + if (token[0] != '\"') + { + // if token is simply " + result.set( token ); // Start following the " + } + else + { + static char buff[INI_MAX_CHARS_PER_LINE]; + buff[0] = 0; + if (strlen(token) > 1) + { + strcpy(buff, &token[1]); + } + + token = getNextTokenOrNull(getSepsQuote()); + if (token) { + if (strlen(token) > 1 && token[1] != '\t') + { + strcat(buff, " "); + } + strcat(buff, token); + result.set(buff); + } else { + Int len = strlen(buff); + if (len && buff[len-1] == '"') { // strip off trailing quote jba. [2/12/2003] + buff[len-1] = 0; + } + result.set(buff); + } + } + } + return result; +} + +//------------------------------------------------------------------------------------------------- +/** Parse a string label, get the *translated* actual text from the label and store + * into a *UNICODE* string. */ +//------------------------------------------------------------------------------------------------- +void INI::parseAndTranslateLabel( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + + // translate + UnicodeString translated = TheGameText->fetch( token ); + if( translated.isEmpty() ) + throw INI_INVALID_DATA; + + // save the translated text + UnicodeString *theString = (UnicodeString *)store; + theString->set( translated.str() ); + +} // end parseAndTranslateLabel + +//------------------------------------------------------------------------------------------------- +/** Parse a string label assumed as an image as part of the image collection. Translate + * to an image pointer for storage */ +//------------------------------------------------------------------------------------------------- +void INI::parseMappedImage( INI *ini, void * /*instance*/, void *store, const void *userData ) +{ + const char *token = ini->getNextToken(); + + if( TheMappedImageCollection ) + { + typedef const Image* ConstImagePtr; + *(ConstImagePtr*)store = TheMappedImageCollection->findImageByName( AsciiString( token ) ); + } + + //KM: If we are in the worldbuilder, we want to parse commandbuttons for informational purposes, + //but we don't care about the images -- because we never access them. In RTS/GUIEdit, they always + //exist -- and in those cases, it will never call this code anyways because it'll throw long before. + //else + // throw INI_UNKNOWN_ERROR; + +} // end parseMappedImage + +// ------------------------------------------------------------------------------------------------ +/** Parse a string label assumed as a Anim2D template name. Translate that name to an + * actual template pointer for storage */ +// ------------------------------------------------------------------------------------------------ +/*static*/ void INI::parseAnim2DTemplate( INI *ini, void *instance, void *store, const void *userData ) +{ + const char *token = ini->getNextToken(); + + if( TheAnim2DCollection ) + { + Anim2DTemplate **anim2DTemplate = (Anim2DTemplate **)store; + *anim2DTemplate = TheAnim2DCollection->findTemplate( AsciiString( token ) ); + } // end if + else + { + + DEBUG_CRASH(( "INI::parseAnim2DTemplate - TheAnim2DCollection is NULL\n" )); + throw INI_UNKNOWN_ERROR; + + } // end else + +} // end parseAnim2DTemplate + +//------------------------------------------------------------------------------------------------- +/** Parse a percent in int or real form such as "23%" or "95.4%" and assign + * to location 'store' as a number from 0.0 to 1.0 */ +//------------------------------------------------------------------------------------------------- +void INI::parsePercentToReal( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(ini->getSepsPercent()); + Real *theReal = (Real *)store; + *theReal = scanPercentToReal(token); + +} // end parsePercentToReal + +//------------------------------------------------------------------------------------------------- +/** 'store' points to an 32 bit unsigned integer. We will zero that integer, parse each token + * in the buffer, if the token is in the userData table of strings, we will set the + * according bit flag for it */ +//------------------------------------------------------------------------------------------------- +void INI::parseBitString8( INI* ini, void * /*instance*/, void *store, const void* userData ) +{ + UnsignedInt tmp; + INI::parseBitString32(ini, NULL, &tmp, userData); + if (tmp & 0xffffff00) + { + DEBUG_CRASH(("Bad bitstring list INI::parseBitString8")); + throw ERROR_BUG; + } + *(Byte*)store = (Byte)tmp; +} + +//------------------------------------------------------------------------------------------------- +/** 'store' points to an 32 bit unsigned integer. We will zero that integer, parse each token + * in the buffer, if the token is in the userData table of strings, we will set the + * according bit flag for it */ +//------------------------------------------------------------------------------------------------- +void INI::parseBitString32( INI* ini, void * /*instance*/, void *store, const void* userData ) +{ + ConstCharPtrArray flagList = (ConstCharPtrArray)userData; + UnsignedInt *bits = (UnsignedInt *)store; + + if( flagList == NULL || flagList[ 0 ] == NULL) + { + DEBUG_ASSERTCRASH( flagList, ("INTERNAL ERROR! parseBitString32: No flag list provided!\n") ); + throw INI_INVALID_NAME_LIST; + } + + Bool foundNormal = false; + Bool foundAddOrSub = false; + + // loop through all tokens + for (const char *token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) + { + if (stricmp(token, "NONE") == 0) + { + if (foundNormal || foundAddOrSub) + { + DEBUG_CRASH(("you may not mix normal and +- ops in bitstring lists")); + throw INI_INVALID_NAME_LIST; + } + *bits = 0; + break; + } + + if (token[0] == '+') + { + if (foundNormal) + { + DEBUG_CRASH(("you may not mix normal and +- ops in bitstring lists")); + throw INI_INVALID_NAME_LIST; + } + Int bitIndex = INI::scanIndexList(token+1, flagList); // this throws if the token is not found + *bits |= (1 << bitIndex); + foundAddOrSub = true; + } + else if (token[0] == '-') + { + if (foundNormal) + { + DEBUG_CRASH(("you may not mix normal and +- ops in bitstring lists")); + throw INI_INVALID_NAME_LIST; + } + Int bitIndex = INI::scanIndexList(token+1, flagList); // this throws if the token is not found + *bits &= ~(1 << bitIndex); + foundAddOrSub = true; + } + else + { + if (foundAddOrSub) + { + DEBUG_CRASH(("you may not mix normal and +- ops in bitstring lists")); + throw INI_INVALID_NAME_LIST; + } + + if (!foundNormal) + *bits = 0; + + Int bitIndex = INI::scanIndexList(token, flagList); // this throws if the token is not found + *bits |= (1 << bitIndex); + foundNormal = true; + } + } +} + +//------------------------------------------------------------------------------------------------- +/** Parse a color in the form of + * + * RGB_COLOR = R:100 G:114 B:245 + * and store in "RGBColor" structure pointed to by 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parseRGBColor( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char* names[3] = { "R", "G", "B" }; + Int colors[3]; + for( Int i = 0; i < 3; i++ ) + { + colors[i] = scanInt(ini->getNextSubToken(names[i])); + if( colors[ i ] < 0 ) + throw INI_INVALID_DATA; + if( colors[ i ] > 255 ) + throw INI_INVALID_DATA; + } + + // assign the color components to the "RGBColor" pointer at 'store' + RGBColor *theColor = (RGBColor *)store; + theColor->red = (Real)colors[ 0 ] / 255.0f; + theColor->green = (Real)colors[ 1 ] / 255.0f; + theColor->blue = (Real)colors[ 2 ] / 255.0f; + +} + + +//------------------------------------------------------------------------------------------------- +/** Parse a color in the form of + * + * RGB_COLOR = R:0.5 G:0.3 B:0.6 + * and store in "RGBColor" structure pointed to by 'store' + * Negative numbers, and values greater 1 are allowed! */ + //------------------------------------------------------------------------------------------------- +void INI::parseRGBColorReal(INI* ini, void* /*instance*/, void* store, const void* /*userData*/) +{ + const char* names[3] = { "R", "G", "B" }; + Real colors[3]; + for (Int i = 0; i < 3; i++) + { + colors[i] = scanReal(ini->getNextSubToken(names[i])); + //if (colors[i] < -255) + // throw INI_INVALID_DATA; + //if (colors[i] > 255) + // throw INI_INVALID_DATA; + } + + // assign the color components to the "RGBColor" pointer at 'store' + RGBColor* theColor = (RGBColor*)store; + theColor->red = colors[0]; + theColor->green = colors[1]; + theColor->blue = colors[2]; + +} + + +//------------------------------------------------------------------------------------------------- +/** Parse a color in the form of + * + * RGB_COLOR = R:100 G:114 B:245 [A:233] + * and store in "RGBAColorInt" structure pointed to by 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parseRGBAColorInt( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char* names[4] = { "R", "G", "B", "A" }; + Int colors[4]; + for( Int i = 0; i < 4; i++ ) + { + const char* token = ini->getNextTokenOrNull(ini->getSepsColon()); + if (token == NULL) + { + if (i < 3) + { + throw INI_INVALID_DATA; + } + else + { + // it's ok for A to be omitted. + colors[i] = 255; + } + } + else + { + // if present, the token must match. + if (stricmp(token, names[i]) != 0) + { + throw INI_INVALID_DATA; + } + colors[i] = scanInt(ini->getNextToken(ini->getSepsColon())); + } + if( colors[ i ] < 0 ) + throw INI_INVALID_DATA; + if( colors[ i ] > 255 ) + throw INI_INVALID_DATA; + } + + // + // assign the color components to the "RGBColorInt" pointer at 'store', keep + // the numbers as between 0 and 255 + // + RGBAColorInt *theColor = (RGBAColorInt *)store; + theColor->red = colors[ 0 ]; + theColor->green = colors[ 1 ]; + theColor->blue = colors[ 2 ]; + theColor->alpha = colors[ 3 ]; + +} // end parseRGBAColorInt + +//------------------------------------------------------------------------------------------------- +/** Parse a color in the form of + * + * RGB_COLOR = R:100 G:114 B:245 [A:233] + * and store in "Color" structure pointed to by 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parseColorInt( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char* names[4] = { "R", "G", "B", "A" }; + Int colors[4]; + for( Int i = 0; i < 4; i++ ) + { + const char* token = ini->getNextTokenOrNull(ini->getSepsColon()); + if (token == NULL) + { + if (i < 3) + { + throw INI_INVALID_DATA; + } + else + { + // it's ok for A to be omitted. + colors[i] = 255; + } + } + else + { + // if present, the token must match. + if (stricmp(token, names[i]) != 0) + { + throw INI_INVALID_DATA; + } + colors[i] = scanInt(ini->getNextToken(ini->getSepsColon())); + } + if( colors[ i ] < 0 ) + throw INI_INVALID_DATA; + if( colors[ i ] > 255 ) + throw INI_INVALID_DATA; + } + + // + // assign the color components to the "Color" pointer at 'store', keep + // the numbers as between 0 and 255 + // + Color *theColor = (Color *)store; + *theColor = GameMakeColor(colors[0], colors[1], colors[2], colors[3]); + +} // end parseColorInt + +//------------------------------------------------------------------------------------------------- +/** Parse a 3D coordinate of reals in the form of: + * FIELD_NAME = X:400 Y:-214.3 Z:8.6 */ +//------------------------------------------------------------------------------------------------- +void INI::parseCoord3D( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + Coord3D *theCoord = (Coord3D *)store; + + theCoord->x = scanReal(ini->getNextSubToken("X")); + theCoord->y = scanReal(ini->getNextSubToken("Y")); + theCoord->z = scanReal(ini->getNextSubToken("Z")); + +} // end parseCoord3D + +//------------------------------------------------------------------------------------------------- +/** Parse a 2D coordinate of reals in the form of: + * FIELD_NAME = X:400 Y:-214.3 */ +//------------------------------------------------------------------------------------------------- +void INI::parseCoord2D( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + Coord2D *theCoord = (Coord2D *)store; + + theCoord->x = scanReal(ini->getNextSubToken("X")); + theCoord->y = scanReal(ini->getNextSubToken("Y")); + +} // end parseCoord2D + +//------------------------------------------------------------------------------------------------- +/** Parse a 2D coordinate of Ints in the form of: + * FIELD_NAME = X:400 Y:-214 */ +//------------------------------------------------------------------------------------------------- +void INI::parseICoord2D( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + ICoord2D *theCoord = (ICoord2D *)store; + + theCoord->x = scanInt(ini->getNextSubToken("X")); + theCoord->y = scanInt(ini->getNextSubToken("Y")); + +} // end parseICoord2D + +//------------------------------------------------------------------------------------------------- +/** Parse an audio event and assign to the 'AudioEventRTS*' at store */ +//------------------------------------------------------------------------------------------------- +void INI::parseDynamicAudioEventRTS( INI *ini, void * /*instance*/, void *store, const void* userData ) +{ + const char *token = ini->getNextToken(); + DynamicAudioEventRTS** theSound = (DynamicAudioEventRTS**)store; + + // translate the string into a sound + if (stricmp(token, "NoSound") == 0) + { + if (*theSound) + { + (*theSound)->deleteInstance(); + *theSound = NULL; + } + } + else + { + if (*theSound == NULL) + *theSound = newInstance(DynamicAudioEventRTS); + (*theSound)->m_event.setEventName(AsciiString(token)); + } + + if (*theSound) + TheAudio->getInfoForAudioEvent(&(*theSound)->m_event); +} + +//------------------------------------------------------------------------------------------------- +/** Parse an audio event and assign to the 'AudioEventRTS*' at store */ +//------------------------------------------------------------------------------------------------- +void INI::parseAudioEventRTS( INI *ini, void * /*instance*/, void *store, const void* userData ) +{ + const char *token = ini->getNextToken(); + + AudioEventRTS *theSound = (AudioEventRTS*)store; + + // translate the string into a sound + if (stricmp(token, "NoSound") != 0) { + theSound->setEventName(AsciiString(token)); + } + + TheAudio->getInfoForAudioEvent(theSound); +} + +//------------------------------------------------------------------------------------------------- +/** Parse an ThingTemplate and assign to the 'ThingTemplate *' at store */ +//------------------------------------------------------------------------------------------------- +void INI::parseThingTemplate( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + + if (!TheThingFactory) + { + DEBUG_CRASH(("TheThingFactory not inited yet")); + throw ERROR_BUG; + } + + typedef const ThingTemplate *ConstThingTemplatePtr; + ConstThingTemplatePtr* theThingTemplate = (ConstThingTemplatePtr*)store; + + if (stricmp(token, "None") == 0) + { + *theThingTemplate = NULL; + } + else + { + const ThingTemplate *tt = TheThingFactory->findTemplate(token); // could be null! + DEBUG_ASSERTCRASH(tt, ("ThingTemplate %s not found!\n",token)); + // assign it, even if null! + *theThingTemplate = tt; + } + +} + +//------------------------------------------------------------------------------------------------- +/** Parse an ArmorTemplate and assign to the 'ArmorTemplate *' at store */ +//------------------------------------------------------------------------------------------------- +void INI::parseArmorTemplate( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + + typedef const ArmorTemplate *ConstArmorTemplatePtr; + ConstArmorTemplatePtr* theArmorTemplate = (ConstArmorTemplatePtr*)store; + + if (stricmp(token, "None") == 0) + { + *theArmorTemplate = NULL; + } + else + { + const ArmorTemplate *tt = TheArmorStore->findArmorTemplate(token); // could be null! + DEBUG_ASSERTCRASH(tt, ("ArmorTemplate %s not found!\n",token)); + // assign it, even if null! + *theArmorTemplate = tt; + } + +} + +//------------------------------------------------------------------------------------------------- +/** Parse an WeaponTemplate and assign to the 'WeaponTemplate *' at store */ +//------------------------------------------------------------------------------------------------- +void INI::parseWeaponTemplate( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + + typedef const WeaponTemplate *ConstWeaponTemplatePtr; + ConstWeaponTemplatePtr* theWeaponTemplate = (ConstWeaponTemplatePtr*)store; + + const WeaponTemplate *tt = TheWeaponStore->findWeaponTemplate(token); // could be null! + DEBUG_ASSERTCRASH(tt || stricmp(token, "None") == 0, ("WeaponTemplate %s not found!\n",token)); + // assign it, even if null! + *theWeaponTemplate = tt; + +} + +//------------------------------------------------------------------------------------------------- +/** Parse an FXList and assign to the 'FXList *' at store */ +//------------------------------------------------------------------------------------------------- +void INI::parseFXList( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + + typedef const FXList *ConstFXListPtr; + ConstFXListPtr* theFXList = (ConstFXListPtr*)store; + + const FXList *fxl = TheFXListStore->findFXList(token); // could be null! + DEBUG_ASSERTCRASH(fxl != NULL || stricmp(token, "None") == 0, ("FXList %s not found!\n",token)); + // assign it, even if null! + *theFXList = fxl; + +} + +//------------------------------------------------------------------------------------------------- +/** Parse a particle system and assign to 'ParticleSystemTemplate *' at store */ +//------------------------------------------------------------------------------------------------- +void INI::parseParticleSystemTemplate( INI *ini, void * /*instance*/, void *store, const void *userData ) +{ + const char *token = ini->getNextToken(); + + const ParticleSystemTemplate *pSystemT = TheParticleSystemManager->findTemplate( AsciiString( token ) ); + DEBUG_ASSERTCRASH( pSystemT || stricmp( token, "None" ) == 0, ("ParticleSystem %s not found!\n",token) ); + + typedef const ParticleSystemTemplate* ConstParticleSystemTemplatePtr; + ConstParticleSystemTemplatePtr* theParticleSystemTemplate = (ConstParticleSystemTemplatePtr*)store; + + *theParticleSystemTemplate = pSystemT; + +} // end parseParticleSystemTemplate + +//------------------------------------------------------------------------------------------------- +/** Parse an DamageFX and assign to the 'DamageFX *' at store */ +//------------------------------------------------------------------------------------------------- +void INI::parseDamageFX( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + + typedef const DamageFX *ConstDamageFXPtr; + ConstDamageFXPtr* theDamageFX = (ConstDamageFXPtr*)store; + + if (stricmp(token, "None") == 0) + { + *theDamageFX = NULL; + } + else + { + const DamageFX *fxl = TheDamageFXStore->findDamageFX(token); // could be null! + DEBUG_ASSERTCRASH(fxl, ("DamageFX %s not found!\n",token)); + // assign it, even if null! + *theDamageFX = fxl; + } + +} + +//------------------------------------------------------------------------------------------------- +/** Parse an ObjectCreationList and assign to the 'ObjectCreationList *' at store */ +//------------------------------------------------------------------------------------------------- +void INI::parseObjectCreationList( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + + typedef const ObjectCreationList *ConstObjectCreationListPtr; + ConstObjectCreationListPtr* theObjectCreationList = (ConstObjectCreationListPtr*)store; + + const ObjectCreationList *ocl = TheObjectCreationListStore->findObjectCreationList(token); // could be null! + DEBUG_ASSERTCRASH(ocl || stricmp(token, "None") == 0, ("ObjectCreationList %s not found!\n",token)); + // assign it, even if null! + *theObjectCreationList = ocl; + +} + +//------------------------------------------------------------------------------------------------- +/** Parse a upgrade template string and store as template pointer */ +//------------------------------------------------------------------------------------------------- +void INI::parseUpgradeTemplate( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + + if (!TheUpgradeCenter) + { + DEBUG_CRASH(("TheUpgradeCenter not inited yet")); + throw ERROR_BUG; + } + + const UpgradeTemplate *uu = TheUpgradeCenter->findUpgrade( AsciiString( token ) ); + DEBUG_ASSERTCRASH( uu || stricmp( token, "None" ) == 0, ("Upgrade %s not found!\n",token) ); + + typedef const UpgradeTemplate* ConstUpgradeTemplatePtr; + ConstUpgradeTemplatePtr* theUpgradeTemplate = (ConstUpgradeTemplatePtr *)store; + *theUpgradeTemplate = uu; +} + +//------------------------------------------------------------------------------------------------- +/** Parse a special power template string and store as template pointer */ +//------------------------------------------------------------------------------------------------- +void INI::parseSpecialPowerTemplate( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + + if (!TheSpecialPowerStore) + { + DEBUG_CRASH(("TheSpecialPowerStore not inited yet")); + throw ERROR_BUG; + } + + const SpecialPowerTemplate *sPowerT = TheSpecialPowerStore->findSpecialPowerTemplate( AsciiString( token ) ); + if( !sPowerT && stricmp( token, "None" ) != 0 ) + { + DEBUG_CRASH( ("[LINE: %d in '%s'] Specialpower %s not found!\n", ini->getLineNum(), ini->getFilename().str(), token) ); + } + + typedef const SpecialPowerTemplate* ConstSpecialPowerTemplatePtr; + ConstSpecialPowerTemplatePtr* theSpecialPowerTemplate = (ConstSpecialPowerTemplatePtr *)store; + *theSpecialPowerTemplate = sPowerT; +} + +//------------------------------------------------------------------------------------------------- +/** Parse a science string and store as science type */ +//------------------------------------------------------------------------------------------------- +/* static */void INI::parseScience( INI *ini, void * /*instance*/, void *store, const void *userData ) +{ + const char *token = ini->getNextToken(); + + if (!TheScienceStore) + { + DEBUG_CRASH(("TheScienceStore not inited yet")); + throw ERROR_BUG; + } + + *((ScienceType *)store) = INI::scanScience(token); + +} + +//------------------------------------------------------------------------------------------------- +/** Parse a single string token, check for that token in the index list + * of names provided and store the index into that list. + * + * NOTE: Is is assumed that we are going to store the index into + * a 4 byte integer. This works well for INT and ENUM definitions */ +//------------------------------------------------------------------------------------------------- +void INI::parseIndexList( INI* ini, void * /*instance*/, void *store, const void* userData ) +{ + ConstCharPtrArray nameList = (ConstCharPtrArray)userData; + *(Int *)store = scanIndexList(ini->getNextToken(), nameList); +} + +//------------------------------------------------------------------------------------------------- +/** returns -1 if "None", otherwise like parseIndexList **/ +//------------------------------------------------------------------------------------------------- +void INI::parseIndexListOrNone(INI* ini, void* /*instance*/, void* store, const void* userData) +{ + const char* token = ini->getNextToken(); + if (stricmp(token, "None") == 0) { + *(Int*)store = -1; + } + else { + //like parseIndexList + ConstCharPtrArray nameList = (ConstCharPtrArray)userData; + *(Int*)store = scanIndexList(token, nameList); + } +} + +//------------------------------------------------------------------------------------------------- +/** Parse a single string token, check for that token in the index list + * of names provided and store the index into that list. + * + * NOTE: Is is assumed that we are going to store the index into + * a 4 byte integer. This works well for INT and ENUM definitions */ +//------------------------------------------------------------------------------------------------- +void INI::parseByteSizedIndexList( INI* ini, void * /*instance*/, void *store, const void* userData ) +{ + ConstCharPtrArray nameList = (ConstCharPtrArray)userData; + Int value = scanIndexList(ini->getNextToken(), nameList); + if (value < 0 || value > 255) + { + DEBUG_CRASH(("Bad index list INI::parseByteSizedIndexList")); + throw ERROR_BUG; + } + *(Byte *)store = (Byte)value; +} + +//------------------------------------------------------------------------------------------------- +/** Parse a single string token, check for that token in the index list + * of names provided and store the associated value into that list. + * + * NOTE: Is is assumed that we are going to store the index into + * a 4 byte integer. This works well for INT and ENUM definitions */ +//------------------------------------------------------------------------------------------------- +void INI::parseLookupList( INI* ini, void * /*instance*/, void *store, const void* userData ) +{ + ConstLookupListRecArray lookupList = (ConstLookupListRecArray)userData; + *(Int *)store = scanLookupList(ini->getNextToken(), lookupList); +} + +//------------------------------------------------------------------------------------------------- +/** Special Handling for None = -2 (Eva_NONE), otherwise like parseIndexList **/ +//------------------------------------------------------------------------------------------------- +void INI::parseEvaNameIndexList(INI* ini, void* /*instance*/, void* store, const void* userData) +{ + const char* token = ini->getNextToken(); + if (stricmp(token, "None") == 0) { + *(Int*)store = -2; + } + else { + //like parseIndexList + ConstCharPtrArray nameList = (ConstCharPtrArray)userData; + *(Int*)store = scanIndexList(token, nameList); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + + +//------------------------------------------------------------------------------------------------- +void MultiIniFieldParse::add(const FieldParse* f, UnsignedInt e) +{ + if (m_count < MAX_MULTI_FIELDS) + { + m_fieldParse[m_count] = f; + m_extraOffset[m_count] = e; + ++m_count; + } + else + { + DEBUG_CRASH(("too many multi-fields in INI::initFromINIMultiProc")); + throw ERROR_BUG; + } +} + +//------------------------------------------------------------------------------------------------- +void INI::initFromINI( void *what, const FieldParse* parseTable ) +{ + MultiIniFieldParse p; + p.add(parseTable); + initFromINIMulti(what, p); +} + +//------------------------------------------------------------------------------------------------- +void INI::initFromINIMultiProc( void *what, BuildMultiIniFieldProc proc ) +{ + MultiIniFieldParse p; + (*proc)(p); + initFromINIMulti(what, p); +} + +//------------------------------------------------------------------------------------------------- +void INI::initFromINIMulti( void *what, const MultiIniFieldParse& parseTableList ) +{ + Bool done = FALSE; + + if( what == NULL ) + { + DEBUG_ASSERTCRASH( 0, ("INI::initFromINI - Invalid parameters supplied!\n") ); + throw INI_INVALID_PARAMS; + } + + // read each of the data fields + while( !done ) + { + + // read next line + readLine(); + + // check for end token + const char* field = strtok( m_buffer, INI::getSeps() ); + if( field ) + { + + if( stricmp( field, m_blockEndToken ) == 0 ) + { + done = TRUE; + } + else + { + Bool found = false; + for (int ptIdx = 0; ptIdx < parseTableList.getCount(); ++ptIdx) + { + int offset = 0; + const void* userData = 0; + INIFieldParseProc parse = findFieldParse(parseTableList.getNthFieldParse(ptIdx), field, offset, userData); + if (parse) + { + // parse this block and check for parse errors + try { + + (*parse)( this, what, (char *)what + offset + parseTableList.getNthExtraOffset(ptIdx), userData ); + + } catch (...) { + DEBUG_CRASH( ("[LINE: %d - FILE: '%s'] Error reading field '%s' of block '%s'\n", + INI::getLineNum(), INI::getFilename().str(), field, m_curBlockStart) ); + + + char buff[1024]; + sprintf(buff, "[LINE: %d - FILE: '%s'] Error reading field '%s'\n", INI::getLineNum(), INI::getFilename().str(), field); + throw INIException(buff); + } + + found = true; + break; + + } + } + + if (!found) + { + DEBUG_ASSERTCRASH( 0, ("[LINE: %d - FILE: '%s'] Unknown field '%s' in block '%s'\n", + INI::getLineNum(), INI::getFilename().str(), field, m_curBlockStart) ); + throw INI_UNKNOWN_TOKEN; + } + + } // end else + + } // end if + + // sanity check for reaching end of file with no closing end token + if( done == FALSE && INI::isEOF() == TRUE ) + { + + done = TRUE; + DEBUG_ASSERTCRASH( 0, ("Error parsing block '%s', in INI file '%s'. Missing '%s' token\n", + m_curBlockStart, getFilename().str(), m_blockEndToken) ); + throw INI_MISSING_END_TOKEN; + + } // end if + + } // end while + +} + +//------------------------------------------------------------------------------------------------- +/*static*/ const char* INI::getNextToken(const char* seps) +{ + if (!seps) seps = getSeps(); + const char *token = ::strtok(NULL, seps); + if (!token) + throw INI_INVALID_DATA; + return token; +} + +//------------------------------------------------------------------------------------------------- +/*static*/ const char* INI::getNextTokenOrNull(const char* seps) +{ + if (!seps) seps = getSeps(); + const char *token = ::strtok(NULL, seps); + return token; +} + +//------------------------------------------------------------------------------------------------- +/*static*/ ScienceType INI::scanScience(const char* token) +{ + return TheScienceStore->friend_lookupScience( token ); +} + +//------------------------------------------------------------------------------------------------- +/*static*/ Int INI::scanInt(const char* token) +{ + Int value; + if (sscanf( token, "%d", &value ) != 1) + throw INI_INVALID_DATA; + return value; +} + +//------------------------------------------------------------------------------------------------- +/*static*/ UnsignedInt INI::scanUnsignedInt(const char* token) +{ + UnsignedInt value; + if (sscanf( token, "%u", &value ) != 1) // unsigned int is %u, not %d + throw INI_INVALID_DATA; + return value; +} + +//------------------------------------------------------------------------------------------------- +/*static*/ Real INI::scanReal(const char* token) +{ + Real value; + if (sscanf( token, "%f", &value ) != 1) + throw INI_INVALID_DATA; + return value; +} + +//------------------------------------------------------------------------------------------------- +/*static*/ Real INI::scanPercentToReal(const char* token) +{ + Real value; + if (sscanf( token, "%f", &value ) != 1) + throw INI_INVALID_DATA; + return value / 100.0f; +} + +//------------------------------------------------------------------------------------------------- +/*static*/ Int INI::scanIndexList(const char* token, ConstCharPtrArray nameList) +{ + if( nameList == NULL || nameList[ 0 ] == NULL ) + { + + DEBUG_ASSERTCRASH( 0, ("INTERNAL ERROR! scanIndexList, invalid name list\n") ); + throw INI_INVALID_NAME_LIST; + + } + + // search for matching name + Int count = 0; + for(ConstCharPtrArray name = nameList; *name; name++, count++ ) + { + if( stricmp( *name, token ) == 0 ) + { + return count; + } + } + + DEBUG_CRASH(("token %s is not a valid member of the index list\n",token)); + throw INI_INVALID_DATA; + return 0; // never executed, but keeps compiler happy + +} +//------------------------------------------------------------------------------------------------- +/*static*/ Int INI::scanLookupList(const char* token, ConstLookupListRecArray lookupList) +{ + if( lookupList == NULL || lookupList[ 0 ].name == NULL ) + { + DEBUG_ASSERTCRASH( 0, ("INTERNAL ERROR! scanLookupList, invalid name list\n") ); + throw INI_INVALID_NAME_LIST; + } + + // search for matching name + Bool found = false; + for( const LookupListRec* lookup = &lookupList[0]; lookup->name; lookup++ ) + { + if( stricmp( lookup->name, token ) == 0 ) + { + return lookup->value; + found = true; + break; + } + } + + DEBUG_CRASH(("token %s is not a valid member of the lookup list\n",token)); + throw INI_INVALID_DATA; + return 0; // never executed, but keeps compiler happy + +} + +//------------------------------------------------------------------------------------------------- +const char* INI::getNextSubToken(const char* expected) +{ + const char* token = getNextToken(getSepsColon()); + if (stricmp(token, expected) != 0) + throw INI_INVALID_DATA; + return getNextToken(getSepsColon()); +} + +//------------------------------------------------------------------------------------------------- +/** + * Parse a "random variable". + * The format is "FIELD = low high [distribution]". + */ +void INI::parseGameClientRandomVariable( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + GameClientRandomVariable *var = static_cast(store); + + const char* token; + + token = ini->getNextToken(); + Real low = INI::scanReal(token); + + token = ini->getNextToken(); + Real high = INI::scanReal(token); + + // if omitted, assume uniform + GameClientRandomVariable::DistributionType type = GameClientRandomVariable::UNIFORM; + token = ini->getNextTokenOrNull(); + if (token) + type = (GameClientRandomVariable::DistributionType)INI::scanIndexList(token, GameClientRandomVariable::DistributionTypeNames); + + // set the range of the random variable + var->setRange( low, high, type ); +} + +//------------------------------------------------------------------------------------------------- +// parse a duration in msec and convert to duration in frames +void INI::parseDurationReal( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + Real val = scanReal(ini->getNextToken()); + *(Real *)store = ConvertDurationFromMsecsToFrames(val); +} + +//------------------------------------------------------------------------------------------------- +// parse a duration in msec and convert to duration in integral number of frames, (unsignedint) rounding UP +void INI::parseDurationUnsignedInt( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + UnsignedInt val = scanUnsignedInt(ini->getNextToken()); + *(UnsignedInt *)store = (UnsignedInt)ceilf(ConvertDurationFromMsecsToFrames((Real)val)); +} + +// ------------------------------------------------------------------------------------------------ +// parse a duration in msec and convert to duration in integral number of frames, (unsignedshort) rounding UP +void INI::parseDurationUnsignedShort( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + UnsignedInt val = scanUnsignedInt(ini->getNextToken()); + *(UnsignedShort *)store = (UnsignedShort)ceilf(ConvertDurationFromMsecsToFrames((Real)val)); +} + +//------------------------------------------------------------------------------------------------- +// parse acceleration in (dist/sec) and convert to (dist/frame) +void INI::parseVelocityReal( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + Real val = scanReal(token); + *(Real *)store = ConvertVelocityInSecsToFrames(val); +} + +//------------------------------------------------------------------------------------------------- +// parse acceleration in (dist/sec^2) and convert to (dist/frame^2) +void INI::parseAccelerationReal( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + Real val = scanReal(token); + *(Real *)store = ConvertAccelerationInSecsToFrames(val); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void INI::parseVeterancyLevelFlags(INI* ini, void* /*instance*/, void* store, const void* /*userData*/) +{ + VeterancyLevelFlags flags = VETERANCY_LEVEL_FLAGS_ALL; + for (const char* token = ini->getNextToken(); token; token = ini->getNextTokenOrNull()) + { + if (stricmp(token, "ALL") == 0) + { + flags = VETERANCY_LEVEL_FLAGS_ALL; + continue; + } + else if (stricmp(token, "NONE") == 0) + { + flags = VETERANCY_LEVEL_FLAGS_NONE; + continue; + } + else if (token[0] == '+') + { + VeterancyLevel dt = (VeterancyLevel)INI::scanIndexList(token+1, TheVeterancyNames); + flags = setVeterancyLevelFlag(flags, dt); + continue; + } + else if (token[0] == '-') + { + VeterancyLevel dt = (VeterancyLevel)INI::scanIndexList(token+1, TheVeterancyNames); + flags = clearVeterancyLevelFlag(flags, dt); + continue; + } + else + { + throw INI_UNKNOWN_TOKEN; + } + } + *(VeterancyLevelFlags*)store = flags; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void INI::parseSoundsList( INI* ini, void *instance, void *store, const void* /*userData*/ ) +{ + std::vector *vec = (std::vector*) store; + vec->clear(); + + const char* SEPS = " \t,="; + const char *c = ini->getNextTokenOrNull(SEPS); + while ( c ) + { + vec->push_back( c ); + c = ini->getNextTokenOrNull(SEPS); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void INI::parseDamageTypeFlags(INI* ini, void* /*instance*/, void* store, const void* /*userData*/) +{ + DamageTypeFlags flags = DAMAGE_TYPE_FLAGS_NONE; + flags.flip(); + + for (const char* token = ini->getNextToken(); token; token = ini->getNextTokenOrNull()) + { + if (stricmp(token, "ALL") == 0) + { + flags = DAMAGE_TYPE_FLAGS_NONE; + flags.flip(); + continue; + } + if (stricmp(token, "NONE") == 0) + { + flags = DAMAGE_TYPE_FLAGS_NONE; + continue; + } + if (token[0] == '+') + { + DamageType dt = (DamageType)DamageTypeFlags::getSingleBitFromName(token+1); + flags = setDamageTypeFlag(flags, dt); + continue; + } + if (token[0] == '-') + { + DamageType dt = (DamageType)DamageTypeFlags::getSingleBitFromName(token+1); + flags = clearDamageTypeFlag(flags, dt); + continue; + } + throw INI_UNKNOWN_TOKEN; + } + *(DamageTypeFlags*)store = flags; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void INI::parseDeathTypeFlags(INI* ini, void* /*instance*/, void* store, const void* /*userData*/) +{ + DeathTypeFlags flags = DEATH_TYPE_FLAGS_ALL; + + for (const char* token = ini->getNextToken(); token; token = ini->getNextTokenOrNull()) + { + if (stricmp(token, "ALL") == 0) + { + flags = DEATH_TYPE_FLAGS_ALL; + + if (TheGlobalData) { + flags &= ~TheGlobalData->m_defaultExcludedDeathTypes; + DEBUG_LOG(("INI::parseDeathTypeFlags - flags = %X\n", flags)); + } + else { + DEBUG_LOG(("INI::parseDeathTypeFlags - TheGlobalData is NULL\n")); + } + + continue; + } + if (stricmp(token, "NONE") == 0) + { + flags = DEATH_TYPE_FLAGS_NONE; + continue; + } + if (token[0] == '+') + { + DeathType dt = (DeathType)INI::scanIndexList(token+1, TheDeathNames); + flags = setDeathTypeFlag(flags, dt); + continue; + } + if (token[0] == '-') + { + DeathType dt = (DeathType)INI::scanIndexList(token+1, TheDeathNames); + flags = clearDeathTypeFlag(flags, dt); + continue; + } + throw INI_UNKNOWN_TOKEN; + } + *(DeathTypeFlags*)store = flags; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +// Parse a simple list, no +/- syntax allowed +void INI::parseDeathTypeFlagsList(INI* ini, void* /*instance*/, void* store, const void* /*userData*/) +{ + DeathTypeFlags flags = DEATH_TYPE_FLAGS_NONE; + for (const char* token = ini->getNextToken(); token; token = ini->getNextTokenOrNull()) + { + if (stricmp(token, "ALL") == 0) + { + flags = DEATH_TYPE_FLAGS_ALL; + continue; + } + if (stricmp(token, "NONE") == 0) + { + flags = DEATH_TYPE_FLAGS_NONE; + continue; + } + + DeathType dt = (DeathType)INI::scanIndexList(token, TheDeathNames); + flags = setDeathTypeFlag(flags, dt); + } + *(DeathTypeFlags*)store = flags; +} + +//------------------------------------------------------------------------------------------------- +// parse the line and return whether the given line is a Block declaration of the form +// [whitespace] blockType [whitespace] blockName [EOL] +// both blockType and blockName are case insensitive +Bool INI::isDeclarationOfType( AsciiString blockType, AsciiString blockName, char *bufferToCheck ) +{ + Bool retVal = true; + if (!bufferToCheck || blockType.isEmpty() || blockName.isEmpty()) { + return false; + } + // DO NOT RETURN EARLY FROM THIS FUNCTION. (beyond this point) + // we have to restore the bufferToCheck to its previous state before returning, so + // it is important to get through all the checks. + + char restoreChar; + char *tempBuff = bufferToCheck; + int blockTypeLength = blockType.getLength(); + int blockNameLength = blockName.getLength(); + + while (isspace(*tempBuff)) { + ++tempBuff; + } + + if (strlen(tempBuff) > blockTypeLength) { + restoreChar = tempBuff[blockTypeLength]; + tempBuff[blockTypeLength] = 0; + + if (stricmp(blockType.str(), tempBuff) != 0) { + retVal = false; + } + + tempBuff[blockTypeLength] = restoreChar; + tempBuff = tempBuff + blockTypeLength; + } else { + retVal = false; + } + + while (isspace(*tempBuff)) { + ++tempBuff; + } + + if (strlen(tempBuff) > blockNameLength) { + restoreChar = tempBuff[blockNameLength]; + tempBuff[blockNameLength] = 0; + + if (stricmp(blockName.str(), tempBuff) != 0) { + retVal = false; + } + + tempBuff[blockNameLength] = restoreChar; + tempBuff = tempBuff + blockNameLength; + } else { + retVal = false; + } + + while (strlen(tempBuff)) { + retVal = retVal && isspace(tempBuff[0]); + ++tempBuff; + } + + return retVal; +} + +//------------------------------------------------------------------------------------------------- +// parse the line and return whether the given line is a Block declaration of the form +// [whitespace] end [EOL] +Bool INI::isEndOfBlock( char *bufferToCheck ) +{ + Bool retVal = true; + if (!bufferToCheck) { + return false; + } + + // DO NOT RETURN EARLY FROM THIS FUNCTION (beyond this point) + // we have to restore the bufferToCheck to its previous state before returning, so + // it is important to get through all the checks. + + static const char* endString = "End"; + int endStringLength = strlen(endString); + char restoreChar; + char *tempBuff = bufferToCheck; + + + while (isspace(*tempBuff)) { + ++tempBuff; + } + + if (strlen(tempBuff) > endStringLength) { + restoreChar = tempBuff[endStringLength]; + tempBuff[endStringLength] = 0; + + if (stricmp(endString, tempBuff) != 0) { + retVal = false; + } + + tempBuff[endStringLength] = restoreChar; + tempBuff = tempBuff + endStringLength; + } else { + retVal = false; + } + + while (strlen(tempBuff)) { + retVal = retVal && isspace(tempBuff[0]); + ++tempBuff; + } + + return retVal; +} diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/DisabledTypes.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/DisabledTypes.cpp index a85984e6744..5ff439cb583 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/DisabledTypes.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/DisabledTypes.cpp @@ -1,62 +1,63 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// DisabledTypes.cpp ///////////////////////////////////////////////////////////////////////////////////// -// Kris Morness, September 2002 - -#include "PreRTS.h" - -#include "Common/DisabledTypes.h" -#include "Common/BitFlagsIO.h" - -const char* DisabledMaskType::s_bitNameList[] = -{ - "DEFAULT", - "DISABLED_HACKED", - "DISABLED_EMP", - "DISABLED_HELD", - "DISABLED_PARALYZED", - "DISABLED_UNMANNED", - "DISABLED_UNDERPOWERED", - "DISABLED_FREEFALL", - - "DISABLED_AWESTRUCK", - "DISABLED_BRAINWASHED", - "DISABLED_SUBDUED", - - "DISABLED_SCRIPT_DISABLED", - "DISABLED_SCRIPT_UNDERPOWERED", - - "DISABLED_TELEPORT", - - NULL -}; - -DisabledMaskType DISABLEDMASK_NONE; // inits to all zeroes -DisabledMaskType DISABLEDMASK_ALL; - -void initDisabledMasks() -{ - SET_ALL_DISABLEDMASK_BITS( DISABLEDMASK_ALL ); -} +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// DisabledTypes.cpp ///////////////////////////////////////////////////////////////////////////////////// +// Kris Morness, September 2002 + +#include "PreRTS.h" + +#include "Common/DisabledTypes.h" +#include "Common/BitFlagsIO.h" + +const char* DisabledMaskType::s_bitNameList[] = +{ + "DEFAULT", + "DISABLED_HACKED", + "DISABLED_EMP", + "DISABLED_HELD", + "DISABLED_PARALYZED", + "DISABLED_UNMANNED", + "DISABLED_UNDERPOWERED", + "DISABLED_FREEFALL", + + "DISABLED_AWESTRUCK", + "DISABLED_BRAINWASHED", + "DISABLED_SUBDUED", + + "DISABLED_SCRIPT_DISABLED", + "DISABLED_SCRIPT_UNDERPOWERED", + + "DISABLED_TELEPORT", + "DISABLED_CHRONO", + + NULL +}; + +DisabledMaskType DISABLEDMASK_NONE; // inits to all zeroes +DisabledMaskType DISABLEDMASK_ALL; + +void initDisabledMasks() +{ + SET_ALL_DISABLEDMASK_BITS( DISABLEDMASK_ALL ); +} diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp index 739c7ff5fbf..17136466723 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp @@ -1,816 +1,817 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: MemoryInit.cpp -//----------------------------------------------------------------------------- -// -// Westwood Studios Pacific. -// -// Confidential Information -// Copyright (C) 2001 - All Rights Reserved -// -//----------------------------------------------------------------------------- -// -// Project: RTS3 -// -// File name: MemoryInit.cpp -// -// Created: Steven Johnson, August 2001 -// -// Desc: Memory manager -// -// ---------------------------------------------------------------------------- -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -// SYSTEM INCLUDES - -// USER INCLUDES -#include "Lib/BaseType.h" -#include "Common/GameMemory.h" - -#ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -//----------------------------------------------------------------------------- -void userMemoryManagerGetDmaParms(Int *numSubPools, const PoolInitRec **pParms) -{ - static const PoolInitRec defaultDMA[7] = - { - // name, allocsize, initialcount, overflowcount - { "dmaPool_16", 16, 130000, 10000 }, - { "dmaPool_32", 32, 250000, 10000 }, - { "dmaPool_64", 64, 100000, 10000 }, - { "dmaPool_128", 128, 80000, 10000 }, - { "dmaPool_256", 256, 20000, 5000 }, - { "dmaPool_512", 512, 16000, 5000 }, - { "dmaPool_1024", 1024, 6000, 1024} - }; - - *numSubPools = 7; - *pParms = defaultDMA; -} - -//----------------------------------------------------------------------------- -struct PoolSizeRec -{ - const char* name; - Int initial; - Int overflow; -}; - -//----------------------------------------------------------------------------- -// And please be careful of duplicates. They are not rejected. -// not const -- we might override from INI -static PoolSizeRec sizes[] = -{ - { "PartitionContactListNode", 2048, 512 }, - { "BattleshipUpdate", 32, 32 }, - { "FlyToDestAndDestroyUpdate", 32, 32 }, - { "MusicTrack", 32, 32 }, - { "PositionalSoundPool", 32, 32 }, - { "GameMessage", 2048, 32 }, - { "NameKeyBucketPool", 9000, 1024 }, - { "ObjectSellInfo", 16, 16 }, - { "ProductionPrerequisitePool", 1024, 32 }, - { "RadarObject", 512, 32 }, - { "ResourceGatheringManager", 16, 16 }, - { "SightingInfo", 8192, 2048 },// Looks big, but all 3000 objects used to have 4 just built in. - { "SpecialPowerTemplate", 84, 32 }, - { "StateMachinePool", 32, 32 }, - { "TeamPool", 128, 32 }, // if you increase this, increase player/team relation map pools - { "PlayerRelationMapPool", 128, 32 }, - { "TeamRelationMapPool", 128, 32 }, - { "TeamPrototypePool", 256, 32 }, - { "TerrainType", 256, 32 }, - { "ThingTemplatePool", 2120, 32 }, - { "TunnelTracker", 16, 16 }, - { "Upgrade", 16, 16 }, - { "UpgradeTemplate", 128, 16 }, - { "Anim2D", 32, 32 }, - { "CommandButton", 1024, 256 }, - { "CommandSet", 820, 16 }, - { "DisplayString", 32, 32 }, - { "WebBrowserURL", 16, 16 }, - { "Drawable", 4096, 32 }, - { "Image", 2048, 32 }, - { "ParticlePool", 1400, 1024 }, - { "ParticleSystemTemplatePool", 1100, 32 }, - { "ParticleSystemPool", 1024, 32 }, - { "TerrainRoadType", 100, 32, }, - { "WindowLayoutPool", 32, 32 }, - { "AnimatedParticleSysBoneClientUpdate", 16, 16 }, - { "SwayClientUpdate", 32, 32 }, - { "BeaconClientUpdate", 64, 32 }, - { "AIGroupPool", 64, 32 }, - { "AIDockMachinePool", 256, 32 }, - { "AIGuardMachinePool", 32, 32 }, - { "AIGuardRetaliateMachinePool", 32, 32 }, - { "AITNGuardMachinePool", 32, 32 }, - { "PathNodePool", 8192, 1024 }, - { "PathPool", 256, 16 }, - { "WorkOrder", 32, 32 }, - { "TeamInQueue", 32, 32 }, - { "AIPlayer", 12, 4 }, - { "AISkirmishPlayer", 8, 8 }, - { "AIStateMachine", 600, 32 }, - { "JetAIStateMachine", 64, 32 }, - { "HeliAIStateMachine", 64, 32 }, - { "VtolAIStateMachine", 64, 32 }, - { "AIAttackMoveStateMachine", 2048, 32 }, - { "AIAttackThenIdleStateMachine", 512, 32 }, - { "AttackStateMachine", 512, 32 }, - { "CrateTemplate", 32, 32 }, - { "ExperienceTrackerPool", 2048, 512 }, - { "FiringTrackerPool", 4096, 256 }, - { "ObjectRepulsorHelper", 1024, 256 }, - { "ObjectSMCHelperPool", 2048, 256 }, - { "ObjectWeaponStatusHelperPool", 4096, 256 }, - { "ObjectDefectionHelperPool", 2048, 256 }, - { "StatusDamageHelper", 1500, 256 }, - { "SubdualDamageHelper", 1500, 256 }, - { "TempWeaponBonusHelper", 4096, 256 }, - { "Locomotor", 2048, 32 }, - { "LocomotorTemplate", 192, 32 }, - { "ObjectPool", 1500, 256 }, - { "SimpleObjectIteratorPool", 32, 32 }, - { "SimpleObjectIteratorClumpPool", 4096, 32 }, - { "PartitionDataPool", 2048, 512 }, - { "BuildEntry", 32, 32 }, - { "Weapon", 4096, 32 }, - { "WeaponTemplate", 360, 32 }, - { "AIUpdateInterface", 600, 32 }, - { "ActiveBody", 1024, 32 }, - { "ActiveShroudUpgrade", 32, 32 }, - { "AssistedTargetingUpdate", 32, 32 }, - { "AudioEventInfo", 4096, 64 }, - { "AudioRequest", 256, 8 }, - { "AutoHealBehavior", 1024, 256 }, - { "WeaponBonusUpdate", 16, 16 }, - { "GrantStealthBehavior", 4096, 32 }, - { "NeutronBlastBehavior", 4096, 32 }, - { "CountermeasuresBehavior", 256, 32 }, - { "BaseRegenerateUpdate", 128, 32 }, - { "BoneFXDamage", 64, 32 }, - { "BoneFXUpdate", 64, 32 }, - { "BridgeBehavior", 4, 4 }, - { "BridgeTowerBehavior", 32, 32 }, - { "BridgeScaffoldBehavior", 32, 32 }, - { "CaveContain", 16, 16 }, - { "HealContain", 32, 32 }, - { "CreateCrateDie", 256, 128 }, - { "CreateObjectDie", 1024, 32 }, - { "EjectPilotDie", 1024, 32 }, - { "CrushDie", 1024, 32 }, - { "DamDie", 8, 8 }, - { "DeliverPayloadStateMachine", 32, 32 }, - { "DeliverPayloadAIUpdate", 32, 32 }, - { "DeletionUpdate", 128, 32 }, - { "SmartBombTargetHomingUpdate", 8, 8 }, - { "DynamicAudioEventInfo", 16, 256 }, // Note: some levels have none, some have lots. Since all are allocated at level load time, we can set this low for the levels with none. - { "HackInternetStateMachine", 32, 32 }, - { "HackInternetAIUpdate", 32, 32 }, - { "MissileAIUpdate", 512, 32 }, - { "DumbProjectileBehavior", 64, 32 }, - { "FreeFallProjectileBehavior", 32, 32 }, - { "DestroyDie", 1024, 32 }, - { "UpgradeDie", 128, 32 }, - { "KeepObjectDie", 128, 32 }, - { "DozerAIUpdate", 32, 32 }, - { "DynamicGeometryInfoUpdate", 16, 16 }, - { "DynamicShroudClearingRangeUpdate", 128, 16 }, - { "FXListDie", 1024, 32 }, - { "FireSpreadUpdate", 2048, 128 }, - { "FirestormDynamicGeometryInfoUpdate", 16, 16 }, - { "FireWeaponCollide", 2048, 32 }, - { "FireWeaponUpdate", 32, 32 }, - { "FlammableUpdate", 512, 256 }, - { "FloatUpdate", 512, 128 }, - { "TensileFormationUpdate", 256, 32 }, - { "GarrisonContain", 256, 32 }, - { "HealCrateCollide", 32, 32 }, - { "HeightDieUpdate", 32, 32 }, - { "ScatterShotUpdate", 128, 64 }, - { "FireWeaponWhenDamagedBehavior", 32, 32 }, - { "FireWeaponWhenDeadBehavior", 128, 64 }, - { "DelayedUpgradeBehavior", 128, 64 }, - { "GenerateMinefieldBehavior", 32, 32 }, - { "HelicopterSlowDeathBehavior", 64, 32 }, - { "ParkingPlaceBehavior", 32, 32 }, - { "FlightDeckBehavior", 8, 8 }, -#ifdef ALLOW_SURRENDER - { "POWTruckAIUpdate", 32, 32, }, - { "POWTruckBehavior", 32, 32, }, - { "PrisonBehavior", 32, 32 }, - { "PrisonVisual", 32, 32 }, - { "PropagandaCenterBehavior", 16, 16 }, -#endif - { "PropagandaTowerBehavior", 16, 16 }, - { "BunkerBusterBehavior", 16, 16 }, - { "ObjectTracker", 128, 32 }, - { "OCLUpdate", 16, 16 }, - { "BodyParticleSystem", 196, 64 }, - { "HighlanderBody", 2048, 128 }, - { "UndeadBody", 32, 32 }, - { "HordeUpdate", 128, 32 }, - { "ImmortalBody", 128, 256 }, - { "InactiveBody", 2048, 32 }, - { "InstantDeathBehavior", 512, 32 }, - { "LaserUpdate", 32, 32 }, - { "PointDefenseLaserUpdate", 32, 32 }, - { "CleanupHazardUpdate", 32, 32 }, - { "AutoFindHealingUpdate", 256, 32 }, - { "CommandButtonHuntUpdate", 512, 8 }, - { "PilotFindVehicleUpdate", 256, 32 }, - { "DemoTrapUpdate", 32, 32 }, - { "ParticleUplinkCannonUpdate", 16, 16 }, - { "SpectreGunshipUpdate", 8, 8 }, - { "SpectreGunshipDeploymentUpdate", 8, 8 }, - { "BaikonurLaunchPower", 4, 4 }, - { "RadiusDecalUpdate", 16, 16 }, - { "RadiusDecalBehavior", 32, 32 }, - { "BattlePlanUpdate", 32, 32 }, - { "LifetimeUpdate", 32, 32 }, - { "LocomotorSetUpgrade", 512, 128 }, - { "LockWeaponCreate", 64, 128 }, - { "AutoDepositUpdate", 256, 32 }, - { "NeutronMissileUpdate", 512, 32 }, - { "MoneyCrateCollide", 48, 16 }, - { "NeutronMissileSlowDeathBehavior", 8, 8 }, - { "OpenContain", 128, 32 }, - { "OverchargeBehavior", 32, 32 }, - { "OverlordContain", 32, 32 }, - { "HelixContain", 32, 32 }, - { "ParachuteContain", 128, 32 }, - { "PhysicsBehavior", 600, 32 }, - { "PoisonedBehavior", 512, 64 }, - { "ProductionEntry", 32, 32 }, - { "ProductionUpdate", 256, 32 }, - { "ProjectileStreamUpdate", 32, 32 }, - { "ProneUpdate", 128, 32 }, - { "QueueProductionExitUpdate", 32, 32 }, - { "RadarUpdate", 16, 16 }, - { "RadarUpgrade", 16, 16 }, - { "AnimationSteeringUpdate", 1024, 32 }, - { "SupplyWarehouseCripplingBehavior", 16, 16 }, - { "CostModifierUpgrade", 32, 32 }, - { "ProductionTimeModifierUpgrade", 32, 32 }, - { "UnitProductionBonusUpgrade", 64, 32 }, - { "CashBountyPower", 32, 32 }, - { "CleanupAreaPower", 32, 32 }, - { "ObjectCreationUpgrade", 196, 32 }, - { "MinefieldBehavior", 256, 32 }, - { "JetSlowDeathBehavior", 64, 32 }, - { "BattleBusSlowDeathBehavior", 64, 32 }, - { "RebuildHoleBehavior", 64, 32 }, - { "RebuildHoleExposeDie", 64, 32 }, - { "RepairDockUpdate", 32, 32 }, -#ifdef ALLOW_SURRENDER - { "PrisonDockUpdate", 32, 32 }, -#endif - { "RailedTransportDockUpdate", 16, 16 }, - { "RailedTransportAIUpdate", 16, 16 }, - { "RailedTransportContain", 16, 16 }, - { "RailroadBehavior", 16, 16 }, - { "SalvageCrateCollide", 32, 32 }, - { "ShroudCrateCollide", 32, 32 }, - { "SlavedUpdate", 64, 32 }, - { "SlowDeathBehavior", 1400, 256 }, - { "SpyVisionUpdate", 16, 16 }, - { "DefaultProductionExitUpdate", 32, 32 }, - { "SpawnPointProductionExitUpdate", 32, 32 }, - { "SpawnBehavior", 32, 32 }, - { "SpecialPowerCompletionDie", 32, 32 }, - { "SpecialPowerCreate", 32, 32 }, - { "PreorderCreate", 32, 32 }, - { "SpecialAbility", 512, 32 }, - { "SpecialAbilityUpdate", 512, 32 }, - { "MissileLauncherBuildingUpdate", 32, 32 }, - { "SquishCollide", 512, 32 }, - { "StructureBody", 512, 64 }, - { "HiveStructureBody", 64, 32 }, //Stinger sites - { "StructureCollapseUpdate", 32, 32 }, - { "StructureToppleUpdate", 32, 32 }, - { "SupplyCenterCreate", 32, 32 }, - { "SupplyCenterDockUpdate", 32, 32 }, - { "SupplyCenterProductionExitUpdate", 32, 32 }, - { "SupplyTruckStateMachine", 256, 32 }, - { "SupplyTruckAIUpdate", 32, 32 }, - { "SupplyWarehouseCreate", 48, 16 }, - { "SupplyWarehouseDockUpdate", 48, 16 }, - { "EnemyNearUpdate", 1024, 32 }, - { "TechBuildingBehavior", 32, 32 }, - { "ToppleUpdate", 256, 128 }, - { "TransitionDamageFX", 384, 128 }, - { "TransportAIUpdate", 64, 32 }, - { "TransportContain", 128, 32 }, - { "RiderChangeContain", 128, 32 }, - { "InternetHackContain", 16, 16 }, - { "TunnelContain", 8, 8 }, - { "TunnelContainDie", 32, 32 }, - { "TunnelCreate", 32, 32 }, - { "TurretAI", 256, 32 }, - { "TurretStateMachine", 128, 32 }, - { "TurretSwapUpgrade", 512, 128 }, - { "UnitCrateCollide", 32, 32 }, - { "UnpauseSpecialPowerUpgrade", 32, 32 }, - { "VeterancyCrateCollide", 32, 32 }, - { "VeterancyGainCreate", 512, 128 }, - { "ConvertToCarBombCrateCollide", 256, 128 }, - { "ConvertToHijackedVehicleCrateCollide", 256, 128 }, - { "SabotageCommandCenterCrateCollide", 256, 128 }, - { "SabotageFakeBuildingCrateCollide", 256, 128 }, - { "SabotageInternetCenterCrateCollide", 256, 128 }, - { "SabotageMilitaryFactoryCrateCollide", 256, 128 }, - { "SabotagePowerPlantCrateCollide", 256, 128 }, - { "SabotageSuperweaponCrateCollide", 256, 128 }, - { "SabotageSupplyCenterCrateCollide", 256, 128 }, - { "SabotageSupplyDropzoneCrateCollide", 256, 128 }, - { "JetAIUpdate", 64, 32 }, - { "ChinookAIUpdate", 32, 32 }, - { "WanderAIUpdate", 32, 32 }, - { "TeleporterAIUpdate", 64, 32 }, - { "WaveGuideUpdate", 16, 16 }, - { "ArmorDamageScalarUpdate", 256, 32 }, - { "WeaponBonusUpgrade", 512, 128 }, - { "WeaponSetUpgrade", 512, 128 }, - { "ArmorUpgrade", 512, 128 }, - { "WorkerAIUpdate", 128, 128 }, - { "WorkerStateMachine", 128, 128 }, - { "ChinookAIStateMachine", 32, 32 }, - { "DeployStyleAIUpdate", 32, 32 }, - { "AssaultTransportAIUpdate", 64, 32 }, - { "StreamingArchiveFile", 8, 8 }, - - { "DozerActionStateMachine", 256, 32 }, - { "DozerPrimaryStateMachine", 256, 32 }, - { "W3DDisplayString", 1400, 128 }, - { "W3DDefaultDraw", 1024, 128 }, - { "W3DDebrisDraw", 128, 128 }, - { "W3DDependencyModelDraw", 64, 64 }, - { "W3DLaserDraw", 32, 32 }, - { "W3DModelDraw", 2048, 512 }, - { "W3DOverlordTankDraw", 64, 64 }, - { "W3DOverlordTruckDraw", 64, 64 }, - { "W3DOverlordAircraftDraw", 64, 64 }, - { "W3DPoliceCarDraw", 32, 32 }, - { "W3DProjectileStreamDraw", 32, 32 }, - { "W3DRopeDraw", 32, 32 }, - { "W3DScienceModelDraw", 32, 32 }, - { "W3DSupplyDraw", 40, 16 }, - { "W3DTankDraw", 256, 32 }, - { "W3DTreeDraw", 16, 16 }, - { "W3DPropDraw", 16, 16 }, - { "W3DTracerDraw", 64, 32 }, - { "W3DTruckDraw", 128, 32 }, - { "W3DTankTruckDraw", 32, 16 }, - { "W3DTreeTextureClass", 4, 4 }, - { "DefaultSpecialPower", 32, 32 }, - { "OCLSpecialPower", 96, 32 }, - { "FireWeaponPower", 32, 32 }, -#ifdef ALLOW_DEMORALIZE - { "DemoralizeSpecialPower", 16, 16, }, -#endif - { "CashHackSpecialPower", 32, 32 }, - { "CommandSetUpgrade", 32, 32 }, - { "PassengersFireUpgrade", 32, 32 }, - { "GrantUpgradeCreate", 256, 32 }, - { "GrantScienceUpgrade", 256, 32 }, - { "ReplaceObjectUpgrade", 32, 32 }, - { "ModelConditionUpgrade", 32, 32 }, - { "UpgradeSpecialPower", 64, 32 }, - { "SpyVisionSpecialPower", 256, 32 }, - { "StealthDetectorUpdate", 256, 32 }, - { "StealthUpdate", 512, 128 }, - { "StealthUpgrade", 256, 32 }, - { "StatusBitsUpgrade", 128, 128 }, - { "SubObjectsUpgrade", 128, 128 }, - { "ExperienceScalarUpgrade", 256, 128 }, - { "MaxHealthUpgrade", 128, 128 }, - { "WeaponBonusUpgrade", 128, 64 }, - { "StickyBombUpdate", 64, 32 }, - { "FireOCLAfterWeaponCooldownUpdate", 64, 32 }, - { "HijackerUpdate", 64, 32 }, - { "ChinaMinesUpgrade", 64, 32 }, - { "PowerPlantUpdate", 48, 16 }, - { "PowerPlantUpgrade", 48, 16 }, - { "DefectorSpecialPower", 16, 16 }, - { "CheckpointUpdate", 16, 16 }, - { "MobNexusContain", 128, 32 }, - { "MobMemberSlavedUpdate", 64, 32 }, - { "EMPUpdate", 64, 32 }, - { "LeafletDropBehavior", 64, 32 }, - { "Overridable", 32, 32 }, - - { "W3DGameWindow", 700, 256 }, - { "SuccessState", 32, 32 }, - { "FailureState", 32, 32 }, - { "ContinueState", 32, 32 }, - { "SleepState", 32, 32 }, - - { "AIDockWaitForClearanceState", 256, 32 }, - { "AIDockProcessDockState", 256, 32 }, - { "AIGuardInnerState", 32, 32 }, - { "AIGuardIdleState", 32, 32 }, - { "AIGuardOuterState", 32, 32 }, - { "AIGuardReturnState", 32, 32 }, - { "AIGuardPickUpCrateState", 32, 32 }, - { "AIGuardAttackAggressorState", 32, 32 }, - { "AIGuardRetaliateInnerState", 32, 32 }, - { "AIGuardRetaliateIdleState", 32, 32 }, - { "AIGuardRetaliateOuterState", 32, 32 }, - { "AIGuardRetaliateReturnState", 32, 32 }, - { "AIGuardRetaliatePickUpCrateState", 32, 32 }, - { "AIGuardRetaliateAttackAggressorState", 32, 32 }, - { "AITNGuardInnerState", 32, 32 }, - { "AITNGuardIdleState", 32, 32 }, - { "AITNGuardOuterState", 32, 32 }, - { "AITNGuardReturnState", 32, 32 }, - { "AITNGuardPickUpCrateState", 32, 32 }, - { "AITNGuardAttackAggressorState", 32, 32 }, - { "AIIdleState", 2400, 32 }, - { "AIRappelState", 600, 32 }, - { "AIBusyState", 600, 32 }, - { "AIWaitState", 600, 32 }, - { "AIAttackState", 4096, 32 }, - { "AIAttackSquadState", 600, 32 }, - { "AIDeadState", 600, 32 }, - { "AIDockState", 600, 32 }, - { "AIExitState", 600, 32 }, - { "AIExitInstantlyState", 600, 32 }, - { "AIGuardState", 600, 32 }, - { "AIGuardRetaliateState", 600, 32 }, - { "AITunnelNetworkGuardState", 600, 32 }, - { "AIHuntState", 600, 32 }, - { "AIAttackAreaState", 600, 32 }, - { "AIFaceState", 1200, 32 }, - { "ApproachState", 600, 32 }, - { "DeliveringState", 600, 32 }, - { "ConsiderNewApproachState", 600, 32 }, - { "RecoverFromOffMapState", 600, 32 }, - { "HeadOffMapState", 600, 32 }, - { "CleanUpState", 600, 32 }, - { "HackInternetState", 600, 32 }, - { "PackingState", 600, 32 }, - { "UnpackingState", 600, 32 }, - { "SupplyTruckWantsToPickUpOrDeliverBoxesState", 600, 32 }, - { "RegroupingState", 600, 32 }, - { "DockingState", 600, 32 }, - { "ChinookEvacuateState", 32, 32 }, - { "ChinookHeadOffMapState", 32, 32 }, - { "ChinookTakeoffOrLandingState", 32, 32 }, - { "ChinookCombatDropState", 32, 32 }, - { "DozerActionPickActionPosState", 256, 32 }, - { "DozerActionMoveToActionPosState", 256, 32 }, - { "DozerActionDoActionState", 256, 32 }, - { "DozerPrimaryIdleState", 256, 32 }, - { "DozerActionState", 256, 32 }, - { "DozerPrimaryGoingHomeState", 256, 32 }, - { "JetAwaitingRunwayState", 64, 32 }, - { "JetOrHeliCirclingDeadAirfieldState", 64, 32 }, - { "HeliTakeoffOrLandingState", 64, 32 }, - { "VtolTakeoffOrLandingState", 64, 32 }, - { "JetOrHeliParkOrientState", 64, 32 }, - { "VtolParkOrientState", 64, 32 }, - { "JetOrHeliReloadAmmoState", 64, 32 }, - { "SupplyTruckBusyState", 600, 32 }, - { "SupplyTruckIdleState", 600, 32 }, - { "ActAsDozerState", 600, 32 }, - { "ActAsSupplyTruckState", 600, 32 }, - { "AIDockApproachState", 256, 32 }, - { "AIDockAdvancePositionState", 256, 32 }, - { "AIDockMoveToEntryState", 256, 32 }, - { "AIDockMoveToDockState", 256, 32 }, - { "AIDockMoveToExitState", 256, 32 }, - { "AIDockMoveToRallyState", 256, 32 }, - { "AIMoveToState", 600, 32 }, - { "AIMoveOutOfTheWayState", 600, 32 }, - { "AIMoveAndTightenState", 600, 32 }, - { "AIMoveAwayFromRepulsorsState", 600, 32 }, - { "AIAttackApproachTargetState", 96, 32 }, - { "AIAttackPursueTargetState", 96, 32 }, - { "AIAttackAimAtTargetState", 96, 32 }, - { "AIAttackFireWeaponState", 256, 32 }, - { "AIPickUpCrateState", 4096, 32 }, - { "AIFollowWaypointPathState", 1200, 32 }, - { "AIFollowWaypointPathExactState", 1200, 32 }, - { "AIWanderInPlaceState", 600, 32 }, - { "AIFollowPathState", 1200, 32 }, - { "AIMoveAndEvacuateState", 1200, 32 }, - { "AIMoveAndDeleteState", 600, 32 }, - { "AIEnterState", 600, 32 }, - { "JetOrHeliReturningToDeadAirfieldState", 64, 32 }, - { "JetOrHeliReturnForLandingState", 64, 32 }, - { "TurretAIIdleState", 600, 32 }, - { "TurretAIIdleScanState", 600, 32 }, - { "TurretAIAimTurretState", 600, 32 }, - { "TurretAIRecenterTurretState", 600, 32 }, - { "TurretAIHoldTurretState", 600, 32 }, - { "JetOrHeliTaxiState", 64, 32 }, - { "JetTakeoffOrLandingState", 64, 32 }, - { "JetPauseBeforeTakeoffState", 64, 32 }, - { "AIAttackMoveToState", 600, 32 }, - { "AIAttackFollowWaypointPathState", 1200, 32 }, - { "AIWanderState", 600, 32 }, - { "AIPanicState", 600, 32 }, - { "ChinookMoveToBldgState", 32, 32 }, - { "ChinookRecordCreationState", 32, 32 }, - { "ScienceInfo", 96, 32 }, - { "RankInfo", 32, 32 }, - - { "FireWeaponNugget", 32, 32 }, - { "AttackNugget", 32, 32 }, - { "DeliverPayloadNugget", 48, 32 }, - { "ApplyRandomForceNugget", 32, 32 }, - { "GenericObjectCreationNugget", 632, 32 }, - { "SoundFXNugget", 320, 32 }, - { "TracerFXNugget", 32, 32 }, - { "RayEffectFXNugget", 32, 32 }, - { "LightPulseFXNugget", 68, 32 }, - { "ViewShakeFXNugget", 140, 32 }, - { "TerrainScorchFXNugget", 48, 32 }, - { "ParticleSystemFXNugget", 832, 32 }, - { "FXListAtBonePosFXNugget", 32, 32 }, - { "Squad", 256, 32 }, - { "BuildListInfo", 400, 64 }, - - { "ScriptGroup", 128, 32 }, - { "OrCondition", 1024, 256 }, - { "ScriptAction", 2600, 512 }, - { "Script", 1024, 256 }, - { "Parameter", 8192, 1024 }, - { "Condition", 2048, 256 }, - { "Template", 32, 32 }, - { "ScriptList", 32, 32 }, - { "AttackPriorityInfo", 32, 32 }, - { "SequentialScript", 32, 32 }, - { "Win32LocalFile", 1024, 256 }, - { "StdLocalFile", 1024, 256 }, - { "RAMFile", 32, 32 }, - { "BattlePlanBonuses", 32, 32 }, - { "KindOfPercentProductionChange", 32, 32 }, - { "UserParser", 4096, 256 }, - { "XferBlockData", 32, 32 }, - { "EvaCheckInfo", 52, 16 }, - { "SuperweaponInfo", 32, 32 }, - { "NamedTimerInfo", 32, 32 }, - { "PopupMessageData", 32, 32 }, - { "FloatingTextData", 32, 32 }, - { "MapObject", 5000, 1024 }, - { "Waypoint", 1024, 32 }, - { "PolygonTrigger", 64, 64 }, - { "Bridge", 32, 32 }, - { "Mapping", 384, 64 }, - { "OutputChunk", 32, 32 }, - { "InputChunk", 32, 32 }, - { "AnimateWindow", 32, 32 }, - { "GameFont", 32, 32 }, - { "NetCommandRef", 256, 32 }, - { "GameMessageArgument", 1024, 256 }, - { "GameMessageParserArgumentType", 32, 32 }, - { "GameMessageParser", 32, 32 }, - { "WeaponBonusSet", 96, 32 }, - { "Campaign", 32, 32 }, - { "Mission", 88, 32 }, - { "ModalWindow", 32, 32 }, - { "NetPacket", 32, 32 }, - { "AISideInfo", 32, 32 }, - { "AISideBuildList", 32, 32 }, - { "MetaMapRec", 256, 32 }, - { "TransportStatus", 32, 32 }, - { "Anim2DTemplate", 32, 32 }, - { "ObjectTypes", 32, 32 }, - { "NetCommandList", 512, 32 }, - { "TurretAIData", 256, 32 }, - { "NetCommandMsg", 32, 32 }, - { "NetGameCommandMsg", 64, 32 }, - { "NetAckBothCommandMsg", 32, 32 }, - { "NetAckStage1CommandMsg", 32, 32 }, - { "NetAckStage2CommandMsg", 32, 32 }, - { "NetFrameCommandMsg", 32, 32 }, - { "NetPlayerLeaveCommandMsg", 32, 32 }, - { "NetRunAheadMetricsCommandMsg", 32, 32 }, - { "NetRunAheadCommandMsg", 32, 32 }, - { "NetDestroyPlayerCommandMsg", 32, 32 }, - { "NetDisconnectFrameCommandMsg", 32, 32 }, - { "NetDisconnectScreenOffCommandMsg", 32, 32 }, - { "NetFrameResendRequestCommandMsg", 32, 32 }, - { "NetKeepAliveCommandMsg", 32, 32 }, - { "NetDisconnectKeepAliveCommandMsg", 32, 32 }, - { "NetDisconnectPlayerCommandMsg", 32, 32 }, - { "NetPacketRouterQueryCommandMsg", 32, 32 }, - { "NetPacketRouterAckCommandMsg", 32, 32 }, - { "NetDisconnectChatCommandMsg", 32, 32 }, - { "NetChatCommandMsg", 32, 32 }, - { "NetDisconnectVoteCommandMsg", 32, 32 }, - { "NetProgressCommandMsg", 32, 32 }, - { "NetWrapperCommandMsg", 32, 32 }, - { "NetFileCommandMsg", 32, 32 }, - { "NetFileAnnounceCommandMsg", 32, 32 }, - { "NetFileProgressCommandMsg", 32, 32 }, - { "NetCommandWrapperListNode", 32, 32 }, - { "NetCommandWrapperList", 32, 32 }, - { "Connection", 32, 32 }, - { "User", 32, 32 }, - { "FrameDataManager", 32, 32 }, - { "DrawableIconInfo", 32, 32 }, - { "TintEnvelope", 128, 32 }, - { "DynamicAudioEventRTS", 4000, 256 }, - { "DrawableLocoInfo", 128, 32 }, - { "W3DPrototypeClass", 512, 256 }, - { "EnumeratedIP", 32, 32 }, - { "WaterTransparencySetting", 4, 4 }, - { "WeatherSetting", 4, 4 }, - - // W3D pools! - { "BoxPrototypeClass", 128, 128 }, - { "SpherePrototypeClass", 32, 32 }, - { "SoundRenderObjPrototypeClass", 32, 32 }, - { "RingPrototypeClass", 32, 32 }, - { "PrimitivePrototypeClass", 8192, 32 }, - { "HModelPrototypeClass", 256, 32 }, - { "ParticleEmitterPrototypeClass", 32, 32 }, - { "NullPrototypeClass", 32, 32 }, - { "HLodPrototypeClass", 700, 128 }, - { "HLodDefClass", 700, 128 }, - { "DistLODPrototypeClass", 32, 32 }, - { "DazzlePrototypeClass", 32, 32 }, - { "CollectionPrototypeClass", 32, 32 }, - { "BoxPrototypeClass", 256, 32 }, - { "AggregatePrototypeClass", 32, 32 }, - { "OBBoxRenderObjClass", 512, 128 }, - { "AABoxRenderObjClass", 32, 32 }, - { "VertexMaterialClass", 6000, 2048 }, - { "TextureClass", 1200, 256 }, - { "CloudMapTerrainTextureClass", 4, 4 }, - { "ScorchTextureClass", 4, 4 }, - { "LightMapTerrainTextureClass", 4, 4 }, - { "AlphaEdgeTextureClass", 4, 4 }, - { "AlphaTerrainTextureClass", 4, 4 }, - { "TerrainTextureClass", 4, 4 }, - { "MeshClass", 14000, 2000 }, - { "HTreeClass", 2048, 512 }, - { "HLodClass", 2048, 512 }, - { "MeshModelClass", 8192, 32 }, - { "ShareBufferClass", 32768, 1024 }, - { "AABTreeClass", 300, 128 }, - { "MotionChannelClass", 16384, 32 }, - { "BitChannelClass", 84, 32 }, - { "TimeCodedMotionChannelClass", 116, 32 }, - { "AdaptiveDeltaMotionChannelClass", 32, 32 }, - { "TimeCodedBitChannelClass", 32, 32 }, - { "UVBufferClass", 8192, 32 }, - { "TexBufferClass", 384, 128 }, - { "MatBufferClass", 256, 128 }, - { "MatrixMapperClass", 32, 32 }, - { "ScaleTextureMapperClass", 32, 32 }, - { "LinearOffsetTextureMapperClass", 96, 32 }, - { "GridTextureMapperClass", 32, 32 }, - { "RotateTextureMapperClass", 32, 32 }, - { "SineLinearOffsetTextureMapperClass", 32, 32 }, - { "StepLinearOffsetTextureMapperClass", 32, 32 }, - { "ZigZagLinearOffsetTextureMapperClass", 32, 32 }, - { "ClassicEnvironmentMapperClass", 32, 32 }, - { "EnvironmentMapperClass", 256, 32 }, - { "EdgeMapperClass", 32, 32 }, - { "WSClassicEnvironmentMapperClass", 32, 32 }, - { "WSEnvironmentMapperClass", 32, 32 }, - { "GridClassicEnvironmentMapperClass", 32, 32 }, - { "GridEnvironmentMapperClass", 32, 32 }, - { "ScreenMapperClass", 32, 32 }, - { "RandomTextureMapperClass", 32, 32 }, - { "BumpEnvTextureMapperClass", 32, 32 }, - { "MeshLoadContextClass", 4, 4 }, - { "MaterialInfoClass", 8192, 32 }, - { "MeshMatDescClass", 8192, 32 }, - { "TextureLoadTaskClass", 256, 32 }, - { "SortingNodeStruct", 288, 32 }, - { "ProxyArrayClass", 32, 32 }, - { "Line3DClass", 8, 8 }, - { "Render2DClass", 64, 32 }, - { "SurfaceClass", 128, 32 }, - { "FontCharsClassCharDataStruct", 1024, 32 }, - { "FontCharsBuffer", 16, 4 }, - { "FVFInfoClass", 152, 64 }, - { "TerrainTracksRenderObjClass", 128, 32 }, - { "DynamicIBAccessClass", 32, 32 }, - { "DX8IndexBufferClass", 128, 32 }, - { "SortingIndexBufferClass", 32, 32 }, - { "DX8VertexBufferClass", 128, 32 }, - { "SortingVertexBufferClass", 32, 32 }, - { "DynD3DMATERIAL8", 8192, 32 }, - { "DynamicMatrix3D", 512, 32 }, - { "MeshGeometryClass", 32, 32 }, - { "DynamicMeshModel", 32, 32 }, - { "GapFillerClass", 32, 32 }, - { "FontCharsClass", 64, 32 }, - { "ThumbnailManagerClass", 32, 32}, - { "SmudgeSet", 32, 32}, - { "Smudge", 128, 32}, - { 0, 0, 0 } -}; - -//----------------------------------------------------------------------------- -void userMemoryAdjustPoolSize(const char *poolName, Int& initialAllocationCount, Int& overflowAllocationCount) -{ - if (initialAllocationCount > 0) - return; - - for (const PoolSizeRec* p = sizes; p->name != NULL; ++p) - { - if (strcmp(p->name, poolName) == 0) - { - initialAllocationCount = p->initial; - overflowAllocationCount = p->overflow; - return; - } - } - - DEBUG_CRASH(("Initial size for pool %s not found -- you should add it to MemoryInit.cpp\n",poolName)); -} - -//----------------------------------------------------------------------------- -static Int roundUpMemBound(Int i) -{ - const int MEM_BOUND_ALIGNMENT = 4; - - if (i < MEM_BOUND_ALIGNMENT) - return MEM_BOUND_ALIGNMENT; - else - return (i + (MEM_BOUND_ALIGNMENT-1)) & ~(MEM_BOUND_ALIGNMENT-1); -} - -//----------------------------------------------------------------------------- -void userMemoryManagerInitPools() -{ - // note that we MUST use stdio stuff here, and not the normal game file system - // (with bigfile support, etc), because that relies on memory pools, which - // aren't yet initialized properly! so rely ONLY on straight stdio stuff here. - // (not even AsciiString. thanks.) - - // since we're called prior to main, the cur dir might not be what - // we expect. so do it the hard way. - char buf[_MAX_PATH]; - ::GetModuleFileName(NULL, buf, sizeof(buf)); - char* pEnd = buf + strlen(buf); - while (pEnd != buf) - { - if (*pEnd == '\\') - { - *pEnd = 0; - break; - } - --pEnd; - } - strcat(buf, "\\Data\\INI\\MemoryPools.ini"); - - FILE* fp = fopen(buf, "r"); - if (fp) - { - char poolName[256]; - int initial, overflow; - while (fgets(buf, _MAX_PATH, fp)) - { - if (buf[0] == ';') - continue; - if (sscanf(buf, "%s %d %d", poolName, &initial, &overflow ) == 3) - { - for (PoolSizeRec* p = sizes; p->name != NULL; ++p) - { - if (stricmp(p->name, poolName) == 0) - { - // currently, these must be multiples of 4. so round up. - p->initial = roundUpMemBound(initial); - p->overflow = roundUpMemBound(overflow); - break; // from for-p - } - } - } - } - fclose(fp); - } -} - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: MemoryInit.cpp +//----------------------------------------------------------------------------- +// +// Westwood Studios Pacific. +// +// Confidential Information +// Copyright (C) 2001 - All Rights Reserved +// +//----------------------------------------------------------------------------- +// +// Project: RTS3 +// +// File name: MemoryInit.cpp +// +// Created: Steven Johnson, August 2001 +// +// Desc: Memory manager +// +// ---------------------------------------------------------------------------- +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +// SYSTEM INCLUDES + +// USER INCLUDES +#include "Lib/BaseType.h" +#include "Common/GameMemory.h" + +#ifdef RTS_INTERNAL +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +//----------------------------------------------------------------------------- +void userMemoryManagerGetDmaParms(Int *numSubPools, const PoolInitRec **pParms) +{ + static const PoolInitRec defaultDMA[7] = + { + // name, allocsize, initialcount, overflowcount + { "dmaPool_16", 16, 130000, 10000 }, + { "dmaPool_32", 32, 250000, 10000 }, + { "dmaPool_64", 64, 100000, 10000 }, + { "dmaPool_128", 128, 80000, 10000 }, + { "dmaPool_256", 256, 20000, 5000 }, + { "dmaPool_512", 512, 16000, 5000 }, + { "dmaPool_1024", 1024, 6000, 1024} + }; + + *numSubPools = 7; + *pParms = defaultDMA; +} + +//----------------------------------------------------------------------------- +struct PoolSizeRec +{ + const char* name; + Int initial; + Int overflow; +}; + +//----------------------------------------------------------------------------- +// And please be careful of duplicates. They are not rejected. +// not const -- we might override from INI +static PoolSizeRec sizes[] = +{ + { "PartitionContactListNode", 2048, 512 }, + { "BattleshipUpdate", 32, 32 }, + { "FlyToDestAndDestroyUpdate", 32, 32 }, + { "MusicTrack", 32, 32 }, + { "PositionalSoundPool", 32, 32 }, + { "GameMessage", 2048, 32 }, + { "NameKeyBucketPool", 9000, 1024 }, + { "ObjectSellInfo", 16, 16 }, + { "ProductionPrerequisitePool", 1024, 32 }, + { "RadarObject", 512, 32 }, + { "ResourceGatheringManager", 16, 16 }, + { "SightingInfo", 8192, 2048 },// Looks big, but all 3000 objects used to have 4 just built in. + { "SpecialPowerTemplate", 84, 32 }, + { "StateMachinePool", 32, 32 }, + { "TeamPool", 128, 32 }, // if you increase this, increase player/team relation map pools + { "PlayerRelationMapPool", 128, 32 }, + { "TeamRelationMapPool", 128, 32 }, + { "TeamPrototypePool", 256, 32 }, + { "TerrainType", 256, 32 }, + { "ThingTemplatePool", 2120, 32 }, + { "TunnelTracker", 16, 16 }, + { "Upgrade", 16, 16 }, + { "UpgradeTemplate", 128, 16 }, + { "Anim2D", 32, 32 }, + { "CommandButton", 1024, 256 }, + { "CommandSet", 820, 16 }, + { "DisplayString", 32, 32 }, + { "WebBrowserURL", 16, 16 }, + { "Drawable", 4096, 32 }, + { "Image", 2048, 32 }, + { "ParticlePool", 1400, 1024 }, + { "ParticleSystemTemplatePool", 1100, 32 }, + { "ParticleSystemPool", 1024, 32 }, + { "TerrainRoadType", 100, 32, }, + { "WindowLayoutPool", 32, 32 }, + { "AnimatedParticleSysBoneClientUpdate", 16, 16 }, + { "SwayClientUpdate", 32, 32 }, + { "BeaconClientUpdate", 64, 32 }, + { "AIGroupPool", 64, 32 }, + { "AIDockMachinePool", 256, 32 }, + { "AIGuardMachinePool", 32, 32 }, + { "AIGuardRetaliateMachinePool", 32, 32 }, + { "AITNGuardMachinePool", 32, 32 }, + { "PathNodePool", 8192, 1024 }, + { "PathPool", 256, 16 }, + { "WorkOrder", 32, 32 }, + { "TeamInQueue", 32, 32 }, + { "AIPlayer", 12, 4 }, + { "AISkirmishPlayer", 8, 8 }, + { "AIStateMachine", 600, 32 }, + { "JetAIStateMachine", 64, 32 }, + { "HeliAIStateMachine", 64, 32 }, + { "VtolAIStateMachine", 64, 32 }, + { "AIAttackMoveStateMachine", 2048, 32 }, + { "AIAttackThenIdleStateMachine", 512, 32 }, + { "AttackStateMachine", 512, 32 }, + { "CrateTemplate", 32, 32 }, + { "ExperienceTrackerPool", 2048, 512 }, + { "FiringTrackerPool", 4096, 256 }, + { "ObjectRepulsorHelper", 1024, 256 }, + { "ObjectSMCHelperPool", 2048, 256 }, + { "ObjectWeaponStatusHelperPool", 4096, 256 }, + { "ObjectDefectionHelperPool", 2048, 256 }, + { "StatusDamageHelper", 1500, 256 }, + { "SubdualDamageHelper", 1500, 256 }, + { "ChronoDamageHelper", 1500, 256 }, + { "TempWeaponBonusHelper", 4096, 256 }, + { "Locomotor", 2048, 32 }, + { "LocomotorTemplate", 192, 32 }, + { "ObjectPool", 1500, 256 }, + { "SimpleObjectIteratorPool", 32, 32 }, + { "SimpleObjectIteratorClumpPool", 4096, 32 }, + { "PartitionDataPool", 2048, 512 }, + { "BuildEntry", 32, 32 }, + { "Weapon", 4096, 32 }, + { "WeaponTemplate", 360, 32 }, + { "AIUpdateInterface", 600, 32 }, + { "ActiveBody", 1024, 32 }, + { "ActiveShroudUpgrade", 32, 32 }, + { "AssistedTargetingUpdate", 32, 32 }, + { "AudioEventInfo", 4096, 64 }, + { "AudioRequest", 256, 8 }, + { "AutoHealBehavior", 1024, 256 }, + { "WeaponBonusUpdate", 16, 16 }, + { "GrantStealthBehavior", 4096, 32 }, + { "NeutronBlastBehavior", 4096, 32 }, + { "CountermeasuresBehavior", 256, 32 }, + { "BaseRegenerateUpdate", 128, 32 }, + { "BoneFXDamage", 64, 32 }, + { "BoneFXUpdate", 64, 32 }, + { "BridgeBehavior", 4, 4 }, + { "BridgeTowerBehavior", 32, 32 }, + { "BridgeScaffoldBehavior", 32, 32 }, + { "CaveContain", 16, 16 }, + { "HealContain", 32, 32 }, + { "CreateCrateDie", 256, 128 }, + { "CreateObjectDie", 1024, 32 }, + { "EjectPilotDie", 1024, 32 }, + { "CrushDie", 1024, 32 }, + { "DamDie", 8, 8 }, + { "DeliverPayloadStateMachine", 32, 32 }, + { "DeliverPayloadAIUpdate", 32, 32 }, + { "DeletionUpdate", 128, 32 }, + { "SmartBombTargetHomingUpdate", 8, 8 }, + { "DynamicAudioEventInfo", 16, 256 }, // Note: some levels have none, some have lots. Since all are allocated at level load time, we can set this low for the levels with none. + { "HackInternetStateMachine", 32, 32 }, + { "HackInternetAIUpdate", 32, 32 }, + { "MissileAIUpdate", 512, 32 }, + { "DumbProjectileBehavior", 64, 32 }, + { "FreeFallProjectileBehavior", 32, 32 }, + { "DestroyDie", 1024, 32 }, + { "UpgradeDie", 128, 32 }, + { "KeepObjectDie", 128, 32 }, + { "DozerAIUpdate", 32, 32 }, + { "DynamicGeometryInfoUpdate", 16, 16 }, + { "DynamicShroudClearingRangeUpdate", 128, 16 }, + { "FXListDie", 1024, 32 }, + { "FireSpreadUpdate", 2048, 128 }, + { "FirestormDynamicGeometryInfoUpdate", 16, 16 }, + { "FireWeaponCollide", 2048, 32 }, + { "FireWeaponUpdate", 32, 32 }, + { "FlammableUpdate", 512, 256 }, + { "FloatUpdate", 512, 128 }, + { "TensileFormationUpdate", 256, 32 }, + { "GarrisonContain", 256, 32 }, + { "HealCrateCollide", 32, 32 }, + { "HeightDieUpdate", 32, 32 }, + { "ScatterShotUpdate", 128, 64 }, + { "FireWeaponWhenDamagedBehavior", 32, 32 }, + { "FireWeaponWhenDeadBehavior", 128, 64 }, + { "DelayedUpgradeBehavior", 128, 64 }, + { "GenerateMinefieldBehavior", 32, 32 }, + { "HelicopterSlowDeathBehavior", 64, 32 }, + { "ParkingPlaceBehavior", 32, 32 }, + { "FlightDeckBehavior", 8, 8 }, +#ifdef ALLOW_SURRENDER + { "POWTruckAIUpdate", 32, 32, }, + { "POWTruckBehavior", 32, 32, }, + { "PrisonBehavior", 32, 32 }, + { "PrisonVisual", 32, 32 }, + { "PropagandaCenterBehavior", 16, 16 }, +#endif + { "PropagandaTowerBehavior", 16, 16 }, + { "BunkerBusterBehavior", 16, 16 }, + { "ObjectTracker", 128, 32 }, + { "OCLUpdate", 16, 16 }, + { "BodyParticleSystem", 196, 64 }, + { "HighlanderBody", 2048, 128 }, + { "UndeadBody", 32, 32 }, + { "HordeUpdate", 128, 32 }, + { "ImmortalBody", 128, 256 }, + { "InactiveBody", 2048, 32 }, + { "InstantDeathBehavior", 512, 32 }, + { "LaserUpdate", 32, 32 }, + { "PointDefenseLaserUpdate", 32, 32 }, + { "CleanupHazardUpdate", 32, 32 }, + { "AutoFindHealingUpdate", 256, 32 }, + { "CommandButtonHuntUpdate", 512, 8 }, + { "PilotFindVehicleUpdate", 256, 32 }, + { "DemoTrapUpdate", 32, 32 }, + { "ParticleUplinkCannonUpdate", 16, 16 }, + { "SpectreGunshipUpdate", 8, 8 }, + { "SpectreGunshipDeploymentUpdate", 8, 8 }, + { "BaikonurLaunchPower", 4, 4 }, + { "RadiusDecalUpdate", 16, 16 }, + { "RadiusDecalBehavior", 32, 32 }, + { "BattlePlanUpdate", 32, 32 }, + { "LifetimeUpdate", 32, 32 }, + { "LocomotorSetUpgrade", 512, 128 }, + { "LockWeaponCreate", 64, 128 }, + { "AutoDepositUpdate", 256, 32 }, + { "NeutronMissileUpdate", 512, 32 }, + { "MoneyCrateCollide", 48, 16 }, + { "NeutronMissileSlowDeathBehavior", 8, 8 }, + { "OpenContain", 128, 32 }, + { "OverchargeBehavior", 32, 32 }, + { "OverlordContain", 32, 32 }, + { "HelixContain", 32, 32 }, + { "ParachuteContain", 128, 32 }, + { "PhysicsBehavior", 600, 32 }, + { "PoisonedBehavior", 512, 64 }, + { "ProductionEntry", 32, 32 }, + { "ProductionUpdate", 256, 32 }, + { "ProjectileStreamUpdate", 32, 32 }, + { "ProneUpdate", 128, 32 }, + { "QueueProductionExitUpdate", 32, 32 }, + { "RadarUpdate", 16, 16 }, + { "RadarUpgrade", 16, 16 }, + { "AnimationSteeringUpdate", 1024, 32 }, + { "SupplyWarehouseCripplingBehavior", 16, 16 }, + { "CostModifierUpgrade", 32, 32 }, + { "ProductionTimeModifierUpgrade", 32, 32 }, + { "UnitProductionBonusUpgrade", 64, 32 }, + { "CashBountyPower", 32, 32 }, + { "CleanupAreaPower", 32, 32 }, + { "ObjectCreationUpgrade", 196, 32 }, + { "MinefieldBehavior", 256, 32 }, + { "JetSlowDeathBehavior", 64, 32 }, + { "BattleBusSlowDeathBehavior", 64, 32 }, + { "RebuildHoleBehavior", 64, 32 }, + { "RebuildHoleExposeDie", 64, 32 }, + { "RepairDockUpdate", 32, 32 }, +#ifdef ALLOW_SURRENDER + { "PrisonDockUpdate", 32, 32 }, +#endif + { "RailedTransportDockUpdate", 16, 16 }, + { "RailedTransportAIUpdate", 16, 16 }, + { "RailedTransportContain", 16, 16 }, + { "RailroadBehavior", 16, 16 }, + { "SalvageCrateCollide", 32, 32 }, + { "ShroudCrateCollide", 32, 32 }, + { "SlavedUpdate", 64, 32 }, + { "SlowDeathBehavior", 1400, 256 }, + { "SpyVisionUpdate", 16, 16 }, + { "DefaultProductionExitUpdate", 32, 32 }, + { "SpawnPointProductionExitUpdate", 32, 32 }, + { "SpawnBehavior", 32, 32 }, + { "SpecialPowerCompletionDie", 32, 32 }, + { "SpecialPowerCreate", 32, 32 }, + { "PreorderCreate", 32, 32 }, + { "SpecialAbility", 512, 32 }, + { "SpecialAbilityUpdate", 512, 32 }, + { "MissileLauncherBuildingUpdate", 32, 32 }, + { "SquishCollide", 512, 32 }, + { "StructureBody", 512, 64 }, + { "HiveStructureBody", 64, 32 }, //Stinger sites + { "StructureCollapseUpdate", 32, 32 }, + { "StructureToppleUpdate", 32, 32 }, + { "SupplyCenterCreate", 32, 32 }, + { "SupplyCenterDockUpdate", 32, 32 }, + { "SupplyCenterProductionExitUpdate", 32, 32 }, + { "SupplyTruckStateMachine", 256, 32 }, + { "SupplyTruckAIUpdate", 32, 32 }, + { "SupplyWarehouseCreate", 48, 16 }, + { "SupplyWarehouseDockUpdate", 48, 16 }, + { "EnemyNearUpdate", 1024, 32 }, + { "TechBuildingBehavior", 32, 32 }, + { "ToppleUpdate", 256, 128 }, + { "TransitionDamageFX", 384, 128 }, + { "TransportAIUpdate", 64, 32 }, + { "TransportContain", 128, 32 }, + { "RiderChangeContain", 128, 32 }, + { "InternetHackContain", 16, 16 }, + { "TunnelContain", 8, 8 }, + { "TunnelContainDie", 32, 32 }, + { "TunnelCreate", 32, 32 }, + { "TurretAI", 256, 32 }, + { "TurretStateMachine", 128, 32 }, + { "TurretSwapUpgrade", 512, 128 }, + { "UnitCrateCollide", 32, 32 }, + { "UnpauseSpecialPowerUpgrade", 32, 32 }, + { "VeterancyCrateCollide", 32, 32 }, + { "VeterancyGainCreate", 512, 128 }, + { "ConvertToCarBombCrateCollide", 256, 128 }, + { "ConvertToHijackedVehicleCrateCollide", 256, 128 }, + { "SabotageCommandCenterCrateCollide", 256, 128 }, + { "SabotageFakeBuildingCrateCollide", 256, 128 }, + { "SabotageInternetCenterCrateCollide", 256, 128 }, + { "SabotageMilitaryFactoryCrateCollide", 256, 128 }, + { "SabotagePowerPlantCrateCollide", 256, 128 }, + { "SabotageSuperweaponCrateCollide", 256, 128 }, + { "SabotageSupplyCenterCrateCollide", 256, 128 }, + { "SabotageSupplyDropzoneCrateCollide", 256, 128 }, + { "JetAIUpdate", 64, 32 }, + { "ChinookAIUpdate", 32, 32 }, + { "WanderAIUpdate", 32, 32 }, + { "TeleporterAIUpdate", 64, 32 }, + { "WaveGuideUpdate", 16, 16 }, + { "ArmorDamageScalarUpdate", 256, 32 }, + { "WeaponBonusUpgrade", 512, 128 }, + { "WeaponSetUpgrade", 512, 128 }, + { "ArmorUpgrade", 512, 128 }, + { "WorkerAIUpdate", 128, 128 }, + { "WorkerStateMachine", 128, 128 }, + { "ChinookAIStateMachine", 32, 32 }, + { "DeployStyleAIUpdate", 32, 32 }, + { "AssaultTransportAIUpdate", 64, 32 }, + { "StreamingArchiveFile", 8, 8 }, + + { "DozerActionStateMachine", 256, 32 }, + { "DozerPrimaryStateMachine", 256, 32 }, + { "W3DDisplayString", 1400, 128 }, + { "W3DDefaultDraw", 1024, 128 }, + { "W3DDebrisDraw", 128, 128 }, + { "W3DDependencyModelDraw", 64, 64 }, + { "W3DLaserDraw", 32, 32 }, + { "W3DModelDraw", 2048, 512 }, + { "W3DOverlordTankDraw", 64, 64 }, + { "W3DOverlordTruckDraw", 64, 64 }, + { "W3DOverlordAircraftDraw", 64, 64 }, + { "W3DPoliceCarDraw", 32, 32 }, + { "W3DProjectileStreamDraw", 32, 32 }, + { "W3DRopeDraw", 32, 32 }, + { "W3DScienceModelDraw", 32, 32 }, + { "W3DSupplyDraw", 40, 16 }, + { "W3DTankDraw", 256, 32 }, + { "W3DTreeDraw", 16, 16 }, + { "W3DPropDraw", 16, 16 }, + { "W3DTracerDraw", 64, 32 }, + { "W3DTruckDraw", 128, 32 }, + { "W3DTankTruckDraw", 32, 16 }, + { "W3DTreeTextureClass", 4, 4 }, + { "DefaultSpecialPower", 32, 32 }, + { "OCLSpecialPower", 96, 32 }, + { "FireWeaponPower", 32, 32 }, +#ifdef ALLOW_DEMORALIZE + { "DemoralizeSpecialPower", 16, 16, }, +#endif + { "CashHackSpecialPower", 32, 32 }, + { "CommandSetUpgrade", 32, 32 }, + { "PassengersFireUpgrade", 32, 32 }, + { "GrantUpgradeCreate", 256, 32 }, + { "GrantScienceUpgrade", 256, 32 }, + { "ReplaceObjectUpgrade", 32, 32 }, + { "ModelConditionUpgrade", 32, 32 }, + { "UpgradeSpecialPower", 64, 32 }, + { "SpyVisionSpecialPower", 256, 32 }, + { "StealthDetectorUpdate", 256, 32 }, + { "StealthUpdate", 512, 128 }, + { "StealthUpgrade", 256, 32 }, + { "StatusBitsUpgrade", 128, 128 }, + { "SubObjectsUpgrade", 128, 128 }, + { "ExperienceScalarUpgrade", 256, 128 }, + { "MaxHealthUpgrade", 128, 128 }, + { "WeaponBonusUpgrade", 128, 64 }, + { "StickyBombUpdate", 64, 32 }, + { "FireOCLAfterWeaponCooldownUpdate", 64, 32 }, + { "HijackerUpdate", 64, 32 }, + { "ChinaMinesUpgrade", 64, 32 }, + { "PowerPlantUpdate", 48, 16 }, + { "PowerPlantUpgrade", 48, 16 }, + { "DefectorSpecialPower", 16, 16 }, + { "CheckpointUpdate", 16, 16 }, + { "MobNexusContain", 128, 32 }, + { "MobMemberSlavedUpdate", 64, 32 }, + { "EMPUpdate", 64, 32 }, + { "LeafletDropBehavior", 64, 32 }, + { "Overridable", 32, 32 }, + + { "W3DGameWindow", 700, 256 }, + { "SuccessState", 32, 32 }, + { "FailureState", 32, 32 }, + { "ContinueState", 32, 32 }, + { "SleepState", 32, 32 }, + + { "AIDockWaitForClearanceState", 256, 32 }, + { "AIDockProcessDockState", 256, 32 }, + { "AIGuardInnerState", 32, 32 }, + { "AIGuardIdleState", 32, 32 }, + { "AIGuardOuterState", 32, 32 }, + { "AIGuardReturnState", 32, 32 }, + { "AIGuardPickUpCrateState", 32, 32 }, + { "AIGuardAttackAggressorState", 32, 32 }, + { "AIGuardRetaliateInnerState", 32, 32 }, + { "AIGuardRetaliateIdleState", 32, 32 }, + { "AIGuardRetaliateOuterState", 32, 32 }, + { "AIGuardRetaliateReturnState", 32, 32 }, + { "AIGuardRetaliatePickUpCrateState", 32, 32 }, + { "AIGuardRetaliateAttackAggressorState", 32, 32 }, + { "AITNGuardInnerState", 32, 32 }, + { "AITNGuardIdleState", 32, 32 }, + { "AITNGuardOuterState", 32, 32 }, + { "AITNGuardReturnState", 32, 32 }, + { "AITNGuardPickUpCrateState", 32, 32 }, + { "AITNGuardAttackAggressorState", 32, 32 }, + { "AIIdleState", 2400, 32 }, + { "AIRappelState", 600, 32 }, + { "AIBusyState", 600, 32 }, + { "AIWaitState", 600, 32 }, + { "AIAttackState", 4096, 32 }, + { "AIAttackSquadState", 600, 32 }, + { "AIDeadState", 600, 32 }, + { "AIDockState", 600, 32 }, + { "AIExitState", 600, 32 }, + { "AIExitInstantlyState", 600, 32 }, + { "AIGuardState", 600, 32 }, + { "AIGuardRetaliateState", 600, 32 }, + { "AITunnelNetworkGuardState", 600, 32 }, + { "AIHuntState", 600, 32 }, + { "AIAttackAreaState", 600, 32 }, + { "AIFaceState", 1200, 32 }, + { "ApproachState", 600, 32 }, + { "DeliveringState", 600, 32 }, + { "ConsiderNewApproachState", 600, 32 }, + { "RecoverFromOffMapState", 600, 32 }, + { "HeadOffMapState", 600, 32 }, + { "CleanUpState", 600, 32 }, + { "HackInternetState", 600, 32 }, + { "PackingState", 600, 32 }, + { "UnpackingState", 600, 32 }, + { "SupplyTruckWantsToPickUpOrDeliverBoxesState", 600, 32 }, + { "RegroupingState", 600, 32 }, + { "DockingState", 600, 32 }, + { "ChinookEvacuateState", 32, 32 }, + { "ChinookHeadOffMapState", 32, 32 }, + { "ChinookTakeoffOrLandingState", 32, 32 }, + { "ChinookCombatDropState", 32, 32 }, + { "DozerActionPickActionPosState", 256, 32 }, + { "DozerActionMoveToActionPosState", 256, 32 }, + { "DozerActionDoActionState", 256, 32 }, + { "DozerPrimaryIdleState", 256, 32 }, + { "DozerActionState", 256, 32 }, + { "DozerPrimaryGoingHomeState", 256, 32 }, + { "JetAwaitingRunwayState", 64, 32 }, + { "JetOrHeliCirclingDeadAirfieldState", 64, 32 }, + { "HeliTakeoffOrLandingState", 64, 32 }, + { "VtolTakeoffOrLandingState", 64, 32 }, + { "JetOrHeliParkOrientState", 64, 32 }, + { "VtolParkOrientState", 64, 32 }, + { "JetOrHeliReloadAmmoState", 64, 32 }, + { "SupplyTruckBusyState", 600, 32 }, + { "SupplyTruckIdleState", 600, 32 }, + { "ActAsDozerState", 600, 32 }, + { "ActAsSupplyTruckState", 600, 32 }, + { "AIDockApproachState", 256, 32 }, + { "AIDockAdvancePositionState", 256, 32 }, + { "AIDockMoveToEntryState", 256, 32 }, + { "AIDockMoveToDockState", 256, 32 }, + { "AIDockMoveToExitState", 256, 32 }, + { "AIDockMoveToRallyState", 256, 32 }, + { "AIMoveToState", 600, 32 }, + { "AIMoveOutOfTheWayState", 600, 32 }, + { "AIMoveAndTightenState", 600, 32 }, + { "AIMoveAwayFromRepulsorsState", 600, 32 }, + { "AIAttackApproachTargetState", 96, 32 }, + { "AIAttackPursueTargetState", 96, 32 }, + { "AIAttackAimAtTargetState", 96, 32 }, + { "AIAttackFireWeaponState", 256, 32 }, + { "AIPickUpCrateState", 4096, 32 }, + { "AIFollowWaypointPathState", 1200, 32 }, + { "AIFollowWaypointPathExactState", 1200, 32 }, + { "AIWanderInPlaceState", 600, 32 }, + { "AIFollowPathState", 1200, 32 }, + { "AIMoveAndEvacuateState", 1200, 32 }, + { "AIMoveAndDeleteState", 600, 32 }, + { "AIEnterState", 600, 32 }, + { "JetOrHeliReturningToDeadAirfieldState", 64, 32 }, + { "JetOrHeliReturnForLandingState", 64, 32 }, + { "TurretAIIdleState", 600, 32 }, + { "TurretAIIdleScanState", 600, 32 }, + { "TurretAIAimTurretState", 600, 32 }, + { "TurretAIRecenterTurretState", 600, 32 }, + { "TurretAIHoldTurretState", 600, 32 }, + { "JetOrHeliTaxiState", 64, 32 }, + { "JetTakeoffOrLandingState", 64, 32 }, + { "JetPauseBeforeTakeoffState", 64, 32 }, + { "AIAttackMoveToState", 600, 32 }, + { "AIAttackFollowWaypointPathState", 1200, 32 }, + { "AIWanderState", 600, 32 }, + { "AIPanicState", 600, 32 }, + { "ChinookMoveToBldgState", 32, 32 }, + { "ChinookRecordCreationState", 32, 32 }, + { "ScienceInfo", 96, 32 }, + { "RankInfo", 32, 32 }, + + { "FireWeaponNugget", 32, 32 }, + { "AttackNugget", 32, 32 }, + { "DeliverPayloadNugget", 48, 32 }, + { "ApplyRandomForceNugget", 32, 32 }, + { "GenericObjectCreationNugget", 632, 32 }, + { "SoundFXNugget", 320, 32 }, + { "TracerFXNugget", 32, 32 }, + { "RayEffectFXNugget", 32, 32 }, + { "LightPulseFXNugget", 68, 32 }, + { "ViewShakeFXNugget", 140, 32 }, + { "TerrainScorchFXNugget", 48, 32 }, + { "ParticleSystemFXNugget", 832, 32 }, + { "FXListAtBonePosFXNugget", 32, 32 }, + { "Squad", 256, 32 }, + { "BuildListInfo", 400, 64 }, + + { "ScriptGroup", 128, 32 }, + { "OrCondition", 1024, 256 }, + { "ScriptAction", 2600, 512 }, + { "Script", 1024, 256 }, + { "Parameter", 8192, 1024 }, + { "Condition", 2048, 256 }, + { "Template", 32, 32 }, + { "ScriptList", 32, 32 }, + { "AttackPriorityInfo", 32, 32 }, + { "SequentialScript", 32, 32 }, + { "Win32LocalFile", 1024, 256 }, + { "StdLocalFile", 1024, 256 }, + { "RAMFile", 32, 32 }, + { "BattlePlanBonuses", 32, 32 }, + { "KindOfPercentProductionChange", 32, 32 }, + { "UserParser", 4096, 256 }, + { "XferBlockData", 32, 32 }, + { "EvaCheckInfo", 52, 16 }, + { "SuperweaponInfo", 32, 32 }, + { "NamedTimerInfo", 32, 32 }, + { "PopupMessageData", 32, 32 }, + { "FloatingTextData", 32, 32 }, + { "MapObject", 5000, 1024 }, + { "Waypoint", 1024, 32 }, + { "PolygonTrigger", 64, 64 }, + { "Bridge", 32, 32 }, + { "Mapping", 384, 64 }, + { "OutputChunk", 32, 32 }, + { "InputChunk", 32, 32 }, + { "AnimateWindow", 32, 32 }, + { "GameFont", 32, 32 }, + { "NetCommandRef", 256, 32 }, + { "GameMessageArgument", 1024, 256 }, + { "GameMessageParserArgumentType", 32, 32 }, + { "GameMessageParser", 32, 32 }, + { "WeaponBonusSet", 96, 32 }, + { "Campaign", 32, 32 }, + { "Mission", 88, 32 }, + { "ModalWindow", 32, 32 }, + { "NetPacket", 32, 32 }, + { "AISideInfo", 32, 32 }, + { "AISideBuildList", 32, 32 }, + { "MetaMapRec", 256, 32 }, + { "TransportStatus", 32, 32 }, + { "Anim2DTemplate", 32, 32 }, + { "ObjectTypes", 32, 32 }, + { "NetCommandList", 512, 32 }, + { "TurretAIData", 256, 32 }, + { "NetCommandMsg", 32, 32 }, + { "NetGameCommandMsg", 64, 32 }, + { "NetAckBothCommandMsg", 32, 32 }, + { "NetAckStage1CommandMsg", 32, 32 }, + { "NetAckStage2CommandMsg", 32, 32 }, + { "NetFrameCommandMsg", 32, 32 }, + { "NetPlayerLeaveCommandMsg", 32, 32 }, + { "NetRunAheadMetricsCommandMsg", 32, 32 }, + { "NetRunAheadCommandMsg", 32, 32 }, + { "NetDestroyPlayerCommandMsg", 32, 32 }, + { "NetDisconnectFrameCommandMsg", 32, 32 }, + { "NetDisconnectScreenOffCommandMsg", 32, 32 }, + { "NetFrameResendRequestCommandMsg", 32, 32 }, + { "NetKeepAliveCommandMsg", 32, 32 }, + { "NetDisconnectKeepAliveCommandMsg", 32, 32 }, + { "NetDisconnectPlayerCommandMsg", 32, 32 }, + { "NetPacketRouterQueryCommandMsg", 32, 32 }, + { "NetPacketRouterAckCommandMsg", 32, 32 }, + { "NetDisconnectChatCommandMsg", 32, 32 }, + { "NetChatCommandMsg", 32, 32 }, + { "NetDisconnectVoteCommandMsg", 32, 32 }, + { "NetProgressCommandMsg", 32, 32 }, + { "NetWrapperCommandMsg", 32, 32 }, + { "NetFileCommandMsg", 32, 32 }, + { "NetFileAnnounceCommandMsg", 32, 32 }, + { "NetFileProgressCommandMsg", 32, 32 }, + { "NetCommandWrapperListNode", 32, 32 }, + { "NetCommandWrapperList", 32, 32 }, + { "Connection", 32, 32 }, + { "User", 32, 32 }, + { "FrameDataManager", 32, 32 }, + { "DrawableIconInfo", 32, 32 }, + { "TintEnvelope", 128, 32 }, + { "DynamicAudioEventRTS", 4000, 256 }, + { "DrawableLocoInfo", 128, 32 }, + { "W3DPrototypeClass", 512, 256 }, + { "EnumeratedIP", 32, 32 }, + { "WaterTransparencySetting", 4, 4 }, + { "WeatherSetting", 4, 4 }, + + // W3D pools! + { "BoxPrototypeClass", 128, 128 }, + { "SpherePrototypeClass", 32, 32 }, + { "SoundRenderObjPrototypeClass", 32, 32 }, + { "RingPrototypeClass", 32, 32 }, + { "PrimitivePrototypeClass", 8192, 32 }, + { "HModelPrototypeClass", 256, 32 }, + { "ParticleEmitterPrototypeClass", 32, 32 }, + { "NullPrototypeClass", 32, 32 }, + { "HLodPrototypeClass", 700, 128 }, + { "HLodDefClass", 700, 128 }, + { "DistLODPrototypeClass", 32, 32 }, + { "DazzlePrototypeClass", 32, 32 }, + { "CollectionPrototypeClass", 32, 32 }, + { "BoxPrototypeClass", 256, 32 }, + { "AggregatePrototypeClass", 32, 32 }, + { "OBBoxRenderObjClass", 512, 128 }, + { "AABoxRenderObjClass", 32, 32 }, + { "VertexMaterialClass", 6000, 2048 }, + { "TextureClass", 1200, 256 }, + { "CloudMapTerrainTextureClass", 4, 4 }, + { "ScorchTextureClass", 4, 4 }, + { "LightMapTerrainTextureClass", 4, 4 }, + { "AlphaEdgeTextureClass", 4, 4 }, + { "AlphaTerrainTextureClass", 4, 4 }, + { "TerrainTextureClass", 4, 4 }, + { "MeshClass", 14000, 2000 }, + { "HTreeClass", 2048, 512 }, + { "HLodClass", 2048, 512 }, + { "MeshModelClass", 8192, 32 }, + { "ShareBufferClass", 32768, 1024 }, + { "AABTreeClass", 300, 128 }, + { "MotionChannelClass", 16384, 32 }, + { "BitChannelClass", 84, 32 }, + { "TimeCodedMotionChannelClass", 116, 32 }, + { "AdaptiveDeltaMotionChannelClass", 32, 32 }, + { "TimeCodedBitChannelClass", 32, 32 }, + { "UVBufferClass", 8192, 32 }, + { "TexBufferClass", 384, 128 }, + { "MatBufferClass", 256, 128 }, + { "MatrixMapperClass", 32, 32 }, + { "ScaleTextureMapperClass", 32, 32 }, + { "LinearOffsetTextureMapperClass", 96, 32 }, + { "GridTextureMapperClass", 32, 32 }, + { "RotateTextureMapperClass", 32, 32 }, + { "SineLinearOffsetTextureMapperClass", 32, 32 }, + { "StepLinearOffsetTextureMapperClass", 32, 32 }, + { "ZigZagLinearOffsetTextureMapperClass", 32, 32 }, + { "ClassicEnvironmentMapperClass", 32, 32 }, + { "EnvironmentMapperClass", 256, 32 }, + { "EdgeMapperClass", 32, 32 }, + { "WSClassicEnvironmentMapperClass", 32, 32 }, + { "WSEnvironmentMapperClass", 32, 32 }, + { "GridClassicEnvironmentMapperClass", 32, 32 }, + { "GridEnvironmentMapperClass", 32, 32 }, + { "ScreenMapperClass", 32, 32 }, + { "RandomTextureMapperClass", 32, 32 }, + { "BumpEnvTextureMapperClass", 32, 32 }, + { "MeshLoadContextClass", 4, 4 }, + { "MaterialInfoClass", 8192, 32 }, + { "MeshMatDescClass", 8192, 32 }, + { "TextureLoadTaskClass", 256, 32 }, + { "SortingNodeStruct", 288, 32 }, + { "ProxyArrayClass", 32, 32 }, + { "Line3DClass", 8, 8 }, + { "Render2DClass", 64, 32 }, + { "SurfaceClass", 128, 32 }, + { "FontCharsClassCharDataStruct", 1024, 32 }, + { "FontCharsBuffer", 16, 4 }, + { "FVFInfoClass", 152, 64 }, + { "TerrainTracksRenderObjClass", 128, 32 }, + { "DynamicIBAccessClass", 32, 32 }, + { "DX8IndexBufferClass", 128, 32 }, + { "SortingIndexBufferClass", 32, 32 }, + { "DX8VertexBufferClass", 128, 32 }, + { "SortingVertexBufferClass", 32, 32 }, + { "DynD3DMATERIAL8", 8192, 32 }, + { "DynamicMatrix3D", 512, 32 }, + { "MeshGeometryClass", 32, 32 }, + { "DynamicMeshModel", 32, 32 }, + { "GapFillerClass", 32, 32 }, + { "FontCharsClass", 64, 32 }, + { "ThumbnailManagerClass", 32, 32}, + { "SmudgeSet", 32, 32}, + { "Smudge", 128, 32}, + { 0, 0, 0 } +}; + +//----------------------------------------------------------------------------- +void userMemoryAdjustPoolSize(const char *poolName, Int& initialAllocationCount, Int& overflowAllocationCount) +{ + if (initialAllocationCount > 0) + return; + + for (const PoolSizeRec* p = sizes; p->name != NULL; ++p) + { + if (strcmp(p->name, poolName) == 0) + { + initialAllocationCount = p->initial; + overflowAllocationCount = p->overflow; + return; + } + } + + DEBUG_CRASH(("Initial size for pool %s not found -- you should add it to MemoryInit.cpp\n",poolName)); +} + +//----------------------------------------------------------------------------- +static Int roundUpMemBound(Int i) +{ + const int MEM_BOUND_ALIGNMENT = 4; + + if (i < MEM_BOUND_ALIGNMENT) + return MEM_BOUND_ALIGNMENT; + else + return (i + (MEM_BOUND_ALIGNMENT-1)) & ~(MEM_BOUND_ALIGNMENT-1); +} + +//----------------------------------------------------------------------------- +void userMemoryManagerInitPools() +{ + // note that we MUST use stdio stuff here, and not the normal game file system + // (with bigfile support, etc), because that relies on memory pools, which + // aren't yet initialized properly! so rely ONLY on straight stdio stuff here. + // (not even AsciiString. thanks.) + + // since we're called prior to main, the cur dir might not be what + // we expect. so do it the hard way. + char buf[_MAX_PATH]; + ::GetModuleFileName(NULL, buf, sizeof(buf)); + char* pEnd = buf + strlen(buf); + while (pEnd != buf) + { + if (*pEnd == '\\') + { + *pEnd = 0; + break; + } + --pEnd; + } + strcat(buf, "\\Data\\INI\\MemoryPools.ini"); + + FILE* fp = fopen(buf, "r"); + if (fp) + { + char poolName[256]; + int initial, overflow; + while (fgets(buf, _MAX_PATH, fp)) + { + if (buf[0] == ';') + continue; + if (sscanf(buf, "%s %d %d", poolName, &initial, &overflow ) == 3) + { + for (PoolSizeRec* p = sizes; p->name != NULL; ++p) + { + if (stricmp(p->name, poolName) == 0) + { + // currently, these must be multiples of 4. so round up. + p->initial = roundUpMemBound(initial); + p->overflow = roundUpMemBound(overflow); + break; // from for-p + } + } + } + } + fclose(fp); + } +} + diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index c80dc09cab9..e7dfe842d35 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -1,749 +1,749 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: ModuleFactory.cpp //////////////////////////////////////////////////////////////////////// -// Author: Colin Day, September 2001 -// Desc: TheModuleFactory is where we actually instance modules for objects -// and drawbles. Those modules are things such as an UpdateModule -// or DamageModule or DrawModule etc. -// -// TheModuleFactory will contain a list of ModuleTemplates, when we -// request a new module, we will look for that template in our -// list and create it -// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -#include "Common/Module.h" -#include "Common/ModuleFactory.h" -#include "Common/NameKeyGenerator.h" - -// behavior includes -#include "GameLogic/Module/AutoHealBehavior.h" -#include "GameLogic/Module/GrantStealthBehavior.h" -#include "GameLogic/Module/NeutronBlastBehavior.h" -#include "GameLogic/Module/BehaviorModule.h" -#include "GameLogic/Module/BridgeBehavior.h" -#include "GameLogic/Module/BridgeScaffoldBehavior.h" -#include "GameLogic/Module/BridgeTowerBehavior.h" -#include "GameLogic/Module/CountermeasuresBehavior.h" -#include "GameLogic/Module/DumbProjectileBehavior.h" -#include "GameLogic/Module/FreeFallProjectileBehavior.h" -#include "GameLogic/Module/InstantDeathBehavior.h" -#include "GameLogic/Module/SlowDeathBehavior.h" -#include "GameLogic/Module/HelicopterSlowDeathUpdate.h" -#include "GameLogic/Module/NeutronMissileSlowDeathUpdate.h" -#include "GameLogic/Module/CaveContain.h" -#include "GameLogic/Module/OpenContain.h" -#include "GameLogic/Module/OverchargeBehavior.h" -#include "GameLogic/Module/HealContain.h" -#include "GameLogic/Module/GarrisonContain.h" -#include "GameLogic/Module/InternetHackContain.h" -#include "GameLogic/Module/RailedTransportContain.h" -#include "GameLogic/Module/RiderChangeContain.h" -#include "GameLogic/Module/TransportContain.h" -#include "GameLogic/Module/MobNexusContain.h" -#include "GameLogic/Module/TunnelContain.h" -#include "GameLogic/Module/OverlordContain.h" -#include "GameLogic/Module/HelixContain.h" -#include "GameLogic/Module/ParachuteContain.h" -#ifdef ALLOW_SURRENDER -#include "GameLogic/Module/POWTruckBehavior.h" -#include "GameLogic/Module/PrisonBehavior.h" -#include "GameLogic/Module/PropagandaCenterBehavior.h" -#endif -#include "GameLogic/Module/PropagandaTowerBehavior.h" -#include "GameLogic/Module/BunkerBusterBehavior.h" -#include "GameLogic/Module/FireWeaponWhenDamagedBehavior.h" -#include "GameLogic/Module/FireWeaponWhenDeadBehavior.h" -#include "GameLogic/Module/DelayedUpgradeBehavior.h" -#include "GameLogic/Module/GenerateMinefieldBehavior.h" -#include "GameLogic/Module/ParkingPlaceBehavior.h" -#include "GameLogic/Module/FlightDeckBehavior.h" -#include "GameLogic/Module/PoisonedBehavior.h" -#include "GameLogic/Module/RebuildHoleBehavior.h" -#include "GameLogic/Module/SupplyWarehouseCripplingBehavior.h" -#include "GameLogic/Module/TechBuildingBehavior.h" -#include "GameLogic/Module/MinefieldBehavior.h" -#include "GameLogic/Module/BattleBusSlowDeathBehavior.h" -#include "GameLogic/Module/JetSlowDeathBehavior.h" - -// die includes -#include "GameLogic/Module/CreateCrateDie.h" -#include "GameLogic/Module/CreateObjectDie.h" -#include "GameLogic/Module/CrushDie.h" -#include "GameLogic/Module/DamDie.h" -#include "GameLogic/Module/DestroyDie.h" -#include "GameLogic/Module/EjectPilotDie.h" -#include "GameLogic/Module/FXListDie.h" -#include "GameLogic/Module/RebuildHoleExposeDie.h" -#include "GameLogic/Module/SpecialPowerCompletionDie.h" -#include "GameLogic/Module/UpgradeDie.h" -#include "GameLogic/Module/KeepObjectDie.h" - -// logic update includes -#include "GameLogic/Module/AIUpdate.h" -#include "GameLogic/Module/AnimationSteeringUpdate.h" -#include "GameLogic/Module/AssistedTargetingUpdate.h" -#include "GameLogic/Module/BaseRegenerateUpdate.h" -#include "GameLogic/Module/BoneFXUpdate.h" -#include "GameLogic/Module/ChinookAIUpdate.h" -#include "GameLogic/Module/DefaultProductionExitUpdate.h" -#include "GameLogic/Module/DeletionUpdate.h" -#include "GameLogic/Module/SmartBombTargetHomingUpdate.h" -#include "GameLogic/Module/DeliverPayloadAIUpdate.h" -#include "GameLogic/Module/DozerAIUpdate.h" -#include "GameLogic/Module/DynamicGeometryInfoUpdate.h" -#include "GameLogic/Module/DynamicShroudClearingRangeUpdate.h" -#include "GameLogic/Module/EnemyNearUpdate.h" -#include "GameLogic/Module/FireSpreadUpdate.h" -#include "GameLogic/Module/FirestormDynamicGeometryInfoUpdate.h" -#include "GameLogic/Module/FireWeaponUpdate.h" -#include "GameLogic/Module/FlammableUpdate.h" -#include "GameLogic/Module/FloatUpdate.h" -#include "GameLogic/Module/TensileFormationUpdate.h" -#include "GameLogic/Module/HackInternetAIUpdate.h" -#include "GameLogic/Module/DeployStyleAIUpdate.h" -#include "GameLogic/Module/AssaultTransportAIUpdate.h" -#include "GameLogic/Module/HeightDieUpdate.h" -#include "GameLogic/Module/HordeUpdate.h" -#include "GameLogic/Module/ScatterShotUpdate.h" -#include "GameLogic/Module/JetAIUpdate.h" -#include "GameLogic/Module/LaserUpdate.h" -#include "GameLogic/Module/PointDefenseLaserUpdate.h" -#include "GameLogic/Module/CleanupHazardUpdate.h" -#include "GameLogic/Module/AutoFindHealingUpdate.h" -#include "GameLogic/Module/CommandButtonHuntUpdate.h" -#include "GameLogic/Module/PilotFindVehicleUpdate.h" -#include "GameLogic/Module/DemoTrapUpdate.h" -#include "GameLogic/Module/ParticleUplinkCannonUpdate.h" -#include "GameLogic/Module/SpectreGunshipUpdate.h" -#include "GameLogic/Module/SpectreGunshipDeploymentUpdate.h" -#include "GameLogic/Module/BaikonurLaunchPower.h" -#include "GameLogic/Module/BattlePlanUpdate.h" -#include "GameLogic/Module/LifetimeUpdate.h" -#include "GameLogic/Module/RadiusDecalUpdate.h" -#include "GameLogic/Module/RadiusDecalBehavior.h" -#include "GameLogic/Module/AutoDepositUpdate.h" -#include "GameLogic/Module/MissileAIUpdate.h" -#include "GameLogic/Module/NeutronMissileUpdate.h" -#include "GameLogic/Module/OCLUpdate.h" -#include "GameLogic/Module/PhysicsUpdate.h" -#ifdef ALLOW_SURRENDER -#include "GameLogic/Module/POWTruckAIUpdate.h" -#endif -#include "GameLogic/Module/ProductionUpdate.h" -#include "GameLogic/Module/ProjectileStreamUpdate.h" -#include "GameLogic/Module/ProneUpdate.h" -#include "GameLogic/Module/QueueProductionExitUpdate.h" -#include "GameLogic/Module/RadarUpdate.h" -#include "GameLogic/Module/RepairDockUpdate.h" -#ifdef ALLOW_SURRENDER -#include "GameLogic/Module/PrisonDockUpdate.h" -#endif -#include "GameLogic/Module/RailedTransportDockUpdate.h" -#include "GameLogic/Module/RailedTransportAIUpdate.h" -#include "GameLogic/Module/RailroadGuideAIUpdate.h" -#include "GameLogic/Module/SlavedUpdate.h" -#include "GameLogic/Module/MobMemberSlavedUpdate.h" -#include "GameLogic/Module/SpecialAbilityUpdate.h" -#include "GameLogic/Module/MissileLauncherBuildingUpdate.h" -#include "GameLogic/Module/StealthDetectorUpdate.h" -#include "GameLogic/Module/StealthUpdate.h" -#include "GameLogic/Module/SpawnPointProductionExitUpdate.h" -#include "GameLogic/Module/SpawnBehavior.h" -#include "GameLogic/Module/SpyVisionUpdate.h" -#include "GameLogic/Module/StickyBombUpdate.h" -#include "GameLogic/Module/FireOCLAfterWeaponCooldownUpdate.h" -#include "GameLogic/Module/HijackerUpdate.h" -#include "GameLogic/Module/StructureCollapseUpdate.h" -#include "GameLogic/Module/StructureToppleUpdate.h" -#include "GameLogic/Module/SupplyCenterDockUpdate.h" -#include "GameLogic/Module/SupplyCenterProductionExitUpdate.h" -#include "GameLogic/Module/SupplyTruckAIUpdate.h" -#include "GameLogic/Module/SupplyWarehouseDockUpdate.h" -#include "GameLogic/Module/ToppleUpdate.h" -#include "GameLogic/Module/TransportAIUpdate.h" -#include "GameLogic/Module/WanderAIUpdate.h" -#include "GameLogic/Module/TeleporterAIUpdate.h" -#include "GameLogic/Module/WaveGuideUpdate.h" -#include "GameLogic/Module/WeaponBonusUpdate.h" -#include "GameLogic/Module/ArmorDamageScalarUpdate.h" -#include "GameLogic/Module/WorkerAIUpdate.h" -#include "GameLogic/Module/PowerPlantUpdate.h" -#include "GameLogic/Module/CheckpointUpdate.h" -#include "GameLogic/Module/EMPUpdate.h" - -// upgrade includes -#include "GameLogic/Module/ActiveShroudUpgrade.h" -#include "GameLogic/Module/ArmorUpgrade.h" -#include "GameLogic/Module/CommandSetUpgrade.h" -#include "GameLogic/Module/GrantScienceUpgrade.h" -#include "GameLogic/Module/PassengersFireUpgrade.h" -#include "GameLogic/Module/LocomotorSetUpgrade.h" -#include "GameLogic/Module/ObjectCreationUpgrade.h" -#include "GameLogic/Module/RadarUpgrade.h" -#include "GameLogic/Module/PowerPlantUpgrade.h" -#include "GameLogic/Module/ReplaceObjectUpgrade.h" -#include "GameLogic/Module/ModelConditionUpgrade.h" -#include "GameLogic/Module/StatusBitsUpgrade.h" -#include "GameLogic/Module/SubObjectsUpgrade.h" -#include "GameLogic/Module/StealthUpgrade.h" -#include "GameLogic/Module/UnpauseSpecialPowerUpgrade.h" -#include "GameLogic/Module/WeaponBonusUpgrade.h" -#include "GameLogic/Module/WeaponSetUpgrade.h" -#include "GameLogic/Module/WeaponBonusUpgrade.h" -#include "GameLogic/Module/CostModifierUpgrade.h" -#include "GameLogic/Module/ProductionTimeModifierUpgrade.h" -#include "GameLogic/Module/UnitProductionBonusUpgrade.h" -#include "GameLogic/Module/ExperienceScalarUpgrade.h" -#include "GameLogic/Module/MaxHealthUpgrade.h" - -// create includes -#include "GameLogic/Module/LockWeaponCreate.h" -#include "GameLogic/Module/SupplyCenterCreate.h" -#include "GameLogic/Module/SupplyWarehouseCreate.h" -#include "GameLogic/Module/GrantUpgradeCreate.h" -#include "GameLogic/Module/PreorderCreate.h" -#include "GameLogic/Module/SpecialPowerCreate.h" -#include "GameLogic/Module/VeterancyGainCreate.h" - -// damage includes -#include "GameLogic/Module/BoneFXDamage.h" -#include "GameLogic/Module/TransitionDamageFX.h" - -// collide includes -#include "GameLogic/Module/FireWeaponCollide.h" -#include "GameLogic/Module/SquishCollide.h" - -#include "GameLogic/Module/ConvertToCarBombCrateCollide.h" -#include "GameLogic/Module/ConvertToHijackedVehicleCrateCollide.h" -#include "GameLogic/Module/HealCrateCollide.h" -#include "GameLogic/Module/MoneyCrateCollide.h" -#include "GameLogic/Module/SabotageCommandCenterCrateCollide.h" -#include "GameLogic/Module/SabotageFakeBuildingCrateCollide.h" -#include "GameLogic/Module/SabotageInternetCenterCrateCollide.h" -#include "GameLogic/Module/SabotageMilitaryFactoryCrateCollide.h" -#include "GameLogic/Module/SabotagePowerPlantCrateCollide.h" -#include "GameLogic/Module/SabotageSuperweaponCrateCollide.h" -#include "GameLogic/Module/SabotageSupplyCenterCrateCollide.h" -#include "GameLogic/Module/SabotageSupplyDropzoneCrateCollide.h" -#include "GameLogic/Module/SalvageCrateCollide.h" -#include "GameLogic/Module/ShroudCrateCollide.h" -#include "GameLogic/Module/UnitCrateCollide.h" -#include "GameLogic/Module/VeterancyCrateCollide.h" - -// body includes -#include "GameLogic/Module/InactiveBody.h" -#include "GameLogic/Module/ActiveBody.h" -#include "GameLogic/Module/HighlanderBody.h" -#include "GameLogic/Module/ImmortalBody.h" -#include "GameLogic/Module/StructureBody.h" -#include "GameLogic/Module/HiveStructureBody.h" -#include "GameLogic/Module/UndeadBody.h" - -// contain includes -// (none) - -// special power modules -#include "GameLogic/Module/CashHackSpecialPower.h" -#include "GameLogic/Module/DefectorSpecialPower.h" -#ifdef ALLOW_DEMORALIZE -#include "GameLogic/Module/DemoralizeSpecialPower.h" -#endif -#include "GameLogic/Module/OCLSpecialPower.h" -#include "GameLogic/Module/SpecialAbility.h" -#include "GameLogic/Module/SpyVisionSpecialPower.h" -#include "GameLogic/Module/UpgradeSpecialPower.h" -#include "GameLogic/Module/CashBountyPower.h" -#include "GameLogic/Module/CleanupAreaPower.h" -#include "GameLogic/Module/FireWeaponPower.h" - -// destroy includes -// (none) - -// client update includes -#include "GameClient/Module/AnimatedParticleSysBoneClientUpdate.h" -#include "GameClient/Module/SwayClientUpdate.h" -#include "GameClient/Module/BeaconClientUpdate.h" - -// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// -ModuleFactory *TheModuleFactory = NULL; ///< the module factory singleton - -// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// - -#ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -ModuleFactory::ModuleFactory( void ) -{ - m_moduleTemplateMap.clear(); - m_moduleDataList.clear(); - -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -ModuleFactory::~ModuleFactory( void ) -{ - m_moduleTemplateMap.clear(); - - for (ModuleDataList::iterator i = m_moduleDataList.begin(); i != m_moduleDataList.end(); ++i) - { - const ModuleData* data = *i; - delete data; - } - m_moduleDataList.clear(); - -} - -//------------------------------------------------------------------------------------------------- -/** Initialize the module factory. Any class that needs to be attached - * to objects or drawables as modules needs to add a template - * for that class here */ -//------------------------------------------------------------------------------------------------- -void ModuleFactory::init( void ) -{ - - // behavior modules - addModule( AutoHealBehavior ); - addModule( GrantStealthBehavior ); - addModule( NeutronBlastBehavior ); - addModule( BridgeBehavior ); - addModule( BridgeScaffoldBehavior ); - addModule( BridgeTowerBehavior ); - addModule( CountermeasuresBehavior ); - addModule( DumbProjectileBehavior ); - addModule( FreeFallProjectileBehavior ); - addModule( PhysicsBehavior ); - addModule( InstantDeathBehavior ); - addModule( SlowDeathBehavior ); - addModule( HelicopterSlowDeathBehavior ); - addModule( NeutronMissileSlowDeathBehavior ); - addModule( CaveContain ); - addModule( OpenContain ); - addModule( OverchargeBehavior ); - addModule( HealContain ); - addModule( GarrisonContain ); - addModule( InternetHackContain ); - addModule( TransportContain ); - addModule( RiderChangeContain ); - addModule( RailedTransportContain ); - addModule( MobNexusContain ); - addModule( TunnelContain ); - addModule( OverlordContain ); - addModule( HelixContain ); - addModule( ParachuteContain ); -#ifdef ALLOW_SURRENDER - addModule( POWTruckBehavior ); - addModule( PrisonBehavior ); - addModule( PropagandaCenterBehavior ); -#endif - addModule( PropagandaTowerBehavior ); - addModule( BunkerBusterBehavior ); - addModule( FireWeaponWhenDamagedBehavior ); - addModule( FireWeaponWhenDeadBehavior ); - addModule( DelayedUpgradeBehavior ); - addModule( GenerateMinefieldBehavior ); - addModule( ParkingPlaceBehavior ); - addModule( FlightDeckBehavior ); - addModule( PoisonedBehavior ); - addModule( RebuildHoleBehavior ); - addModule( SupplyWarehouseCripplingBehavior ); - addModule( TechBuildingBehavior ); - addModule( MinefieldBehavior ); - addModule( BattleBusSlowDeathBehavior ); - addModule( JetSlowDeathBehavior ); - addModule( RailroadBehavior ); - addModule( SpawnBehavior ); - - // die modules - addModule( DestroyDie ); - addModule( FXListDie ); - addModule( CrushDie ); - addModule( DamDie ); - addModule( CreateCrateDie ); - addModule( CreateObjectDie ); - addModule( EjectPilotDie ); - addModule( SpecialPowerCompletionDie ); - addModule( RebuildHoleExposeDie ); - addModule( UpgradeDie ); - addModule( KeepObjectDie ); - - // update modules - addModule( AssistedTargetingUpdate ); - addModule( AutoFindHealingUpdate ); - addModule( BaseRegenerateUpdate ); - addModule( StealthDetectorUpdate ); - addModule( StealthUpdate ); - addModule( DeletionUpdate ); - addModule( SmartBombTargetHomingUpdate ); - addModule( DynamicShroudClearingRangeUpdate ); - addModule( DeployStyleAIUpdate ); - addModule( AssaultTransportAIUpdate ); - addModule( HordeUpdate ); - addModule( ToppleUpdate ); - addModule( EnemyNearUpdate ); - addModule( LifetimeUpdate ); - addModule( RadiusDecalUpdate ); - addModule( RadiusDecalBehavior ); - addModule( EMPUpdate ); - addModule( LeafletDropBehavior ); - addModule( AutoDepositUpdate ); - addModule( WeaponBonusUpdate ); - addModule( ArmorDamageScalarUpdate ); - addModule( MissileAIUpdate ); - addModule( NeutronMissileUpdate ); - addModule( FireSpreadUpdate ); - addModule( FireWeaponUpdate ); - addModule( FlammableUpdate ); - addModule( FloatUpdate ); - addModule( TensileFormationUpdate ); - addModule( HeightDieUpdate ); - addModule( ScatterShotUpdate ); - addModule( ChinookAIUpdate ); - addModule( JetAIUpdate ); - addModule( AIUpdateInterface ); - addModule( SupplyTruckAIUpdate ); - addModule( DeliverPayloadAIUpdate ); - addModule( HackInternetAIUpdate ); - addModule( DynamicGeometryInfoUpdate ); - addModule( FirestormDynamicGeometryInfoUpdate ); - addModule( LaserUpdate ); - addModule( PointDefenseLaserUpdate ); - addModule( CleanupHazardUpdate ); - addModule( CommandButtonHuntUpdate ); - addModule( PilotFindVehicleUpdate ); - addModule( DemoTrapUpdate ); - addModule( ParticleUplinkCannonUpdate ); - addModule( SpectreGunshipUpdate ); - addModule( SpectreGunshipDeploymentUpdate ); - addModule( BaikonurLaunchPower ); - addModule( BattlePlanUpdate ); - addModule( ProjectileStreamUpdate ); - addModule( QueueProductionExitUpdate ); - addModule( RepairDockUpdate ); -#ifdef ALLOW_SURRENDER - addModule( PrisonDockUpdate ); -#endif - addModule( RailedTransportDockUpdate ); - addModule( DefaultProductionExitUpdate ); - addModule( SpawnPointProductionExitUpdate ); - addModule( SpyVisionUpdate ); - addModule( SlavedUpdate ); - addModule( MobMemberSlavedUpdate ); - addModule( OCLUpdate ); - addModule( SpecialAbilityUpdate ); - addModule( MissileLauncherBuildingUpdate ); - addModule( SupplyCenterProductionExitUpdate ); - addModule( SupplyCenterDockUpdate ); - addModule( SupplyWarehouseDockUpdate ); - addModule( DozerAIUpdate ); -#ifdef ALLOW_SURRENDER - addModule( POWTruckAIUpdate ); -#endif - addModule( RailedTransportAIUpdate ); - addModule( ProductionUpdate ); - addModule( ProneUpdate ); - addModule( StickyBombUpdate ); - addModule( FireOCLAfterWeaponCooldownUpdate ); - addModule( HijackerUpdate ); - addModule( StructureToppleUpdate ); - addModule( StructureCollapseUpdate ); - addModule( BoneFXUpdate ); - addModule( RadarUpdate ); - addModule( AnimationSteeringUpdate ); - addModule( TransportAIUpdate ); - addModule( WanderAIUpdate ); - addModule( TeleporterAIUpdate ); - addModule( WaveGuideUpdate ); - addModule( WorkerAIUpdate ); - addModule( PowerPlantUpdate ); - addModule( CheckpointUpdate ); - - // upgrade modules - addModule( CostModifierUpgrade ); - addModule( ProductionTimeModifierUpgrade ); - addModule( UnitProductionBonusUpgrade ); - addModule( ActiveShroudUpgrade ); - addModule( ArmorUpgrade ); - addModule( CommandSetUpgrade ); - addModule( GrantScienceUpgrade ); - addModule( PassengersFireUpgrade ); - addModule( StatusBitsUpgrade ); - addModule( SubObjectsUpgrade ); - addModule( StealthUpgrade ); - addModule( RadarUpgrade ); - addModule( PowerPlantUpgrade ); - addModule( LocomotorSetUpgrade ); - addModule( ObjectCreationUpgrade ); - addModule( ReplaceObjectUpgrade ); - addModule( ModelConditionUpgrade ); - addModule( UnpauseSpecialPowerUpgrade ); - addModule( WeaponBonusUpgrade ); - addModule( WeaponSetUpgrade ); - addModule( WeaponBonusUpgrade ); - addModule( ExperienceScalarUpgrade ); - addModule( MaxHealthUpgrade ); - - // create modules - addModule( LockWeaponCreate ); - addModule( PreorderCreate ); - addModule( SupplyCenterCreate ); - addModule( SupplyWarehouseCreate ); - addModule( SpecialPowerCreate ); - addModule( GrantUpgradeCreate ); - addModule( VeterancyGainCreate ); - - // damage modules - addModule( BoneFXDamage ); - addModule( TransitionDamageFX ); - - // collide modules - addModule( FireWeaponCollide ); - addModule( SquishCollide ); - - addModule( HealCrateCollide ); - addModule( MoneyCrateCollide ); - addModule( ShroudCrateCollide ); - addModule( UnitCrateCollide ); - addModule( VeterancyCrateCollide ); - addModule( ConvertToCarBombCrateCollide ); - addModule( ConvertToHijackedVehicleCrateCollide ); - addModule( SabotageCommandCenterCrateCollide ); - addModule( SabotageFakeBuildingCrateCollide ); - addModule( SabotageInternetCenterCrateCollide ); - addModule( SabotageMilitaryFactoryCrateCollide ); - addModule( SabotagePowerPlantCrateCollide ); - addModule( SabotageSuperweaponCrateCollide ); - addModule( SabotageSupplyCenterCrateCollide ); - addModule( SabotageSupplyDropzoneCrateCollide ); - addModule( SalvageCrateCollide ); - - // body modules - addModule( InactiveBody ); - addModule( ActiveBody ); - addModule( HighlanderBody ); - addModule( ImmortalBody ); - addModule( StructureBody ); - addModule( HiveStructureBody ); - addModule( UndeadBody ); - - // contain modules - // (none) - - // special power modules - addModule( CashHackSpecialPower ); - addModule( DefectorSpecialPower ); -#ifdef ALLOW_DEMORALIZE - addModule( DemoralizeSpecialPower ); -#endif - addModule( OCLSpecialPower ); - addModule( FireWeaponPower ); - addModule( SpecialAbility ); - addModule( SpyVisionSpecialPower ); - addModule( UpgradeSpecialPower ); - addModule( CashBountyPower ); - addModule( CleanupAreaPower ); - - // destroy modules - // (none) - - // client update modules - addModule( AnimatedParticleSysBoneClientUpdate ); - addModule( SwayClientUpdate ); - addModule( BeaconClientUpdate ); - -} // end init - -//------------------------------------------------------------------------------------------------- -Int ModuleFactory::findModuleInterfaceMask(const AsciiString& name, ModuleType type) -{ - if (name.isEmpty()) - return 0; - - const ModuleTemplate* moduleTemplate = findModuleTemplate(name, type); - if (moduleTemplate) - { - return moduleTemplate->m_whichInterfaces; - } - - return 0; -} - -//------------------------------------------------------------------------------------------------- -ModuleData* ModuleFactory::newModuleDataFromINI(INI* ini, const AsciiString& name, ModuleType type, - const AsciiString& moduleTag) -{ - if (name.isEmpty()) - return NULL; - - const ModuleTemplate* moduleTemplate = findModuleTemplate(name, type); - if (moduleTemplate) - { - ModuleData* md = (*moduleTemplate->m_createDataProc)(ini); - md->setModuleTagNameKey( NAMEKEY( moduleTag ) ); - m_moduleDataList.push_back(md); - return md; - } - - return NULL; -} - -// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------------- -/*static*/ NameKeyType ModuleFactory::makeDecoratedNameKey(const AsciiString& name, ModuleType type) -{ - char tmp[256]; - tmp[0] = '0' + (int)type; - strcpy(&tmp[1], name.str()); - return TheNameKeyGenerator->nameToKey(tmp); -} - -//------------------------------------------------------------------------------------------------- -const ModuleFactory::ModuleTemplate* ModuleFactory::findModuleTemplate(const AsciiString& name, ModuleType type) -{ - NameKeyType namekey = makeDecoratedNameKey(name, type); - - ModuleTemplateMap::const_iterator it = m_moduleTemplateMap.find(namekey); - if (it == m_moduleTemplateMap.end()) - { - DEBUG_CRASH(( "Module name '%s' not found\n", name.str() )); - return NULL; - } - else - { - return &(*it).second; - } -} - -//------------------------------------------------------------------------------------------------- -/** Allocate a new acton class istance given the name */ -//------------------------------------------------------------------------------------------------- -Module *ModuleFactory::newModule( Thing *thing, const AsciiString& name, const ModuleData* moduleData, ModuleType type ) -{ - // sanity - if( name.isEmpty() ) - { - DEBUG_CRASH(("attempting to create module with empty name\n")); - return NULL; - } - const ModuleTemplate* mt = findModuleTemplate(name, type); - if (mt) - { - Module* mod = (*mt->m_createProc)( thing, moduleData ); - -#ifdef DEBUG_CRASHING - if (type == MODULETYPE_BEHAVIOR) - { - BehaviorModule* bm = (BehaviorModule*)mod; - - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_BODY)) != 0) == (bm->getBody() != NULL), - ("getInterfaceMask bad for MODULE_BODY (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_COLLIDE)) != 0) == (bm->getCollide() != NULL), - ("getInterfaceMask bad for MODULE_COLLIDE (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_CONTAIN)) != 0) == (bm->getContain() != NULL), - ("getInterfaceMask bad for MODULE_CONTAIN (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_CREATE)) != 0) == (bm->getCreate() != NULL), - ("getInterfaceMask bad for MODULE_CREATE (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_DAMAGE)) != 0) == (bm->getDamage() != NULL), - ("getInterfaceMask bad for MODULE_DAMAGE (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_DESTROY)) != 0) == (bm->getDestroy() != NULL), - ("getInterfaceMask bad for MODULE_DESTROY (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_DIE)) != 0) == (bm->getDie() != NULL), - ("getInterfaceMask bad for MODULE_DIE (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_SPECIAL_POWER)) != 0) == (bm->getSpecialPower() != NULL), - ("getInterfaceMask bad for MODULE_SPECIAL_POWER (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_UPDATE)) != 0) == (bm->getUpdate() != NULL), - ("getInterfaceMask bad for MODULE_UPDATE (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_UPGRADE)) != 0) == (bm->getUpgrade() != NULL), - ("getInterfaceMask bad for MODULE_UPGRADE (%s)\n",name.str())); - } -#endif - - return mod; - } - - return NULL; - -} // end newModule - -//------------------------------------------------------------------------------------------------- -/** Add a module template to our list of templates */ -//------------------------------------------------------------------------------------------------- -void ModuleFactory::addModuleInternal( NewModuleProc proc, NewModuleDataProc dataproc, ModuleType type, const AsciiString& name, Int whichIntf ) -{ - NameKeyType namekey = makeDecoratedNameKey(name, type); - ModuleTemplate& mtm = m_moduleTemplateMap[namekey]; // this creates it if it does not exist already - mtm.m_createProc = proc; - mtm.m_createDataProc = dataproc; - mtm.m_whichInterfaces = whichIntf; -} - -//------------------------------------------------------------------------------------------------- -void ModuleFactory::crc( Xfer *xfer ) -{ - for (ModuleDataList::iterator mdIt = m_moduleDataList.begin(); mdIt != m_moduleDataList.end(); ++mdIt) - { - ((ModuleData *)(*mdIt))->crc(xfer); - } -} - -//------------------------------------------------------------------------------------------------- -void ModuleFactory::xfer( Xfer *xfer ) -{ - - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - for (ModuleDataList::iterator mdIt = m_moduleDataList.begin(); mdIt != m_moduleDataList.end(); ++mdIt) - { - ((ModuleData *)(*mdIt))->xfer(xfer); - } -} - -//------------------------------------------------------------------------------------------------- -void ModuleFactory::loadPostProcess( void ) -{ -} +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: ModuleFactory.cpp //////////////////////////////////////////////////////////////////////// +// Author: Colin Day, September 2001 +// Desc: TheModuleFactory is where we actually instance modules for objects +// and drawbles. Those modules are things such as an UpdateModule +// or DamageModule or DrawModule etc. +// +// TheModuleFactory will contain a list of ModuleTemplates, when we +// request a new module, we will look for that template in our +// list and create it +// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#include "Common/Module.h" +#include "Common/ModuleFactory.h" +#include "Common/NameKeyGenerator.h" + +// behavior includes +#include "GameLogic/Module/AutoHealBehavior.h" +#include "GameLogic/Module/GrantStealthBehavior.h" +#include "GameLogic/Module/NeutronBlastBehavior.h" +#include "GameLogic/Module/BehaviorModule.h" +#include "GameLogic/Module/BridgeBehavior.h" +#include "GameLogic/Module/BridgeScaffoldBehavior.h" +#include "GameLogic/Module/BridgeTowerBehavior.h" +#include "GameLogic/Module/CountermeasuresBehavior.h" +#include "GameLogic/Module/DumbProjectileBehavior.h" +#include "GameLogic/Module/FreeFallProjectileBehavior.h" +#include "GameLogic/Module/InstantDeathBehavior.h" +#include "GameLogic/Module/SlowDeathBehavior.h" +#include "GameLogic/Module/HelicopterSlowDeathUpdate.h" +#include "GameLogic/Module/NeutronMissileSlowDeathUpdate.h" +#include "GameLogic/Module/CaveContain.h" +#include "GameLogic/Module/OpenContain.h" +#include "GameLogic/Module/OverchargeBehavior.h" +#include "GameLogic/Module/HealContain.h" +#include "GameLogic/Module/GarrisonContain.h" +#include "GameLogic/Module/InternetHackContain.h" +#include "GameLogic/Module/RailedTransportContain.h" +#include "GameLogic/Module/RiderChangeContain.h" +#include "GameLogic/Module/TransportContain.h" +#include "GameLogic/Module/MobNexusContain.h" +#include "GameLogic/Module/TunnelContain.h" +#include "GameLogic/Module/OverlordContain.h" +#include "GameLogic/Module/HelixContain.h" +#include "GameLogic/Module/ParachuteContain.h" +#ifdef ALLOW_SURRENDER +#include "GameLogic/Module/POWTruckBehavior.h" +#include "GameLogic/Module/PrisonBehavior.h" +#include "GameLogic/Module/PropagandaCenterBehavior.h" +#endif +#include "GameLogic/Module/PropagandaTowerBehavior.h" +#include "GameLogic/Module/BunkerBusterBehavior.h" +#include "GameLogic/Module/FireWeaponWhenDamagedBehavior.h" +#include "GameLogic/Module/FireWeaponWhenDeadBehavior.h" +#include "GameLogic/Module/DelayedUpgradeBehavior.h" +#include "GameLogic/Module/GenerateMinefieldBehavior.h" +#include "GameLogic/Module/ParkingPlaceBehavior.h" +#include "GameLogic/Module/FlightDeckBehavior.h" +#include "GameLogic/Module/PoisonedBehavior.h" +#include "GameLogic/Module/RebuildHoleBehavior.h" +#include "GameLogic/Module/SupplyWarehouseCripplingBehavior.h" +#include "GameLogic/Module/TechBuildingBehavior.h" +#include "GameLogic/Module/MinefieldBehavior.h" +#include "GameLogic/Module/BattleBusSlowDeathBehavior.h" +#include "GameLogic/Module/JetSlowDeathBehavior.h" + +// die includes +#include "GameLogic/Module/CreateCrateDie.h" +#include "GameLogic/Module/CreateObjectDie.h" +#include "GameLogic/Module/CrushDie.h" +#include "GameLogic/Module/DamDie.h" +#include "GameLogic/Module/DestroyDie.h" +#include "GameLogic/Module/EjectPilotDie.h" +#include "GameLogic/Module/FXListDie.h" +#include "GameLogic/Module/RebuildHoleExposeDie.h" +#include "GameLogic/Module/SpecialPowerCompletionDie.h" +#include "GameLogic/Module/UpgradeDie.h" +#include "GameLogic/Module/KeepObjectDie.h" + +// logic update includes +#include "GameLogic/Module/AIUpdate.h" +#include "GameLogic/Module/AnimationSteeringUpdate.h" +#include "GameLogic/Module/AssistedTargetingUpdate.h" +#include "GameLogic/Module/BaseRegenerateUpdate.h" +#include "GameLogic/Module/BoneFXUpdate.h" +#include "GameLogic/Module/ChinookAIUpdate.h" +#include "GameLogic/Module/DefaultProductionExitUpdate.h" +#include "GameLogic/Module/DeletionUpdate.h" +#include "GameLogic/Module/SmartBombTargetHomingUpdate.h" +#include "GameLogic/Module/DeliverPayloadAIUpdate.h" +#include "GameLogic/Module/DozerAIUpdate.h" +#include "GameLogic/Module/DynamicGeometryInfoUpdate.h" +#include "GameLogic/Module/DynamicShroudClearingRangeUpdate.h" +#include "GameLogic/Module/EnemyNearUpdate.h" +#include "GameLogic/Module/FireSpreadUpdate.h" +#include "GameLogic/Module/FirestormDynamicGeometryInfoUpdate.h" +#include "GameLogic/Module/FireWeaponUpdate.h" +#include "GameLogic/Module/FlammableUpdate.h" +#include "GameLogic/Module/FloatUpdate.h" +#include "GameLogic/Module/TensileFormationUpdate.h" +#include "GameLogic/Module/HackInternetAIUpdate.h" +#include "GameLogic/Module/DeployStyleAIUpdate.h" +#include "GameLogic/Module/AssaultTransportAIUpdate.h" +#include "GameLogic/Module/HeightDieUpdate.h" +#include "GameLogic/Module/HordeUpdate.h" +#include "GameLogic/Module/ScatterShotUpdate.h" +#include "GameLogic/Module/JetAIUpdate.h" +#include "GameLogic/Module/LaserUpdate.h" +#include "GameLogic/Module/PointDefenseLaserUpdate.h" +#include "GameLogic/Module/CleanupHazardUpdate.h" +#include "GameLogic/Module/AutoFindHealingUpdate.h" +#include "GameLogic/Module/CommandButtonHuntUpdate.h" +#include "GameLogic/Module/PilotFindVehicleUpdate.h" +#include "GameLogic/Module/DemoTrapUpdate.h" +#include "GameLogic/Module/ParticleUplinkCannonUpdate.h" +#include "GameLogic/Module/SpectreGunshipUpdate.h" +#include "GameLogic/Module/SpectreGunshipDeploymentUpdate.h" +#include "GameLogic/Module/BaikonurLaunchPower.h" +#include "GameLogic/Module/BattlePlanUpdate.h" +#include "GameLogic/Module/LifetimeUpdate.h" +#include "GameLogic/Module/RadiusDecalUpdate.h" +#include "GameLogic/Module/RadiusDecalBehavior.h" +#include "GameLogic/Module/AutoDepositUpdate.h" +#include "GameLogic/Module/MissileAIUpdate.h" +#include "GameLogic/Module/NeutronMissileUpdate.h" +#include "GameLogic/Module/OCLUpdate.h" +#include "GameLogic/Module/PhysicsUpdate.h" +#ifdef ALLOW_SURRENDER +#include "GameLogic/Module/POWTruckAIUpdate.h" +#endif +#include "GameLogic/Module/ProductionUpdate.h" +#include "GameLogic/Module/ProjectileStreamUpdate.h" +#include "GameLogic/Module/ProneUpdate.h" +#include "GameLogic/Module/QueueProductionExitUpdate.h" +#include "GameLogic/Module/RadarUpdate.h" +#include "GameLogic/Module/RepairDockUpdate.h" +#ifdef ALLOW_SURRENDER +#include "GameLogic/Module/PrisonDockUpdate.h" +#endif +#include "GameLogic/Module/RailedTransportDockUpdate.h" +#include "GameLogic/Module/RailedTransportAIUpdate.h" +#include "GameLogic/Module/RailroadGuideAIUpdate.h" +#include "GameLogic/Module/SlavedUpdate.h" +#include "GameLogic/Module/MobMemberSlavedUpdate.h" +#include "GameLogic/Module/SpecialAbilityUpdate.h" +#include "GameLogic/Module/MissileLauncherBuildingUpdate.h" +#include "GameLogic/Module/StealthDetectorUpdate.h" +#include "GameLogic/Module/StealthUpdate.h" +#include "GameLogic/Module/SpawnPointProductionExitUpdate.h" +#include "GameLogic/Module/SpawnBehavior.h" +#include "GameLogic/Module/SpyVisionUpdate.h" +#include "GameLogic/Module/StickyBombUpdate.h" +#include "GameLogic/Module/FireOCLAfterWeaponCooldownUpdate.h" +#include "GameLogic/Module/HijackerUpdate.h" +#include "GameLogic/Module/StructureCollapseUpdate.h" +#include "GameLogic/Module/StructureToppleUpdate.h" +#include "GameLogic/Module/SupplyCenterDockUpdate.h" +#include "GameLogic/Module/SupplyCenterProductionExitUpdate.h" +#include "GameLogic/Module/SupplyTruckAIUpdate.h" +#include "GameLogic/Module/SupplyWarehouseDockUpdate.h" +#include "GameLogic/Module/ToppleUpdate.h" +#include "GameLogic/Module/TransportAIUpdate.h" +#include "GameLogic/Module/WanderAIUpdate.h" +#include "GameLogic/Module/TeleporterAIUpdate.h" +#include "GameLogic/Module/WaveGuideUpdate.h" +#include "GameLogic/Module/WeaponBonusUpdate.h" +#include "GameLogic/Module/ArmorDamageScalarUpdate.h" +#include "GameLogic/Module/WorkerAIUpdate.h" +#include "GameLogic/Module/PowerPlantUpdate.h" +#include "GameLogic/Module/CheckpointUpdate.h" +#include "GameLogic/Module/EMPUpdate.h" + +// upgrade includes +#include "GameLogic/Module/ActiveShroudUpgrade.h" +#include "GameLogic/Module/ArmorUpgrade.h" +#include "GameLogic/Module/CommandSetUpgrade.h" +#include "GameLogic/Module/GrantScienceUpgrade.h" +#include "GameLogic/Module/PassengersFireUpgrade.h" +#include "GameLogic/Module/LocomotorSetUpgrade.h" +#include "GameLogic/Module/ObjectCreationUpgrade.h" +#include "GameLogic/Module/RadarUpgrade.h" +#include "GameLogic/Module/PowerPlantUpgrade.h" +#include "GameLogic/Module/ReplaceObjectUpgrade.h" +#include "GameLogic/Module/ModelConditionUpgrade.h" +#include "GameLogic/Module/StatusBitsUpgrade.h" +#include "GameLogic/Module/SubObjectsUpgrade.h" +#include "GameLogic/Module/StealthUpgrade.h" +#include "GameLogic/Module/UnpauseSpecialPowerUpgrade.h" +#include "GameLogic/Module/WeaponBonusUpgrade.h" +#include "GameLogic/Module/WeaponSetUpgrade.h" +#include "GameLogic/Module/WeaponBonusUpgrade.h" +#include "GameLogic/Module/CostModifierUpgrade.h" +#include "GameLogic/Module/ProductionTimeModifierUpgrade.h" +#include "GameLogic/Module/UnitProductionBonusUpgrade.h" +#include "GameLogic/Module/ExperienceScalarUpgrade.h" +#include "GameLogic/Module/MaxHealthUpgrade.h" + +// create includes +#include "GameLogic/Module/LockWeaponCreate.h" +#include "GameLogic/Module/SupplyCenterCreate.h" +#include "GameLogic/Module/SupplyWarehouseCreate.h" +#include "GameLogic/Module/GrantUpgradeCreate.h" +#include "GameLogic/Module/PreorderCreate.h" +#include "GameLogic/Module/SpecialPowerCreate.h" +#include "GameLogic/Module/VeterancyGainCreate.h" + +// damage includes +#include "GameLogic/Module/BoneFXDamage.h" +#include "GameLogic/Module/TransitionDamageFX.h" + +// collide includes +#include "GameLogic/Module/FireWeaponCollide.h" +#include "GameLogic/Module/SquishCollide.h" + +#include "GameLogic/Module/ConvertToCarBombCrateCollide.h" +#include "GameLogic/Module/ConvertToHijackedVehicleCrateCollide.h" +#include "GameLogic/Module/HealCrateCollide.h" +#include "GameLogic/Module/MoneyCrateCollide.h" +#include "GameLogic/Module/SabotageCommandCenterCrateCollide.h" +#include "GameLogic/Module/SabotageFakeBuildingCrateCollide.h" +#include "GameLogic/Module/SabotageInternetCenterCrateCollide.h" +#include "GameLogic/Module/SabotageMilitaryFactoryCrateCollide.h" +#include "GameLogic/Module/SabotagePowerPlantCrateCollide.h" +#include "GameLogic/Module/SabotageSuperweaponCrateCollide.h" +#include "GameLogic/Module/SabotageSupplyCenterCrateCollide.h" +#include "GameLogic/Module/SabotageSupplyDropzoneCrateCollide.h" +#include "GameLogic/Module/SalvageCrateCollide.h" +#include "GameLogic/Module/ShroudCrateCollide.h" +#include "GameLogic/Module/UnitCrateCollide.h" +#include "GameLogic/Module/VeterancyCrateCollide.h" + +// body includes +#include "GameLogic/Module/InactiveBody.h" +#include "GameLogic/Module/ActiveBody.h" +#include "GameLogic/Module/HighlanderBody.h" +#include "GameLogic/Module/ImmortalBody.h" +#include "GameLogic/Module/StructureBody.h" +#include "GameLogic/Module/HiveStructureBody.h" +#include "GameLogic/Module/UndeadBody.h" + +// contain includes +// (none) + +// special power modules +#include "GameLogic/Module/CashHackSpecialPower.h" +#include "GameLogic/Module/DefectorSpecialPower.h" +#ifdef ALLOW_DEMORALIZE +#include "GameLogic/Module/DemoralizeSpecialPower.h" +#endif +#include "GameLogic/Module/OCLSpecialPower.h" +#include "GameLogic/Module/SpecialAbility.h" +#include "GameLogic/Module/SpyVisionSpecialPower.h" +#include "GameLogic/Module/UpgradeSpecialPower.h" +#include "GameLogic/Module/CashBountyPower.h" +#include "GameLogic/Module/CleanupAreaPower.h" +#include "GameLogic/Module/FireWeaponPower.h" + +// destroy includes +// (none) + +// client update includes +#include "GameClient/Module/AnimatedParticleSysBoneClientUpdate.h" +#include "GameClient/Module/SwayClientUpdate.h" +#include "GameClient/Module/BeaconClientUpdate.h" + +// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// +ModuleFactory *TheModuleFactory = NULL; ///< the module factory singleton + +// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// + +#ifdef RTS_INTERNAL +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ModuleFactory::ModuleFactory( void ) +{ + m_moduleTemplateMap.clear(); + m_moduleDataList.clear(); + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ModuleFactory::~ModuleFactory( void ) +{ + m_moduleTemplateMap.clear(); + + for (ModuleDataList::iterator i = m_moduleDataList.begin(); i != m_moduleDataList.end(); ++i) + { + const ModuleData* data = *i; + delete data; + } + m_moduleDataList.clear(); + +} + +//------------------------------------------------------------------------------------------------- +/** Initialize the module factory. Any class that needs to be attached + * to objects or drawables as modules needs to add a template + * for that class here */ +//------------------------------------------------------------------------------------------------- +void ModuleFactory::init( void ) +{ + + // behavior modules + addModule( AutoHealBehavior ); + addModule( GrantStealthBehavior ); + addModule( NeutronBlastBehavior ); + addModule( BridgeBehavior ); + addModule( BridgeScaffoldBehavior ); + addModule( BridgeTowerBehavior ); + addModule( CountermeasuresBehavior ); + addModule( DumbProjectileBehavior ); + addModule( FreeFallProjectileBehavior ); + addModule( PhysicsBehavior ); + addModule( InstantDeathBehavior ); + addModule( SlowDeathBehavior ); + addModule( HelicopterSlowDeathBehavior ); + addModule( NeutronMissileSlowDeathBehavior ); + addModule( CaveContain ); + addModule( OpenContain ); + addModule( OverchargeBehavior ); + addModule( HealContain ); + addModule( GarrisonContain ); + addModule( InternetHackContain ); + addModule( TransportContain ); + addModule( RiderChangeContain ); + addModule( RailedTransportContain ); + addModule( MobNexusContain ); + addModule( TunnelContain ); + addModule( OverlordContain ); + addModule( HelixContain ); + addModule( ParachuteContain ); +#ifdef ALLOW_SURRENDER + addModule( POWTruckBehavior ); + addModule( PrisonBehavior ); + addModule( PropagandaCenterBehavior ); +#endif + addModule( PropagandaTowerBehavior ); + addModule( BunkerBusterBehavior ); + addModule( FireWeaponWhenDamagedBehavior ); + addModule( FireWeaponWhenDeadBehavior ); + addModule( DelayedUpgradeBehavior ); + addModule( GenerateMinefieldBehavior ); + addModule( ParkingPlaceBehavior ); + addModule( FlightDeckBehavior ); + addModule( PoisonedBehavior ); + addModule( RebuildHoleBehavior ); + addModule( SupplyWarehouseCripplingBehavior ); + addModule( TechBuildingBehavior ); + addModule( MinefieldBehavior ); + addModule( BattleBusSlowDeathBehavior ); + addModule( JetSlowDeathBehavior ); + addModule( RailroadBehavior ); + addModule( SpawnBehavior ); + + // die modules + addModule( DestroyDie ); + addModule( FXListDie ); + addModule( CrushDie ); + addModule( DamDie ); + addModule( CreateCrateDie ); + addModule( CreateObjectDie ); + addModule( EjectPilotDie ); + addModule( SpecialPowerCompletionDie ); + addModule( RebuildHoleExposeDie ); + addModule( UpgradeDie ); + addModule( KeepObjectDie ); + + // update modules + addModule( AssistedTargetingUpdate ); + addModule( AutoFindHealingUpdate ); + addModule( BaseRegenerateUpdate ); + addModule( StealthDetectorUpdate ); + addModule( StealthUpdate ); + addModule( DeletionUpdate ); + addModule( SmartBombTargetHomingUpdate ); + addModule( DynamicShroudClearingRangeUpdate ); + addModule( DeployStyleAIUpdate ); + addModule( AssaultTransportAIUpdate ); + addModule( HordeUpdate ); + addModule( ToppleUpdate ); + addModule( EnemyNearUpdate ); + addModule( LifetimeUpdate ); + addModule( RadiusDecalUpdate ); + addModule( RadiusDecalBehavior ); + addModule( EMPUpdate ); + addModule( LeafletDropBehavior ); + addModule( AutoDepositUpdate ); + addModule( WeaponBonusUpdate ); + addModule( ArmorDamageScalarUpdate ); + addModule( MissileAIUpdate ); + addModule( NeutronMissileUpdate ); + addModule( FireSpreadUpdate ); + addModule( FireWeaponUpdate ); + addModule( FlammableUpdate ); + addModule( FloatUpdate ); + addModule( TensileFormationUpdate ); + addModule( HeightDieUpdate ); + addModule( ScatterShotUpdate ); + addModule( ChinookAIUpdate ); + addModule( JetAIUpdate ); + addModule( AIUpdateInterface ); + addModule( SupplyTruckAIUpdate ); + addModule( DeliverPayloadAIUpdate ); + addModule( HackInternetAIUpdate ); + addModule( DynamicGeometryInfoUpdate ); + addModule( FirestormDynamicGeometryInfoUpdate ); + addModule( LaserUpdate ); + addModule( PointDefenseLaserUpdate ); + addModule( CleanupHazardUpdate ); + addModule( CommandButtonHuntUpdate ); + addModule( PilotFindVehicleUpdate ); + addModule( DemoTrapUpdate ); + addModule( ParticleUplinkCannonUpdate ); + addModule( SpectreGunshipUpdate ); + addModule( SpectreGunshipDeploymentUpdate ); + addModule( BaikonurLaunchPower ); + addModule( BattlePlanUpdate ); + addModule( ProjectileStreamUpdate ); + addModule( QueueProductionExitUpdate ); + addModule( RepairDockUpdate ); +#ifdef ALLOW_SURRENDER + addModule( PrisonDockUpdate ); +#endif + addModule( RailedTransportDockUpdate ); + addModule( DefaultProductionExitUpdate ); + addModule( SpawnPointProductionExitUpdate ); + addModule( SpyVisionUpdate ); + addModule( SlavedUpdate ); + addModule( MobMemberSlavedUpdate ); + addModule( OCLUpdate ); + addModule( SpecialAbilityUpdate ); + addModule( MissileLauncherBuildingUpdate ); + addModule( SupplyCenterProductionExitUpdate ); + addModule( SupplyCenterDockUpdate ); + addModule( SupplyWarehouseDockUpdate ); + addModule( DozerAIUpdate ); +#ifdef ALLOW_SURRENDER + addModule( POWTruckAIUpdate ); +#endif + addModule( RailedTransportAIUpdate ); + addModule( ProductionUpdate ); + addModule( ProneUpdate ); + addModule( StickyBombUpdate ); + addModule( FireOCLAfterWeaponCooldownUpdate ); + addModule( HijackerUpdate ); + addModule( StructureToppleUpdate ); + addModule( StructureCollapseUpdate ); + addModule( BoneFXUpdate ); + addModule( RadarUpdate ); + addModule( AnimationSteeringUpdate ); + addModule( TransportAIUpdate ); + addModule( WanderAIUpdate ); + addModule( TeleporterAIUpdate ); + addModule( WaveGuideUpdate ); + addModule( WorkerAIUpdate ); + addModule( PowerPlantUpdate ); + addModule( CheckpointUpdate ); + + // upgrade modules + addModule( CostModifierUpgrade ); + addModule( ProductionTimeModifierUpgrade ); + addModule( UnitProductionBonusUpgrade ); + addModule( ActiveShroudUpgrade ); + addModule( ArmorUpgrade ); + addModule( CommandSetUpgrade ); + addModule( GrantScienceUpgrade ); + addModule( PassengersFireUpgrade ); + addModule( StatusBitsUpgrade ); + addModule( SubObjectsUpgrade ); + addModule( StealthUpgrade ); + addModule( RadarUpgrade ); + addModule( PowerPlantUpgrade ); + addModule( LocomotorSetUpgrade ); + addModule( ObjectCreationUpgrade ); + addModule( ReplaceObjectUpgrade ); + addModule( ModelConditionUpgrade ); + addModule( UnpauseSpecialPowerUpgrade ); + addModule( WeaponBonusUpgrade ); + addModule( WeaponSetUpgrade ); + addModule( WeaponBonusUpgrade ); + addModule( ExperienceScalarUpgrade ); + addModule( MaxHealthUpgrade ); + + // create modules + addModule( LockWeaponCreate ); + addModule( PreorderCreate ); + addModule( SupplyCenterCreate ); + addModule( SupplyWarehouseCreate ); + addModule( SpecialPowerCreate ); + addModule( GrantUpgradeCreate ); + addModule( VeterancyGainCreate ); + + // damage modules + addModule( BoneFXDamage ); + addModule( TransitionDamageFX ); + + // collide modules + addModule( FireWeaponCollide ); + addModule( SquishCollide ); + + addModule( HealCrateCollide ); + addModule( MoneyCrateCollide ); + addModule( ShroudCrateCollide ); + addModule( UnitCrateCollide ); + addModule( VeterancyCrateCollide ); + addModule( ConvertToCarBombCrateCollide ); + addModule( ConvertToHijackedVehicleCrateCollide ); + addModule( SabotageCommandCenterCrateCollide ); + addModule( SabotageFakeBuildingCrateCollide ); + addModule( SabotageInternetCenterCrateCollide ); + addModule( SabotageMilitaryFactoryCrateCollide ); + addModule( SabotagePowerPlantCrateCollide ); + addModule( SabotageSuperweaponCrateCollide ); + addModule( SabotageSupplyCenterCrateCollide ); + addModule( SabotageSupplyDropzoneCrateCollide ); + addModule( SalvageCrateCollide ); + + // body modules + addModule( InactiveBody ); + addModule( ActiveBody ); + addModule( HighlanderBody ); + addModule( ImmortalBody ); + addModule( StructureBody ); + addModule( HiveStructureBody ); + addModule( UndeadBody ); + + // contain modules + // (none) + + // special power modules + addModule( CashHackSpecialPower ); + addModule( DefectorSpecialPower ); +#ifdef ALLOW_DEMORALIZE + addModule( DemoralizeSpecialPower ); +#endif + addModule( OCLSpecialPower ); + addModule( FireWeaponPower ); + addModule( SpecialAbility ); + addModule( SpyVisionSpecialPower ); + addModule( UpgradeSpecialPower ); + addModule( CashBountyPower ); + addModule( CleanupAreaPower ); + + // destroy modules + // (none) + + // client update modules + addModule( AnimatedParticleSysBoneClientUpdate ); + addModule( SwayClientUpdate ); + addModule( BeaconClientUpdate ); + +} // end init + +//------------------------------------------------------------------------------------------------- +Int ModuleFactory::findModuleInterfaceMask(const AsciiString& name, ModuleType type) +{ + if (name.isEmpty()) + return 0; + + const ModuleTemplate* moduleTemplate = findModuleTemplate(name, type); + if (moduleTemplate) + { + return moduleTemplate->m_whichInterfaces; + } + + return 0; +} + +//------------------------------------------------------------------------------------------------- +ModuleData* ModuleFactory::newModuleDataFromINI(INI* ini, const AsciiString& name, ModuleType type, + const AsciiString& moduleTag) +{ + if (name.isEmpty()) + return NULL; + + const ModuleTemplate* moduleTemplate = findModuleTemplate(name, type); + if (moduleTemplate) + { + ModuleData* md = (*moduleTemplate->m_createDataProc)(ini); + md->setModuleTagNameKey( NAMEKEY( moduleTag ) ); + m_moduleDataList.push_back(md); + return md; + } + + return NULL; +} + +// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +/*static*/ NameKeyType ModuleFactory::makeDecoratedNameKey(const AsciiString& name, ModuleType type) +{ + char tmp[256]; + tmp[0] = '0' + (int)type; + strcpy(&tmp[1], name.str()); + return TheNameKeyGenerator->nameToKey(tmp); +} + +//------------------------------------------------------------------------------------------------- +const ModuleFactory::ModuleTemplate* ModuleFactory::findModuleTemplate(const AsciiString& name, ModuleType type) +{ + NameKeyType namekey = makeDecoratedNameKey(name, type); + + ModuleTemplateMap::const_iterator it = m_moduleTemplateMap.find(namekey); + if (it == m_moduleTemplateMap.end()) + { + DEBUG_CRASH(( "Module name '%s' not found\n", name.str() )); + return NULL; + } + else + { + return &(*it).second; + } +} + +//------------------------------------------------------------------------------------------------- +/** Allocate a new acton class istance given the name */ +//------------------------------------------------------------------------------------------------- +Module *ModuleFactory::newModule( Thing *thing, const AsciiString& name, const ModuleData* moduleData, ModuleType type ) +{ + // sanity + if( name.isEmpty() ) + { + DEBUG_CRASH(("attempting to create module with empty name\n")); + return NULL; + } + const ModuleTemplate* mt = findModuleTemplate(name, type); + if (mt) + { + Module* mod = (*mt->m_createProc)( thing, moduleData ); + +#ifdef DEBUG_CRASHING + if (type == MODULETYPE_BEHAVIOR) + { + BehaviorModule* bm = (BehaviorModule*)mod; + + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_BODY)) != 0) == (bm->getBody() != NULL), + ("getInterfaceMask bad for MODULE_BODY (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_COLLIDE)) != 0) == (bm->getCollide() != NULL), + ("getInterfaceMask bad for MODULE_COLLIDE (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_CONTAIN)) != 0) == (bm->getContain() != NULL), + ("getInterfaceMask bad for MODULE_CONTAIN (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_CREATE)) != 0) == (bm->getCreate() != NULL), + ("getInterfaceMask bad for MODULE_CREATE (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_DAMAGE)) != 0) == (bm->getDamage() != NULL), + ("getInterfaceMask bad for MODULE_DAMAGE (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_DESTROY)) != 0) == (bm->getDestroy() != NULL), + ("getInterfaceMask bad for MODULE_DESTROY (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_DIE)) != 0) == (bm->getDie() != NULL), + ("getInterfaceMask bad for MODULE_DIE (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_SPECIAL_POWER)) != 0) == (bm->getSpecialPower() != NULL), + ("getInterfaceMask bad for MODULE_SPECIAL_POWER (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_UPDATE)) != 0) == (bm->getUpdate() != NULL), + ("getInterfaceMask bad for MODULE_UPDATE (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_UPGRADE)) != 0) == (bm->getUpgrade() != NULL), + ("getInterfaceMask bad for MODULE_UPGRADE (%s)\n",name.str())); + } +#endif + + return mod; + } + + return NULL; + +} // end newModule + +//------------------------------------------------------------------------------------------------- +/** Add a module template to our list of templates */ +//------------------------------------------------------------------------------------------------- +void ModuleFactory::addModuleInternal( NewModuleProc proc, NewModuleDataProc dataproc, ModuleType type, const AsciiString& name, Int whichIntf ) +{ + NameKeyType namekey = makeDecoratedNameKey(name, type); + ModuleTemplate& mtm = m_moduleTemplateMap[namekey]; // this creates it if it does not exist already + mtm.m_createProc = proc; + mtm.m_createDataProc = dataproc; + mtm.m_whichInterfaces = whichIntf; +} + +//------------------------------------------------------------------------------------------------- +void ModuleFactory::crc( Xfer *xfer ) +{ + for (ModuleDataList::iterator mdIt = m_moduleDataList.begin(); mdIt != m_moduleDataList.end(); ++mdIt) + { + ((ModuleData *)(*mdIt))->crc(xfer); + } +} + +//------------------------------------------------------------------------------------------------- +void ModuleFactory::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + for (ModuleDataList::iterator mdIt = m_moduleDataList.begin(); mdIt != m_moduleDataList.end(); ++mdIt) + { + ((ModuleData *)(*mdIt))->xfer(xfer); + } +} + +//------------------------------------------------------------------------------------------------- +void ModuleFactory::loadPostProcess( void ) +{ +} diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Armor.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Armor.cpp index 3b66ecaafcd..cd6736ed53b 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Armor.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Armor.cpp @@ -1,190 +1,192 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: ArmorTemplate.cpp /////////////////////////////////////////////////////////////////////////////// -// Author: Steven Johnson, November 2001 -// Desc: ArmorTemplate descriptions -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - - -#include "Common/INI.h" -#include "Common/ThingFactory.h" -#include "GameLogic/Armor.h" -#include "GameLogic/Damage.h" - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -ArmorStore* TheArmorStore = NULL; ///< the ArmorTemplate store definition - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------------- -ArmorTemplate::ArmorTemplate() -{ - clear(); -} - -//------------------------------------------------------------------------------------------------- -void ArmorTemplate::clear() -{ - for (int i = 0; i < DAMAGE_NUM_TYPES; i++) - { - m_damageCoefficient[i] = 1.0f; - } -} - -void ArmorTemplate::copyFrom(const ArmorTemplate* other) { - for (int i = 0; i < DAMAGE_NUM_TYPES; i++) - { - m_damageCoefficient[i] = other->m_damageCoefficient[i]; - } -} - -//------------------------------------------------------------------------------------------------- -Real ArmorTemplate::adjustDamage(DamageType t, Real damage) const -{ - if (t == DAMAGE_UNRESISTABLE) - return damage; - if (t == DAMAGE_SUBDUAL_UNRESISTABLE) - return damage; - - damage *= m_damageCoefficient[t]; - - if (damage < 0.0f) - damage = 0.0f; - - return damage; -} - -//-------------------------------------------------------------------------------------------Static -/*static*/ void ArmorTemplate::parseArmorCoefficients( INI* ini, void *instance, void* /* store */, const void* userData ) -{ - ArmorTemplate* self = (ArmorTemplate*) instance; - - const char* damageName = ini->getNextToken(); - Real pct = INI::scanPercentToReal(ini->getNextToken()); - - if (stricmp(damageName, "Default") == 0) - { - for (Int i = 0; i < DAMAGE_NUM_TYPES; i++) - { - self->m_damageCoefficient[i] = pct; - } - return; - } - - DamageType dt = (DamageType)DamageTypeFlags::getSingleBitFromName(damageName); - self->m_damageCoefficient[dt] = pct; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -ArmorStore::ArmorStore() -{ - m_armorTemplates.clear(); -} - -//------------------------------------------------------------------------------------------------- -ArmorStore::~ArmorStore() -{ - m_armorTemplates.clear(); -} - -//------------------------------------------------------------------------------------------------- -const ArmorTemplate* ArmorStore::findArmorTemplate(AsciiString name) const -{ - NameKeyType namekey = TheNameKeyGenerator->nameToKey(name); - ArmorTemplateMap::const_iterator it = m_armorTemplates.find(namekey); - if (it == m_armorTemplates.end()) - { - return NULL; - } - else - { - return &(*it).second; - } -} - -//------------------------------------------------------------------------------------------------- -/*static */ void ArmorStore::parseArmorDefinition(INI *ini) -{ - static const FieldParse myFieldParse[] = - { - { "Armor", ArmorTemplate::parseArmorCoefficients, NULL, 0 } - }; - - const char *c = ini->getNextToken(); - NameKeyType key = TheNameKeyGenerator->nameToKey(c); - ArmorTemplate& armorTmpl = TheArmorStore->m_armorTemplates[key]; - armorTmpl.clear(); - ini->initFromINI(&armorTmpl, myFieldParse); -} - -//------------------------------------------------------------------------------------------------- -/*static */ void ArmorStore::parseArmorExtendDefinition(INI* ini) -{ - static const FieldParse myFieldParse[] = - { - { "Armor", ArmorTemplate::parseArmorCoefficients, NULL, 0 } - }; - - const char* new_armor_name = ini->getNextToken(); - - const char* parent = ini->getNextToken(); - const ArmorTemplate* parentTemplate = TheArmorStore->findArmorTemplate(parent); - if (parentTemplate == NULL) { - DEBUG_CRASH(("ArmorExtend must extend a previously defined Armor (%s).\n", parent)); - throw INI_INVALID_DATA; - } - - NameKeyType key = TheNameKeyGenerator->nameToKey(new_armor_name); - ArmorTemplate& armorTmpl = TheArmorStore->m_armorTemplates[key]; - armorTmpl.clear(); - armorTmpl.copyFrom(parentTemplate); - - ini->initFromINI(&armorTmpl, myFieldParse); -} - -//------------------------------------------------------------------------------------------------- -/*static*/ void INI::parseArmorDefinition(INI *ini) -{ - ArmorStore::parseArmorDefinition(ini); -} - -//------------------------------------------------------------------------------------------------- -/*static*/ void INI::parseArmorExtendDefinition(INI* ini) -{ - ArmorStore::parseArmorExtendDefinition(ini); -} +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: ArmorTemplate.cpp /////////////////////////////////////////////////////////////////////////////// +// Author: Steven Johnson, November 2001 +// Desc: ArmorTemplate descriptions +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + + +#include "Common/INI.h" +#include "Common/ThingFactory.h" +#include "GameLogic/Armor.h" +#include "GameLogic/Damage.h" + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +ArmorStore* TheArmorStore = NULL; ///< the ArmorTemplate store definition + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +ArmorTemplate::ArmorTemplate() +{ + clear(); +} + +//------------------------------------------------------------------------------------------------- +void ArmorTemplate::clear() +{ + for (int i = 0; i < DAMAGE_NUM_TYPES; i++) + { + m_damageCoefficient[i] = 1.0f; + } +} + +void ArmorTemplate::copyFrom(const ArmorTemplate* other) { + for (int i = 0; i < DAMAGE_NUM_TYPES; i++) + { + m_damageCoefficient[i] = other->m_damageCoefficient[i]; + } +} + +//------------------------------------------------------------------------------------------------- +Real ArmorTemplate::adjustDamage(DamageType t, Real damage) const +{ + if (t == DAMAGE_UNRESISTABLE) + return damage; + if (t == DAMAGE_SUBDUAL_UNRESISTABLE) + return damage; + if (t == DAMAGE_CHRONO_UNRESISTABLE) + return damage; + + damage *= m_damageCoefficient[t]; + + if (damage < 0.0f) + damage = 0.0f; + + return damage; +} + +//-------------------------------------------------------------------------------------------Static +/*static*/ void ArmorTemplate::parseArmorCoefficients( INI* ini, void *instance, void* /* store */, const void* userData ) +{ + ArmorTemplate* self = (ArmorTemplate*) instance; + + const char* damageName = ini->getNextToken(); + Real pct = INI::scanPercentToReal(ini->getNextToken()); + + if (stricmp(damageName, "Default") == 0) + { + for (Int i = 0; i < DAMAGE_NUM_TYPES; i++) + { + self->m_damageCoefficient[i] = pct; + } + return; + } + + DamageType dt = (DamageType)DamageTypeFlags::getSingleBitFromName(damageName); + self->m_damageCoefficient[dt] = pct; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ArmorStore::ArmorStore() +{ + m_armorTemplates.clear(); +} + +//------------------------------------------------------------------------------------------------- +ArmorStore::~ArmorStore() +{ + m_armorTemplates.clear(); +} + +//------------------------------------------------------------------------------------------------- +const ArmorTemplate* ArmorStore::findArmorTemplate(AsciiString name) const +{ + NameKeyType namekey = TheNameKeyGenerator->nameToKey(name); + ArmorTemplateMap::const_iterator it = m_armorTemplates.find(namekey); + if (it == m_armorTemplates.end()) + { + return NULL; + } + else + { + return &(*it).second; + } +} + +//------------------------------------------------------------------------------------------------- +/*static */ void ArmorStore::parseArmorDefinition(INI *ini) +{ + static const FieldParse myFieldParse[] = + { + { "Armor", ArmorTemplate::parseArmorCoefficients, NULL, 0 } + }; + + const char *c = ini->getNextToken(); + NameKeyType key = TheNameKeyGenerator->nameToKey(c); + ArmorTemplate& armorTmpl = TheArmorStore->m_armorTemplates[key]; + armorTmpl.clear(); + ini->initFromINI(&armorTmpl, myFieldParse); +} + +//------------------------------------------------------------------------------------------------- +/*static */ void ArmorStore::parseArmorExtendDefinition(INI* ini) +{ + static const FieldParse myFieldParse[] = + { + { "Armor", ArmorTemplate::parseArmorCoefficients, NULL, 0 } + }; + + const char* new_armor_name = ini->getNextToken(); + + const char* parent = ini->getNextToken(); + const ArmorTemplate* parentTemplate = TheArmorStore->findArmorTemplate(parent); + if (parentTemplate == NULL) { + DEBUG_CRASH(("ArmorExtend must extend a previously defined Armor (%s).\n", parent)); + throw INI_INVALID_DATA; + } + + NameKeyType key = TheNameKeyGenerator->nameToKey(new_armor_name); + ArmorTemplate& armorTmpl = TheArmorStore->m_armorTemplates[key]; + armorTmpl.clear(); + armorTmpl.copyFrom(parentTemplate); + + ini->initFromINI(&armorTmpl, myFieldParse); +} + +//------------------------------------------------------------------------------------------------- +/*static*/ void INI::parseArmorDefinition(INI *ini) +{ + ArmorStore::parseArmorDefinition(ini); +} + +//------------------------------------------------------------------------------------------------- +/*static*/ void INI::parseArmorExtendDefinition(INI* ini) +{ + ArmorStore::parseArmorExtendDefinition(ini); +} diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DelayedUpgradeBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DelayedUpgradeBehavior.cpp index f71e31f8290..f56b9c3486e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DelayedUpgradeBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DelayedUpgradeBehavior.cpp @@ -1,248 +1,248 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: DelayedUpgradeBehavior.cpp /////////////////////////////////////////////////////////////////////// -// Author: -// Desc: -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - - -//#include "Common/Thing.h" -//#include "Common/ThingTemplate.h" -#include "Common/INI.h" -//#include "Common/RandomValue.h" -#include "Common/Xfer.h" -#include "Common/Player.h" -//#include "GameClient/Drawable.h" -//#include "GameClient/FXList.h" -//#include "GameClient/InGameUI.h" -#include "GameLogic/GameLogic.h" -//#include "GameLogic/Module/BodyModule.h" -#include "GameLogic/Module/DelayedUpgradeBehavior.h" -#include "GameLogic/Module/AIUpdate.h" -#include "GameLogic/Object.h" -//#include "GameLogic/ObjectCreationList.h" -#include "GameLogic/Weapon.h" -//#include "GameClient/Drawable.h" - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -DelayedUpgradeBehavior::DelayedUpgradeBehavior(Thing* thing, const ModuleData* moduleData) : UpdateModule(thing, moduleData) -{ - DEBUG_LOG(("DelayedUpgradeBehavior::INIT\n")); - m_triggerCompleted = FALSE; - m_triggerFrame = 0; - //m_shotsLeft = 0; - - if (getDelayedUpgradeBehaviorModuleData()->m_initiallyActive) - { - giveSelfUpgrade(); - } - else { - setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -DelayedUpgradeBehavior::~DelayedUpgradeBehavior(void) -{ -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void DelayedUpgradeBehavior::upgradeImplementation(void) -{ - DEBUG_LOG(("DelayedUpgradeBehavior::upgradeImplementation() 1\n")); - - const DelayedUpgradeBehaviorModuleData* d = getDelayedUpgradeBehaviorModuleData(); - - UnsignedInt delay = d->m_triggerDelay; - // Trigger after time: - if (delay > 0) { - m_triggerFrame = TheGameLogic->getFrame() + delay; - } - - //if (d->m_triggerNumShots > 0) { - // m_shotsLeft = d->m_triggerNumShots; - // setWakeFrame(getObject(), UPDATE_SLEEP_NONE); - // return; - //} - - if (delay > 0) { - - DEBUG_LOG(("DelayedUpgradeBehavior::upgradeImplementation(): trigger_frame = %d\n", m_triggerFrame)); - - setWakeFrame(getObject(), UPDATE_SLEEP(d->m_triggerDelay)); - return; - } - - DEBUG_LOG(("DelayedUpgradeBehavior::upgradeImplementation(): We have no trigger!!!\n")); - -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -UpdateSleepTime DelayedUpgradeBehavior::update(void) -{ - if (m_triggerCompleted) { - DEBUG_LOG(("DelayedUpgradeBehavior::Update(): Already triggered. We should not be awake!!!\n")); - return UPDATE_SLEEP_FOREVER; - } - - if (!isUpgradeActive()) { - DEBUG_LOG(("DelayedUpgradeBehavior::Update(): Upgrade not applied. We should not be awake!!!\n")); - return UPDATE_SLEEP_FOREVER; - } - - const DelayedUpgradeBehaviorModuleData* d = getDelayedUpgradeBehaviorModuleData(); - - if (d->m_triggerDelay > 0) { - UnsignedInt now = TheGameLogic->getFrame(); - if (now >= m_triggerFrame) { - DEBUG_LOG(("DelayedUpgradeBehavior::update(): Trigger Frame reached.\n")); - triggerUpgrade(); - return UPDATE_SLEEP_FOREVER; - } - } - - //if (d->m_triggerNumShots > 0) { - - // //checkShots(); - // if (m_shotsLeft >= 0) { - // triggerUpgrade(); - // return UPDATE_SLEEP_FOREVER; - // } - //} - - return UPDATE_SLEEP_NONE; -} - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void DelayedUpgradeBehavior::triggerUpgrade(void) -{ - - const DelayedUpgradeBehaviorModuleData* d = getDelayedUpgradeBehaviorModuleData(); - const UpgradeTemplate* upgradeTemplate = TheUpgradeCenter->findUpgrade(d->m_upgradeToTrigger); - if (!upgradeTemplate) - { - DEBUG_ASSERTCRASH(0, ("DelayedUpgradeBehavior for %s can't find upgrade template %s.", getObject()->getName(), d->m_upgradeToTrigger)); - return; - } - - m_triggerCompleted = TRUE; - - Player* player = getObject()->getControllingPlayer(); - if (upgradeTemplate->getUpgradeType() == UPGRADE_TYPE_PLAYER) - { - // get the player - player->addUpgrade(upgradeTemplate, UPGRADE_STATUS_COMPLETE); - } - else - { - getObject()->giveUpgrade(upgradeTemplate); - } - - player->getAcademyStats()->recordUpgrade(upgradeTemplate, TRUE); - - DEBUG_LOG(("DelayedUpgradeBehavior::triggerUpgrade() Done.\n")); - -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Bool DelayedUpgradeBehavior::resetUpgrade(UpgradeMaskType keyMask) -{ - DEBUG_LOG(("DelayedUpgradeBehavior::resetUpgrade().\n")); - if (UpgradeMux::resetUpgrade(keyMask)) { - m_triggerCompleted = FALSE; - m_triggerFrame = 0; - // m_shotsLeft = 0; - return TRUE; - } - else { - return FALSE; - } -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void DelayedUpgradeBehavior::crc(Xfer* xfer) -{ - - // extend base class - BehaviorModule::crc(xfer); - - // extend upgrade mux - UpgradeMux::upgradeMuxCRC(xfer); - -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ - // ------------------------------------------------------------------------------------------------ -void DelayedUpgradeBehavior::xfer(Xfer* xfer) -{ - - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion(&version, currentVersion); - - // extend base class - BehaviorModule::xfer(xfer); - - // extend upgrade mux - UpgradeMux::upgradeMuxXfer(xfer); - - // trigger frame - xfer->xferUnsignedInt(&m_triggerFrame); - - // trigger completed - xfer->xferBool(&m_triggerCompleted); - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void DelayedUpgradeBehavior::loadPostProcess(void) -{ - - // extend base class - BehaviorModule::loadPostProcess(); - - // extend upgrade mux - UpgradeMux::upgradeMuxLoadPostProcess(); - -} // end loadPostProcess +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: DelayedUpgradeBehavior.cpp /////////////////////////////////////////////////////////////////////// +// Author: +// Desc: +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + + +//#include "Common/Thing.h" +//#include "Common/ThingTemplate.h" +#include "Common/INI.h" +//#include "Common/RandomValue.h" +#include "Common/Xfer.h" +#include "Common/Player.h" +//#include "GameClient/Drawable.h" +//#include "GameClient/FXList.h" +//#include "GameClient/InGameUI.h" +#include "GameLogic/GameLogic.h" +//#include "GameLogic/Module/BodyModule.h" +#include "GameLogic/Module/DelayedUpgradeBehavior.h" +#include "GameLogic/Module/AIUpdate.h" +#include "GameLogic/Object.h" +//#include "GameLogic/ObjectCreationList.h" +#include "GameLogic/Weapon.h" +//#include "GameClient/Drawable.h" + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +DelayedUpgradeBehavior::DelayedUpgradeBehavior(Thing* thing, const ModuleData* moduleData) : UpdateModule(thing, moduleData) +{ + DEBUG_LOG(("DelayedUpgradeBehavior::INIT\n")); + m_triggerCompleted = FALSE; + m_triggerFrame = 0; + //m_shotsLeft = 0; + + if (getDelayedUpgradeBehaviorModuleData()->m_initiallyActive) + { + giveSelfUpgrade(); + } + else { + setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +DelayedUpgradeBehavior::~DelayedUpgradeBehavior(void) +{ +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void DelayedUpgradeBehavior::upgradeImplementation(void) +{ + DEBUG_LOG(("DelayedUpgradeBehavior::upgradeImplementation() 1\n")); + + const DelayedUpgradeBehaviorModuleData* d = getDelayedUpgradeBehaviorModuleData(); + + UnsignedInt delay = d->m_triggerDelay; + // Trigger after time: + if (delay > 0) { + m_triggerFrame = TheGameLogic->getFrame() + delay; + } + + //if (d->m_triggerNumShots > 0) { + // m_shotsLeft = d->m_triggerNumShots; + // setWakeFrame(getObject(), UPDATE_SLEEP_NONE); + // return; + //} + + if (delay > 0) { + + DEBUG_LOG(("DelayedUpgradeBehavior::upgradeImplementation(): trigger_frame = %d\n", m_triggerFrame)); + + setWakeFrame(getObject(), UPDATE_SLEEP(d->m_triggerDelay)); + return; + } + + DEBUG_LOG(("DelayedUpgradeBehavior::upgradeImplementation(): We have no trigger!!!\n")); + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +UpdateSleepTime DelayedUpgradeBehavior::update(void) +{ + if (m_triggerCompleted) { + DEBUG_LOG(("DelayedUpgradeBehavior::Update(): Already triggered. We should not be awake!!!\n")); + return UPDATE_SLEEP_FOREVER; + } + + if (!isUpgradeActive()) { + DEBUG_LOG(("DelayedUpgradeBehavior::Update(): Upgrade not applied. We should not be awake!!!\n")); + return UPDATE_SLEEP_FOREVER; + } + + const DelayedUpgradeBehaviorModuleData* d = getDelayedUpgradeBehaviorModuleData(); + + if (d->m_triggerDelay > 0) { + UnsignedInt now = TheGameLogic->getFrame(); + if (now >= m_triggerFrame) { + DEBUG_LOG(("DelayedUpgradeBehavior::update(): Trigger Frame reached.\n")); + triggerUpgrade(); + return UPDATE_SLEEP_FOREVER; + } + } + + //if (d->m_triggerNumShots > 0) { + + // //checkShots(); + // if (m_shotsLeft >= 0) { + // triggerUpgrade(); + // return UPDATE_SLEEP_FOREVER; + // } + //} + + return UPDATE_SLEEP_NONE; +} + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void DelayedUpgradeBehavior::triggerUpgrade(void) +{ + + const DelayedUpgradeBehaviorModuleData* d = getDelayedUpgradeBehaviorModuleData(); + const UpgradeTemplate* upgradeTemplate = TheUpgradeCenter->findUpgrade(d->m_upgradeToTrigger); + if (!upgradeTemplate) + { + DEBUG_ASSERTCRASH(0, ("DelayedUpgradeBehavior for %s can't find upgrade template %s.", getObject()->getName(), d->m_upgradeToTrigger)); + return; + } + + m_triggerCompleted = TRUE; + + Player* player = getObject()->getControllingPlayer(); + if (upgradeTemplate->getUpgradeType() == UPGRADE_TYPE_PLAYER) + { + // get the player + player->addUpgrade(upgradeTemplate, UPGRADE_STATUS_COMPLETE); + } + else + { + getObject()->giveUpgrade(upgradeTemplate); + } + + player->getAcademyStats()->recordUpgrade(upgradeTemplate, TRUE); + + DEBUG_LOG(("DelayedUpgradeBehavior::triggerUpgrade() Done.\n")); + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool DelayedUpgradeBehavior::resetUpgrade(UpgradeMaskType keyMask) +{ + DEBUG_LOG(("DelayedUpgradeBehavior::resetUpgrade().\n")); + if (UpgradeMux::resetUpgrade(keyMask)) { + m_triggerCompleted = FALSE; + m_triggerFrame = 0; + // m_shotsLeft = 0; + return TRUE; + } + else { + return FALSE; + } +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void DelayedUpgradeBehavior::crc(Xfer* xfer) +{ + + // extend base class + BehaviorModule::crc(xfer); + + // extend upgrade mux + UpgradeMux::upgradeMuxCRC(xfer); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ + // ------------------------------------------------------------------------------------------------ +void DelayedUpgradeBehavior::xfer(Xfer* xfer) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion(&version, currentVersion); + + // extend base class + BehaviorModule::xfer(xfer); + + // extend upgrade mux + UpgradeMux::upgradeMuxXfer(xfer); + + // trigger frame + xfer->xferUnsignedInt(&m_triggerFrame); + + // trigger completed + xfer->xferBool(&m_triggerCompleted); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void DelayedUpgradeBehavior::loadPostProcess(void) +{ + + // extend base class + BehaviorModule::loadPostProcess(); + + // extend upgrade mux + UpgradeMux::upgradeMuxLoadPostProcess(); + +} // end loadPostProcess diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp index 093ba428bfe..47631498a54 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp @@ -1,1678 +1,1787 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: ActiveBody.cpp /////////////////////////////////////////////////////////////////////////// -// Author: Colin Day, November 2001 -// Desc: Active bodies have health, they can die and are affected by health -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine -#include "Common/BitFlagsIO.h" -#include "Common/CRCDebug.h" -#include "Common/DamageFX.h" -#include "Common/Player.h" -#include "Common/GameState.h" -#include "Common/GlobalData.h" -#include "Common/PlayerList.h" -#include "Common/Team.h" -#include "Common/Thing.h" -#include "Common/ThingTemplate.h" -#include "Common/Xfer.h" -#include "GameClient/ControlBar.h" -#include "GameClient/Drawable.h" -#include "GameClient/InGameUI.h" -#include "GameClient/ParticleSys.h" -#include "GameLogic/AI.h" -#include "GameLogic/AIPathfind.h" -#include "GameLogic/Armor.h" -#include "GameLogic/GameLogic.h" -#include "GameLogic/Object.h" -#include "GameLogic/Damage.h" -#include "GameLogic/PartitionManager.h" -#include "GameLogic/TerrainLogic.h" -#include "GameLogic/Weapon.h" -#include "GameLogic/Module/AIUpdate.h" -#include "GameLogic/Module/ActiveBody.h" -#include "GameLogic/Module/BridgeBehavior.h" -#include "GameLogic/Module/ContainModule.h" -#include "GameLogic/Module/DamageModule.h" -#include "GameLogic/Module/DieModule.h" - - -#ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -#define YELLOW_DAMAGE_PERCENT (0.25f) - -// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// - -// ------------------------------------------------------------------------------------------------ -/** Body particle systems are particle systems that are automatically created and attached - * to an object as the damage state changes for that object. We keep a list of these - * so that when we transition from one state to another we can kill any old particle - * systems that we need to before we create new ones */ -// ------------------------------------------------------------------------------------------------ -class BodyParticleSystem : public MemoryPoolObject -{ - - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( BodyParticleSystem, "BodyParticleSystem" ) - -public: - - ParticleSystemID m_particleSystemID; ///< the particle system ID - BodyParticleSystem *m_next; ///< next particle system in this body module - -}; - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -BodyParticleSystem::~BodyParticleSystem( void ) -{ - -} // end ~BodyParticleSystem - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------------- -// ------------------------------------------------------------------------------------------------ -static BodyDamageType calcDamageState(Real health, Real maxHealth) -{ - if (!TheGlobalData) - return BODY_PRISTINE; - - Real ratio = health / maxHealth; - - if (ratio > TheGlobalData->m_unitDamagedThresh) - { - return BODY_PRISTINE; - } - else if (ratio > TheGlobalData->m_unitReallyDamagedThresh) - { - return BODY_DAMAGED; - } - else if (ratio > 0.0f) - { - return BODY_REALLYDAMAGED; - } - else - { - return BODY_RUBBLE; - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -ActiveBodyModuleData::ActiveBodyModuleData() -{ - m_maxHealth = 0; - m_initialHealth = 0; - m_subdualDamageCap = 0; - m_subdualDamageHealRate = 0; - m_subdualDamageHealAmount = 0; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBodyModuleData::buildFieldParse(MultiIniFieldParse& p) -{ - ModuleData::buildFieldParse(p); - - static const FieldParse dataFieldParse[] = - { - { "MaxHealth", INI::parseReal, NULL, offsetof( ActiveBodyModuleData, m_maxHealth ) }, - { "InitialHealth", INI::parseReal, NULL, offsetof( ActiveBodyModuleData, m_initialHealth ) }, - - { "SubdualDamageCap", INI::parseReal, NULL, offsetof( ActiveBodyModuleData, m_subdualDamageCap ) }, - { "SubdualDamageHealRate", INI::parseDurationUnsignedInt, NULL, offsetof( ActiveBodyModuleData, m_subdualDamageHealRate ) }, - { "SubdualDamageHealAmount", INI::parseReal, NULL, offsetof( ActiveBodyModuleData, m_subdualDamageHealAmount ) }, - { 0, 0, 0, 0 } - }; - p.add(dataFieldParse); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -ActiveBody::ActiveBody( Thing *thing, const ModuleData* moduleData ) : - BodyModule(thing, moduleData), - m_curDamageFX(NULL), - m_curArmorSet(NULL), - m_frontCrushed(false), - m_backCrushed(false), - m_lastDamageTimestamp(0xffffffff),// So we don't think we just got damaged on the first frame - m_lastHealingTimestamp(0xffffffff),// So we don't think we just got healed on the first frame - m_curDamageState(BODY_PRISTINE), - m_nextDamageFXTime(0), - m_lastDamageFXDone((DamageType)-1), - m_lastDamageCleared(false), - m_particleSystems(NULL), - m_currentSubdualDamage(0), - m_indestructible(false), - m_damageFXOverride(false) -{ - m_currentHealth = getActiveBodyModuleData()->m_initialHealth; - m_prevHealth = getActiveBodyModuleData()->m_initialHealth; - m_maxHealth = getActiveBodyModuleData()->m_maxHealth; - m_initialHealth = getActiveBodyModuleData()->m_initialHealth; - - // force an initially-valid armor setup - validateArmorAndDamageFX(); - // start us in the right state - setCorrectDamageState(); - -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -ActiveBody::~ActiveBody( void ) -{ -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::onDelete( void ) -{ - - // delete all particle systems - deleteAllParticleSystems(); - -} // end onDelete - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBody::setCorrectDamageState() -{ - m_curDamageState = calcDamageState(m_currentHealth, m_maxHealth); - - /// @todo srj -- bleah, this is an icky way to do it. oh well. - if (m_curDamageState == BODY_RUBBLE && getObject()->isKindOf(KINDOF_STRUCTURE)) - { - Real rubbleHeight = getObject()->getTemplate()->getStructureRubbleHeight(); - - if (rubbleHeight <= 0.0f) - rubbleHeight = TheGlobalData->m_defaultStructureRubbleHeight; - - /** @todo I had to change this to a Z only version to keep it from disappearing from the - PartitionManager for a frame. That didn't used to happen. - */ - getObject()->setGeometryInfoZ(rubbleHeight); - - // Have to tell pathfind as well, as rubble pathfinds differently. - TheAI->pathfinder()->removeObjectFromPathfindMap(getObject()); - TheAI->pathfinder()->addObjectToPathfindMap(getObject()); - - - // here we make sure nobody collides with us, ever again... //Lorenzen - //THis allows projectiles shot from infantry that are inside rubble to get out of said rubble safely - getObject()->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_COLLISIONS ) ); - - - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBody::setDamageState( BodyDamageType newState ) -{ - Real ratio = 1.0f; - if( newState == BODY_PRISTINE ) - { - ratio = 1.0f; - } - else if( newState == BODY_DAMAGED ) - { - ratio = TheGlobalData->m_unitDamagedThresh; - } - else if( newState == BODY_REALLYDAMAGED ) - { - ratio = TheGlobalData->m_unitReallyDamagedThresh; - } - else if( newState == BODY_RUBBLE ) - { - ratio = 0.0f; - } - Real desiredHealth = m_maxHealth * ratio - 1;// -1 because < not <= in calcState - desiredHealth = max( desiredHealth, 0.0f ); - internalChangeHealth( desiredHealth - m_currentHealth ); - setCorrectDamageState(); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBody::validateArmorAndDamageFX() const -{ - const ArmorTemplateSet* set = getObject()->getTemplate()->findArmorTemplateSet(m_curArmorSetFlags); - DEBUG_ASSERTCRASH(set, ("findArmorSet should never return null")); - if (set && set != m_curArmorSet) - { - if (set->getArmorTemplate()) - { - m_curArmor = TheArmorStore->makeArmor(set->getArmorTemplate()); - } - else - { - m_curArmor.clear(); - } - if (!m_damageFXOverride) m_curDamageFX = set->getDamageFX(); // Only set this if override is cleared - m_curArmorSet = set; - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Real ActiveBody::estimateDamage( DamageInfoInput& damageInfo ) const -{ - validateArmorAndDamageFX(); - - //Subdual damage can't affect you if you can't be subdued - if( IsSubdualDamage(damageInfo.m_damageType) && !canBeSubdued() ) - return 0.0f; - - if( damageInfo.m_damageType == DAMAGE_KILL_GARRISONED ) - { - ContainModuleInterface* contain = getObject()->getContain(); - if( contain && contain->getContainCount() > 0 && contain->isGarrisonable() && !contain->isImmuneToClearBuildingAttacks() ) - return 1.0f; - else - return 0.0f; - } - - if( damageInfo.m_damageType == DAMAGE_SNIPER ) - { - if( getObject()->isKindOf( KINDOF_STRUCTURE ) && getObject()->testStatus( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) - { - //If we're a pathfinder shooting a stinger site under construction... don't. Special case code. - return 0.0f; - } - } - - Real amount = m_curArmor.adjustDamage(damageInfo.m_damageType, damageInfo.m_amount); - - return amount; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBody::doDamageFX( const DamageInfo *damageInfo ) -{ - DamageType damageTypeToUse = damageInfo->in.m_damageType; - if (damageInfo->in.m_damageFXOverride != DAMAGE_UNRESISTABLE ) - { - // Just the visual aspect of damage can be overridden in some cases. - // Unresistable is the default to mean no override, as we are out of bits. - damageTypeToUse = damageInfo->in.m_damageFXOverride; - } - - if (m_curDamageFX) - { - UnsignedInt now = TheGameLogic->getFrame(); - if (damageTypeToUse == m_lastDamageFXDone && m_nextDamageFXTime > now) - return; - Object *source = TheGameLogic->findObjectByID(damageInfo->in.m_sourceID); // might be null, I guess - m_lastDamageFXDone = damageTypeToUse; - m_nextDamageFXTime = now + m_curDamageFX->getDamageFXThrottleTime(damageTypeToUse, source); - m_curDamageFX->doDamageFX(damageTypeToUse, damageInfo->out.m_actualDamageDealt, source, getObject()); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBody::attemptDamage( DamageInfo *damageInfo ) -{ - validateArmorAndDamageFX(); - - // sanity - if( damageInfo == NULL ) - return; - - if ( m_indestructible ) - return; - - // initialize these, just in case we bail out early - damageInfo->out.m_actualDamageDealt = 0.0f; - damageInfo->out.m_actualDamageClipped = 0.0f; - - // we cannot damage again objects that are already dead - Object* obj = getObject(); - if( obj->isEffectivelyDead() ) - return; - - Object *damager = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID ); - if( damager ) - { - //Store the template so later if the attacking object dies, we use script conditions to look at the - //damager's template inside evaluateTeamAttackedByType or evaluateNameAttackedByType. - damageInfo->in.m_sourceTemplate = damager->getTemplate(); - } - - Bool alreadyHandled = FALSE; - Bool allowModifier = TRUE; - Real amount = m_curArmor.adjustDamage(damageInfo->in.m_damageType, damageInfo->in.m_amount); - - switch( damageInfo->in.m_damageType ) - { - case DAMAGE_HEALING: - { - if( !damageInfo->in.m_kill ) - { - // Healing and Damage are separate, so this shouldn't happen - attemptHealing( damageInfo ); - } - return; - } - - case DAMAGE_KILLPILOT: - { - // This type of damage doesn't actually damage the unit, but it does kill it's - // pilot, in the case of a vehicle. - if( obj->isKindOf( KINDOF_VEHICLE ) ) - { - //Handle special case for combat bike. We actually will kill the bike by - //forcing the rider to leave the bike. That way the bike will automatically - //scuttle and be unusable. - ContainModuleInterface *contain = obj->getContain(); - if( contain && contain->isRiderChangeContain() ) - { - - AIUpdateInterface *ai = obj->getAI(); - - if( ai->isMoving() ) - { - //Bike is moving, so just blow it up instead. - if (damager) - damager->scoreTheKill( obj ); - obj->kill(); - } - else - { - //Removing the rider will scuttle the bike. - Object *rider = *(contain->getContainedItemsList()->begin()); - ai->aiEvacuateInstantly( TRUE, CMD_FROM_AI ); - - //Kill the rider. - if (damager) - damager->scoreTheKill( rider ); - rider->kill(); - } - } - else - { - // Make it unmanned, so units can easily check the ability to "take control of it" - obj->setDisabled( DISABLED_UNMANNED ); - TheGameLogic->deselectObject(obj, PLAYERMASK_ALL, TRUE); - - if ( obj->getAI() ) - obj->getAI()->aiIdle( CMD_FROM_AI ); - - // Convert it to the neutral team so it renders gray giving visual representation that it is unmanned. - obj->setTeam( ThePlayerList->getNeutralPlayer()->getDefaultTeam() ); - } - - //We don't care which team sniped the vehicle... we use this information to flag whether or not - //we captured a vehicle. - ThePlayerList->getNeutralPlayer()->getAcademyStats()->recordVehicleSniped(); - } - alreadyHandled = TRUE; - allowModifier = FALSE; - break; - } - - case DAMAGE_KILL_GARRISONED: - { - // KRIS: READ THIS!!! - // This code is very misleading (but in a good way). One would think this is - // an excellent place to add the hook to kill garrisoned troops. And that is - // a correct assumption. Unfortunately, the vast majority of garrison slayings - // are performed in DumbProjectileBehavior::projectileHandleCollision(), so my - // hope is that this message will save you some research time! - - Int killsToMake = REAL_TO_INT_FLOOR(damageInfo->in.m_amount); - ContainModuleInterface* contain = obj->getContain(); - if( contain && contain->getContainCount() > 0 && contain->isGarrisonable() && !contain->isImmuneToClearBuildingAttacks() ) - { - Int numKilled = 0; - - // garrisonable buildings subvert the normal process here. - const ContainedItemsList* items = contain->getContainedItemsList(); - if (items) - { - for( ContainedItemsList::const_iterator it = items->begin(); (it != items->end()) && (numKilled < killsToMake); it++ ) - { - Object* thingToKill = *it; - if (!thingToKill->isEffectivelyDead() ) - { - if (damager) - damager->scoreTheKill( thingToKill ); - thingToKill->kill(); - ++numKilled; - thingToKill->getControllingPlayer()->getAcademyStats()->recordClearedGarrisonedBuilding(); - } - } // next contained item - - } // if items - } // if a garrisonable thing - alreadyHandled = TRUE; - allowModifier = FALSE; - break; - } - - case DAMAGE_STATUS: - { - // Damage amount is msec time we set the status given in damageStatusType - Real realFramesToStatusFor = ConvertDurationFromMsecsToFrames(amount); - obj->doStatusDamage( damageInfo->in.m_damageStatusType , REAL_TO_INT_CEIL(realFramesToStatusFor) ); - alreadyHandled = TRUE; - allowModifier = FALSE; - break; - } - } - - if( IsSubdualDamage(damageInfo->in.m_damageType) ) - { - if( !canBeSubdued() ) - return; - - Bool wasSubdued = isSubdued(); - internalAddSubdualDamage(amount); - Bool nowSubdued = isSubdued(); - alreadyHandled = TRUE; - allowModifier = FALSE; - - if( wasSubdued != nowSubdued ) - { - onSubdualChange(nowSubdued); - } - - getObject()->notifySubdualDamage(amount); - } - - if (allowModifier) - { - if( damageInfo->in.m_damageType != DAMAGE_UNRESISTABLE ) - { - // Apply the damage scalar (extra bonuses -- like strategy center defensive battle plan) - // And remember not to adjust unresistable damage, just like the armor code can't. - amount *= m_damageScalar; - } - } - - // sanity check the damage value -- can't apply negative damage - if( amount > 0.0f || damageInfo->in.m_kill ) - { - BodyDamageType oldState = m_curDamageState; - - //If the object is going to die, make sure we damage all remaining health. - if( damageInfo->in.m_kill ) - { - amount = m_currentHealth; - } - - if (!alreadyHandled) - { - // do the damage simplistic damage subtraction - internalChangeHealth( -amount ); - } - -#ifdef ALLOW_SURRENDER -//***************************************************************************************** -//***************************************************************************************** -//THIS CODE HAS BEEN DISABLED FOR THE MULTIPLAYER PLAY TEST!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!** -//***************************************************************************************** -// // if we were "killed" by surrender damage... -// if (damageInfo->in.m_damageType == DAMAGE_SURRENDER && m_currentHealth <= 0.0f && obj->isKindOf(KINDOF_CAN_SURRENDER)) -// { -// AIUpdateInterface* ai = obj->getAIUpdateInterface(); -// if (ai) -// { -// // do no damage, but make it surrender instead. -// m_currentHealth = m_prevHealth; -// const Object* killer = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID ); -// ai->setSurrendered(killer, true); -// return; -// } -// } -//***************************************************************************************** -//***************************************************************************************** -#endif - - // record the actual damage done from this, and when it happened - damageInfo->out.m_actualDamageDealt = amount; - damageInfo->out.m_actualDamageClipped = m_prevHealth - m_currentHealth; - - // then copy the whole DamageInfo struct for easy lookup - // (object pointer loses scope as soon as atteptdamage's caller ends) - // m_lastDamageTimestamp is initialized to FFFFFFFFFF, so doing a < compare is problematic. - // jba. - if (m_lastDamageTimestamp!=TheGameLogic->getFrame() && m_lastDamageTimestamp != TheGameLogic->getFrame()-1) { - m_lastDamageInfo = *damageInfo; - m_lastDamageCleared = false; - m_lastDamageTimestamp = TheGameLogic->getFrame(); - } else { - // Multiple damages applied in one/next frame. We prefer the one that tells who the attacker is. - Object *srcObj1 = TheGameLogic->findObjectByID(m_lastDamageInfo.in.m_sourceID); - Object *srcObj2 = TheGameLogic->findObjectByID(damageInfo->in.m_sourceID); - if (srcObj2) { - if (srcObj1) { - if (srcObj2->isKindOf(KINDOF_VEHICLE) || srcObj2->isKindOf(KINDOF_INFANTRY) || - srcObj2->isFactionStructure()) { - m_lastDamageInfo = *damageInfo; - m_lastDamageCleared = false; - m_lastDamageTimestamp = TheGameLogic->getFrame(); - } - } else { - m_lastDamageInfo = *damageInfo; - m_lastDamageCleared = false; - m_lastDamageTimestamp = TheGameLogic->getFrame(); - } - - } else { - // no change. - } - } - - // Notify the player that they have been attacked by this player - if (m_lastDamageInfo.in.m_sourceID != INVALID_ID) - { - Object *srcObj = TheGameLogic->findObjectByID(m_lastDamageInfo.in.m_sourceID); - if (srcObj) - { - Player *srcPlayer = srcObj->getControllingPlayer(); - obj->getControllingPlayer()->setAttackedBy(srcPlayer->getPlayerIndex()); - } - } - - // if our health has gone down then do run the damage module callback - if( m_currentHealth < m_prevHealth ) - { - for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m) - { - DamageModuleInterface* d = (*m)->getDamage(); - if (!d) - continue; - - d->onDamage( damageInfo ); - } - } - - if (m_curDamageState != oldState) - { - for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m) - { - DamageModuleInterface* d = (*m)->getDamage(); - if (!d) - continue; - - d->onBodyDamageStateChange( damageInfo, oldState, m_curDamageState ); - } - - // @todo: This really feels like it should be in the TransitionFX lists. - if (m_curDamageState == BODY_DAMAGED) - { - AudioEventRTS damaged = *obj->getTemplate()->getSoundOnDamaged(); - damaged.setObjectID(obj->getID()); - TheAudio->addAudioEvent(&damaged); - } - else if (m_curDamageState == BODY_REALLYDAMAGED) - { - AudioEventRTS reallyDamaged = *obj->getTemplate()->getSoundOnReallyDamaged(); - reallyDamaged.setObjectID(obj->getID()); - TheAudio->addAudioEvent(&reallyDamaged); - } - - } - - // Should we play our fear sound? - if( (m_prevHealth / m_maxHealth) > YELLOW_DAMAGE_PERCENT && - (m_currentHealth / m_maxHealth) < YELLOW_DAMAGE_PERCENT && - (m_currentHealth > 0) ) - { - // 25% chance to play - if (GameLogicRandomValue(0, 99) < 25) - { - AudioEventRTS fearSound = *obj->getTemplate()->getVoiceFear(); - fearSound.setPosition( obj->getPosition() ); - fearSound.setPlayerIndex( obj->getControllingPlayer()->getPlayerIndex() ); - TheAudio->addAudioEvent(&fearSound); - } - } - - // check to see if we died - if( m_currentHealth <= 0 && m_prevHealth > 0 ) - { - // Give our killer credit for killing us, if there is one. - if( damager ) - { - damager->scoreTheKill( obj ); - } - - obj->onDie( damageInfo ); - } - } - - doDamageFX(damageInfo); - - // Damaged repulsable civilians scare (repulse) other civs. jba. - if( TheAI->getAiData()->m_enableRepulsors ) - { - if( obj->isKindOf( KINDOF_CAN_BE_REPULSED ) ) - { - obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_REPULSOR ) ); - } - } - - //Retaliate, even if I'm dead -- we'll still get my nearby friends to get revenge!!! - //Also only retaliate if we're controlled by a human player and the thing that attacked me - //is an enemy. - Player *controllingPlayer = obj->getControllingPlayer(); - if( controllingPlayer && controllingPlayer->isLogicalRetaliationModeEnabled() && controllingPlayer->getPlayerType() == PLAYER_HUMAN ) - { - if( shouldRetaliateAgainstAggressor(obj, damager)) - { - PartitionFilterPlayerAffiliation f1( controllingPlayer, ALLOW_ALLIES, true ); - PartitionFilterOnMap filterMapStatus; - PartitionFilter *filters[] = { &f1, &filterMapStatus, 0 }; - - - Real distance = TheAI->getAiData()->m_retaliateFriendsRadius + obj->getGeometryInfo().getBoundingCircleRadius(); - SimpleObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( obj->getPosition(), distance, FROM_CENTER_2D, filters, ITER_FASTEST ); - MemoryPoolObjectHolder hold( iter ); - for( Object *them = iter->first(); them; them = iter->next() ) - { - if (!shouldRetaliate(them)) { - continue; - } - AIUpdateInterface *ai = them->getAI(); - if (ai==NULL) { - continue; - } - //If we have AI and we're mobile, then assist! - if( !them->isKindOf( KINDOF_IMMOBILE )) - { - //But only if we can attack it! - CanAttackResult result = them->getAbleToAttackSpecificObject( ATTACK_NEW_TARGET, damager, CMD_FROM_AI ); - if( result == ATTACKRESULT_POSSIBLE_AFTER_MOVING || result == ATTACKRESULT_POSSIBLE ) - { - ai->aiGuardRetaliate( damager, them->getPosition(), NO_MAX_SHOTS_LIMIT, CMD_FROM_AI ); - } - } - } - } - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Bool ActiveBody::shouldRetaliateAgainstAggressor(Object *obj, Object *damager) -{ - /* This considers whether obj should invoke his friends to retaliate against damager. - Note that obj could be a structure, so we don't actually check whether obj will - retaliate, as in many cases he wouldn't. */ - if (damager==NULL) { - return false; - } - if (damager->isAirborneTarget()) { - return false; // Don't retaliate against aircraft. [8/25/2003] - } - if (damager->getRelationship( obj ) != ENEMIES) { - return false; // only retaliate against enemies. - } - Real distSqr = ThePartitionManager->getDistanceSquared(obj, damager, FROM_BOUNDINGSPHERE_2D); - if (distSqr > sqr(TheAI->getAiData()->m_maxRetaliateDistance)) { - return false; - } - // Only human players retaliate. [8/25/2003] - if (obj->getControllingPlayer()->getPlayerType() != PLAYER_HUMAN) { - return false; - } - // Drones never retaliate. [8/25/2003] - if (obj->isKindOf(KINDOF_DRONE)) { - return false; - } - return true; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Bool ActiveBody::shouldRetaliate(Object *obj) -{ - // Cannot retaliate objects dont. [8/25/2003] - if (obj->isKindOf(KINDOF_CANNOT_RETALIATE)) { - return false; - } - if (obj->isKindOf( KINDOF_IMMOBILE )) { - return false; - } - // Drones never retaliate. [8/25/2003] - if (obj->isKindOf(KINDOF_DRONE)) { - return false; - } - // Any unit that isn't idle won't retaliate. [8/25/2003] - if (obj->getAI()) { - if (!obj->getAI()->isIdle()) { - return false; - } - } else { - return false; // Non-ai can't retaliate. [8/26/2003] - } - // Stealthed units don't retaliate unless they're detected. [8/25/2003] - if ( obj->getStatusBits().test( OBJECT_STATUS_STEALTHED ) && - !obj->getStatusBits().test( OBJECT_STATUS_DETECTED ) ) { - return false; - } - // If we're using an ability, don't stop. [8/25/2003] - if (obj->testStatus(OBJECT_STATUS_IS_USING_ABILITY)) { - return false; - } - return true; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBody::attemptHealing( DamageInfo *damageInfo ) -{ - validateArmorAndDamageFX(); - - // sanity - if( damageInfo == NULL ) - return; - - if( damageInfo->in.m_damageType != DAMAGE_HEALING ) - { - // Healing and Damage are separate, so this shouldn't happen - attemptDamage( damageInfo ); - return; - } - - Object* obj = getObject(); - - // srj sez: sorry, once yer dead, yer dead. - // Special case for bridges, cause the system now things they're dead - ///@todo we need to figure out what has changed so we don't have to hack this (CBD 11-1-2002) - if( obj->isKindOf( KINDOF_BRIDGE ) == FALSE && - obj->isKindOf( KINDOF_BRIDGE_TOWER ) == FALSE && - obj->isEffectivelyDead()) - return; - - // initialize these, just in case we bail out early - damageInfo->out.m_actualDamageDealt = 0.0f; - damageInfo->out.m_actualDamageClipped = 0.0f; - - Real amount = m_curArmor.adjustDamage(damageInfo->in.m_damageType, damageInfo->in.m_amount); - - // sanity check the damage value -- can't apply negative healing - if( amount > 0.0f ) - { - BodyDamageType oldState = m_curDamageState; - - // do the damage simplistic damage ADDITION - internalChangeHealth( amount ); - - // record the actual damage done from this, and when it happened - damageInfo->out.m_actualDamageDealt = amount; - damageInfo->out.m_actualDamageClipped = m_prevHealth - m_currentHealth; - - //then copy the whole DamageInfo struct for easy lookup - //(object pointer loses scope as soon as atteptdamage's caller ends) - m_lastDamageInfo = *damageInfo; - m_lastDamageCleared = false; - m_lastDamageTimestamp = TheGameLogic->getFrame(); - m_lastHealingTimestamp = TheGameLogic->getFrame(); - - // if our health has gone UP then do run the damage module callback - if( m_currentHealth > m_prevHealth ) - { - for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m) - { - DamageModuleInterface* d = (*m)->getDamage(); - if (!d) - continue; - - d->onHealing( damageInfo ); - } - } - - if (m_curDamageState != oldState) - { - for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m) - { - DamageModuleInterface* d = (*m)->getDamage(); - if (!d) - continue; - - d->onBodyDamageStateChange( damageInfo, oldState, m_curDamageState ); - } - } - } - - doDamageFX(damageInfo); -} - -//------------------------------------------------------------------------------------------------- -/** Simple setting of the health value, it does *NOT* track any transition - * states for the event of "damage" or the event of "death". */ -//------------------------------------------------------------------------------------------------- -void ActiveBody::setInitialHealth(Int initialPercent) -{ - - // save the current health as the previous health - m_prevHealth = m_currentHealth; - - Real factor = initialPercent/100.0f; - Real newHealth = factor * m_initialHealth; - - // change the health to the requested percentage. - internalChangeHealth(newHealth - m_currentHealth); - -} - -//------------------------------------------------------------------------------------------------- -/** Simple setting of the health value, it does *NOT* track any transition - * states for the event of "damage" or the event of "death". */ -//------------------------------------------------------------------------------------------------- -void ActiveBody::setMaxHealth( Real maxHealth, MaxHealthChangeType healthChangeType ) -{ - Real prevMaxHealth = m_maxHealth; - m_maxHealth = maxHealth; - m_initialHealth = maxHealth; - - switch( healthChangeType ) - { - case PRESERVE_RATIO: - { - //400/500 (80%) + 100 becomes 480/600 (80%) - //200/500 (40%) - 100 becomes 160/400 (40%) - Real ratio = m_currentHealth / prevMaxHealth; - Real newHealth = maxHealth * ratio; - internalChangeHealth( newHealth - m_currentHealth ); - break; - } - case ADD_CURRENT_HEALTH_TOO: - { - //Add the same amount that we are adding to the max health. - //This could kill you if max health is reduced (if we ever have that ability to add buffer health like in D&D) - //400/500 (80%) + 100 becomes 500/600 (83%) - //200/500 (40%) - 100 becomes 100/400 (25%) - internalChangeHealth( maxHealth - prevMaxHealth ); - break; - } - case SAME_CURRENTHEALTH: - //do nothing - break; - - case FULLY_HEAL: - { - // Set current to the new Max. - //400/500 (80%) + 100 becomes 600/600 (100%) - //200/500 (40%) - 100 becomes 400/400 (100%) - internalChangeHealth(m_maxHealth - m_currentHealth); - break; - } - } - - // - // when max health is getting clipped to a lower value, if our current health - // value is now outside of the max health range we will set it back down to the - // new cap. Note that we are *NOT* going through any healing or damage methods here - // and are doing a direct set - // - if( m_currentHealth > maxHealth ) - { - internalChangeHealth( maxHealth - m_currentHealth ); - } - -} - -// ------------------------------------------------------------------------------------------------ -/** Given the current damage state of the object, evaluate the visual model conditions - * that have a visual impact on the object */ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::evaluateVisualCondition() -{ - - Drawable* draw = getObject()->getDrawable(); - if (draw) - { - draw->reactToBodyDamageStateChange(m_curDamageState); - } - - // - // destroy any particle systems that were attached to our body for the old state - // and create new particle systems for the new state - // - updateBodyParticleSystems(); - -} - -// ------------------------------------------------------------------------------------------------ -/** Create up to maxSystems particle systems of type particleSystemName and attach to bones - * specified by the bone base name. If there are more bones than maxSystems then the - * bones will be randomly selected */ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::createParticleSystems( const AsciiString &boneBaseName, - const ParticleSystemTemplate *systemTemplate, - Int maxSystems ) -{ - Object *us = getObject(); - - // sanity - if( systemTemplate == NULL ) - return; - - // get the bones - enum { MAX_BONES = 16 }; - Coord3D bonePositions[ MAX_BONES ]; - Int numBones = us->getMultiLogicalBonePosition( boneBaseName.str(), - MAX_BONES, - bonePositions, - NULL, - FALSE ); - - // if no bones found nothing else to do - if( numBones == 0 ) - return; - - // - // if we don't have enough bones to go up to maxSystems, we will change maxSystems to be - // the number of bones we actually have (we don't want systems doubling up on bones) - // - if( numBones < maxSystems ) - maxSystems = numBones; - - // - // create an array that we'll use to mark which bone positions have already been used, - // this is necessary when we have more bones than particle systems we're going to - // create, in which case we place the particle systems at random bone locations - // but don't want to repeat any - // - Bool usedBoneIndices[ MAX_BONES ] = { FALSE }; - - // create the particle systems - const Coord3D *pos; - for( Int i = 0; i < maxSystems; ++i ) - { - - // pick a bone index to place this particle system at - // MDC: moving to GameLogicRandomValue. This does not need to be synced, but having it so makes searches *so* much nicer. - // DTEH: Moved back to GameClientRandomValue because of desync problems. July 27th 2003. - Int boneIndex = GameClientRandomValue( 0, maxSystems - i - 1 ); - - // find the actual bone location to use and mark that bone index as used - Int count = 0; - Int j = 0; - for( ; j < numBones; j++ ) - { - - // ignore bone positions that have already been used - if( usedBoneIndices[ j ] == TRUE ) - continue; - - // this spot is available, if count == boneIndex then use this index - if( count == boneIndex ) - { - - pos = &bonePositions[ j ]; - usedBoneIndices[ j ] = TRUE; - break; // exit for j - - } // end if - else - { - - // we won't use this index, increment count until we find a suitable index to use - ++count; - - } // end else - - } // end for, j - - // sanity - DEBUG_ASSERTCRASH( j != numBones, - ("ActiveBody::createParticleSystems, Unable to select particle system index\n") ); - - // create particle system here - ParticleSystem *particleSystem = TheParticleSystemManager->createParticleSystem( systemTemplate ); - if( particleSystem ) - { - - // set the position of the particle system in local object space - particleSystem->setPosition( pos ); - - // attach particle system to object - particleSystem->attachToObject( us ); - - // create a new body particle system entry and keep this particle system in it - BodyParticleSystem *newEntry = newInstance(BodyParticleSystem); - newEntry->m_particleSystemID = particleSystem->getSystemID(); - newEntry->m_next = m_particleSystems; - m_particleSystems = newEntry; - - } // end if - - } // end for, i - -} // end createParticleSystems - -// ------------------------------------------------------------------------------------------------ -/** Delete all the body particle systems */ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::deleteAllParticleSystems( void ) -{ - BodyParticleSystem *nextBodySystem; - ParticleSystem *particleSystem; - - while( m_particleSystems ) - { - - // get this particle system - particleSystem = TheParticleSystemManager->findParticleSystem( m_particleSystems->m_particleSystemID ); - if( particleSystem ) - particleSystem->destroy(); - - // get next system in the body - nextBodySystem = m_particleSystems->m_next; - - // destroy this entry - m_particleSystems->deleteInstance(); - - // set the body systems head to the next - m_particleSystems = nextBodySystem; - - } // end while - -} // end deleteAllParticleSystems - -// ------------------------------------------------------------------------------------------------ -/* This function is called on state changes only. Body Type or Aflameness. */ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::updateBodyParticleSystems( void ) -{ - static const ParticleSystemTemplate *fireSmallTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoFireParticleSmallSystem ); - static const ParticleSystemTemplate *fireMediumTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoFireParticleMediumSystem ); - static const ParticleSystemTemplate *fireLargeTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoFireParticleLargeSystem ); - static const ParticleSystemTemplate *smokeSmallTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoSmokeParticleSmallSystem ); - static const ParticleSystemTemplate *smokeMediumTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoSmokeParticleMediumSystem ); - static const ParticleSystemTemplate *smokeLargeTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoSmokeParticleLargeSystem ); - static const ParticleSystemTemplate *aflameTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoAflameParticleSystem ); - Int countModifier; - const ParticleSystemTemplate *fireSmall; - const ParticleSystemTemplate *fireMedium; - const ParticleSystemTemplate *fireLarge; - const ParticleSystemTemplate *smokeSmall; - const ParticleSystemTemplate *smokeMedium; - const ParticleSystemTemplate *smokeLarge; - - // - // when we're aflame, we use a slightly different set of particle systems that are - // auto created that lends itself to more fire and bigger fire - // - if( getObject()->testStatus( OBJECT_STATUS_AFLAME ) ) - { - - fireSmall = fireMediumTemplate; // small fire becomes medium fire - fireMedium = fireLargeTemplate; // medium fire becomes large fire - fireLarge = fireLargeTemplate; // large fire stays large - smokeSmall = fireSmallTemplate; // small smoke becomes small fire - smokeMedium = fireSmallTemplate; // medium smoke becomes small fire - smokeLarge = fireSmallTemplate; // large smoke becomes small fire - - // we get to make more of them all too - countModifier = 2; - - } // end if - else - { - - // use regular templates - fireSmall = fireSmallTemplate; - fireMedium = fireMediumTemplate; - fireLarge = fireLargeTemplate; - smokeSmall = smokeSmallTemplate; - smokeMedium = smokeMediumTemplate; - smokeLarge = smokeLargeTemplate; - - // we make just the normal amount of these - countModifier = 1; - - } // end else - - // - // remove any particle systems we have currently in the list in favor of any new ones - // that we're going to autopopulate ourselves with - // - deleteAllParticleSystems(); - - // - // create particle systems for the new body state - // - - // small fire bones - createParticleSystems( TheGlobalData->m_autoFireParticleSmallPrefix, - fireSmall, TheGlobalData->m_autoFireParticleSmallMax * countModifier ); - - // medium fire bones - createParticleSystems( TheGlobalData->m_autoFireParticleMediumPrefix, - fireMedium, TheGlobalData->m_autoFireParticleMediumMax * countModifier ); - - // large fire bones - createParticleSystems( TheGlobalData->m_autoFireParticleLargePrefix, - fireLarge, TheGlobalData->m_autoFireParticleLargeMax * countModifier ); - - // small smoke bones - createParticleSystems( TheGlobalData->m_autoSmokeParticleSmallPrefix, - smokeSmall, TheGlobalData->m_autoSmokeParticleSmallMax * countModifier ); - - // medium smoke bones - createParticleSystems( TheGlobalData->m_autoSmokeParticleMediumPrefix, - smokeMedium, TheGlobalData->m_autoSmokeParticleMediumMax * countModifier ); - - // large smoke bones - createParticleSystems( TheGlobalData->m_autoSmokeParticleLargePrefix, - smokeLarge, TheGlobalData->m_autoSmokeParticleLargeMax * countModifier ); - - // actively on fire - if( getObject()->testStatus( OBJECT_STATUS_AFLAME ) ) - createParticleSystems( TheGlobalData->m_autoAflameParticlePrefix, - aflameTemplate, TheGlobalData->m_autoAflameParticleMax * countModifier ); - -} // end updatebodyParticleSystems - -//------------------------------------------------------------------------------------------------- -/** Simple changing of the health value, it does *NOT* track any transition - * states for the event of "damage" or the event of "death". If you - * with to kill an object and give these modules a chance to react - * to that event use the proper damage method calls. - * No game logic should go in here. This is the low level math and flag maintenance. - * Game stuff goes in attemptDamage and attemptHealing. -*/ -//------------------------------------------------------------------------------------------------- -void ActiveBody::internalChangeHealth( Real delta ) -{ - // save the current health as the previous health - m_prevHealth = m_currentHealth; - - // change the health by the delta, it can be positive or negative - m_currentHealth += delta; - - // high end cap - Real maxHealth = m_maxHealth; - if( m_currentHealth > maxHealth ) - m_currentHealth = maxHealth; - - // low end cap - const Real lowEndCap = 0.0f; // low end cap for health, don't go below this - if( m_currentHealth < lowEndCap ) - m_currentHealth = lowEndCap; - - // recalc the damage state - BodyDamageType oldState = m_curDamageState; - setCorrectDamageState(); - - // if our state has changed - if( m_curDamageState != oldState ) - { - - // - // show a visual change in the model for the damage state, we do not show visual changes - // for damage states when things are under construction because we just don't have - // all the art states for that during buildup animation - // - if( !getObject()->getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) - evaluateVisualCondition(); - - } // end if - - // mark the bit according to our health. (if our AI is dead but our health improves, it will - // still re-flag this bit in the AIDeadState every frame.) - getObject()->setEffectivelyDead(m_currentHealth <= 0); - -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBody::internalAddSubdualDamage( Real delta ) -{ - const ActiveBodyModuleData *data = getActiveBodyModuleData(); - - m_currentSubdualDamage += delta; - m_currentSubdualDamage = min(m_currentSubdualDamage, data->m_subdualDamageCap); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Bool ActiveBody::canBeSubdued() const -{ - // Any body with subdue listings can be subdued. - return getActiveBodyModuleData()->m_subdualDamageCap > 0; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBody::onSubdualChange( Bool isNowSubdued ) -{ - if( !getObject()->isKindOf(KINDOF_PROJECTILE) ) - { - Object *me = getObject(); - - if( isNowSubdued ) - { - me->setDisabled(DISABLED_SUBDUED); - - ContainModuleInterface *contain = me->getContain(); - if ( contain ) - contain->orderAllPassengersToIdle( CMD_FROM_AI ); - - } - else - { - me->clearDisabled(DISABLED_SUBDUED); - - if( me->isKindOf( KINDOF_FS_INTERNET_CENTER ) ) - { - //Kris: October 20, 2003 - Patch 1.01 - //Any unit inside an internet center is a hacker! Order - //them to start hacking again. - ContainModuleInterface *contain = me->getContain(); - if ( contain ) - contain->orderAllPassengersToHackInternet( CMD_FROM_AI ); - } - } - } - else if( isNowSubdued )// There is no coming back from being jammed, and projectiles can't even heal, but this makes it clear. - { - ProjectileUpdateInterface *pui = getObject()->getProjectileUpdateInterface(); - if( pui ) - { - pui->projectileNowJammed(); - } - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Bool ActiveBody::isSubdued() const -{ - return m_maxHealth <= m_currentSubdualDamage; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Real ActiveBody::getHealth() const -{ - return m_currentHealth; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -BodyDamageType ActiveBody::getDamageState() const -{ - return m_curDamageState; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Real ActiveBody::getMaxHealth() const -{ - return m_maxHealth; -} ///< return max health - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -UnsignedInt ActiveBody::getSubdualDamageHealRate() const -{ - return getActiveBodyModuleData()->m_subdualDamageHealRate; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Real ActiveBody::getSubdualDamageHealAmount() const -{ - return getActiveBodyModuleData()->m_subdualDamageHealAmount; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Bool ActiveBody::hasAnySubdualDamage() const -{ - return m_currentSubdualDamage > 0; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Real ActiveBody::getInitialHealth() const -{ - return m_initialHealth; -} // return initial health - - -// ------------------------------------------------------------------------------------------------ -/** Set or unset the overridable indestructible flag in the body */ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::setIndestructible( Bool indestructible ) -{ - - m_indestructible = indestructible; - - // for bridges, we mirror this state on its towers - Object *us = getObject(); - if( us->isKindOf( KINDOF_BRIDGE ) ) - { - BridgeBehaviorInterface *bbi = BridgeBehavior::getBridgeBehaviorInterfaceFromObject( us ); - if( bbi ) - { - Object *tower; - - // get tower - for( Int i = 0; i < BRIDGE_MAX_TOWERS; ++i ) - { - - tower = TheGameLogic->findObjectByID( bbi->getTowerID( BridgeTowerType(i) ) ); - if( tower ) - { - BodyModuleInterface *body = tower->getBodyModule(); - - if( body ) - body->setIndestructible( indestructible ); - - } // end if - - } // end for, i - - } // end if - - } // end if - -} // end setIndestructible - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBody::onVeterancyLevelChanged( VeterancyLevel oldLevel, VeterancyLevel newLevel, Bool provideFeedback ) -{ - if (oldLevel == newLevel) - return; - - if (oldLevel < newLevel) - { - if( provideFeedback ) - { - AudioEventRTS veterancyChanged; - switch (newLevel) - { - case LEVEL_VETERAN: - veterancyChanged = *getObject()->getTemplate()->getSoundPromotedVeteran(); - break; - case LEVEL_ELITE: - veterancyChanged = *getObject()->getTemplate()->getSoundPromotedElite(); - break; - case LEVEL_HEROIC: - veterancyChanged = *getObject()->getTemplate()->getSoundPromotedHero(); - break; - } - - veterancyChanged.setObjectID(getObject()->getID()); - TheAudio->addAudioEvent(&veterancyChanged); - } - - //Also mark the UI dirty -- incase the object is selected or contained. - Object *obj = getObject(); - Drawable *draw = TheInGameUI->getFirstSelectedDrawable(); - if( draw ) - { - Object *checkOwner = draw->getObject(); - if( checkOwner == obj ) - { - //Our selected object has been promoted! - TheControlBar->markUIDirty(); - } - else - { - const Object *containedBy = obj->getContainedBy(); - if( containedBy && TheInGameUI->getSelectCount() == 1 ) - { - Object *checkOwner = draw->getObject(); - if( checkOwner == containedBy ) - { - //But only if the contained by object is containing me! - TheControlBar->markUIDirty(); - } - } - } - } - } - - Real oldBonus = TheGlobalData->m_healthBonus[oldLevel]; - Real newBonus = TheGlobalData->m_healthBonus[newLevel]; - Real mult = newBonus / oldBonus; - - // get this before calling setMaxHealth, since it can clip curHealth - //Real newHealth = m_currentHealth * mult; - - // change the max - setMaxHealth(m_maxHealth * mult, PRESERVE_RATIO ); - - // now change the cur (setMaxHealth now handles it) - //internalChangeHealth( newHealth - m_currentHealth ); - - switch (newLevel) - { - case LEVEL_REGULAR: - clearArmorSetFlag(ARMORSET_VETERAN); - clearArmorSetFlag(ARMORSET_ELITE); - clearArmorSetFlag(ARMORSET_HERO); - break; - case LEVEL_VETERAN: - setArmorSetFlag(ARMORSET_VETERAN); - clearArmorSetFlag(ARMORSET_ELITE); - clearArmorSetFlag(ARMORSET_HERO); - break; - case LEVEL_ELITE: - clearArmorSetFlag(ARMORSET_VETERAN); - setArmorSetFlag(ARMORSET_ELITE); - clearArmorSetFlag(ARMORSET_HERO); - break; - case LEVEL_HEROIC: - clearArmorSetFlag(ARMORSET_VETERAN); - clearArmorSetFlag(ARMORSET_ELITE); - setArmorSetFlag(ARMORSET_HERO); - break; - } -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::setAflame( Bool ) -{ - - // - // All this does now is act like a major body state change. It is called after Aflame has been - // set or cleared as an Object Status - // - updateBodyParticleSystems(); - -} -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::overrideDamageFX(DamageFX* damageFX) -{ - if (damageFX != NULL) { - m_curDamageFX = damageFX; - m_damageFXOverride = true; - } - else { - m_curDamageFX = NULL; - m_damageFXOverride = false; - - // Restore DamageFX from current armorset - const ArmorTemplateSet* set = getObject()->getTemplate()->findArmorTemplateSet(m_curArmorSetFlags); - if (set) - { - m_curDamageFX = set->getDamageFX(); - } - } - DEBUG_LOG((">>>ActiveBody: overrideDamageFX - new m_curDamageFX = %d, m_damageFXOverride = %d\n", - m_curDamageFX, m_damageFXOverride)); -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::crc( Xfer *xfer ) -{ - - // extend base class - BodyModule::crc( xfer ); - -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::xfer( Xfer *xfer ) -{ - - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // base class - BodyModule::xfer( xfer ); - - // current health - xfer->xferReal( &m_currentHealth ); - - xfer->xferReal( &m_currentSubdualDamage ); - - // previous health - xfer->xferReal( &m_prevHealth ); - - // max health - xfer->xferReal( &m_maxHealth ); - - // initial health - xfer->xferReal( &m_initialHealth ); - - // current damage state - xfer->xferUser( &m_curDamageState, sizeof( BodyDamageType ) ); - - // next damage fx time - xfer->xferUnsignedInt( &m_nextDamageFXTime ); - - // last damage fx done - xfer->xferUser( &m_lastDamageFXDone, sizeof( DamageType ) ); - - // last damage info - xfer->xferSnapshot( &m_lastDamageInfo ); - - // last damage timestamp - xfer->xferUnsignedInt( &m_lastDamageTimestamp ); - - // last damage timestamp - xfer->xferUnsignedInt( &m_lastHealingTimestamp ); - - // front crushed - xfer->xferBool( &m_frontCrushed ); - - // back crushed - xfer->xferBool( &m_backCrushed ); - - // last damaged cleared - xfer->xferBool( &m_lastDamageCleared ); - - // indestructible - xfer->xferBool( &m_indestructible ); - - // particle system count - BodyParticleSystem *system; - UnsignedShort particleSystemCount = 0; - for( system = m_particleSystems; system; system = system->m_next ) - particleSystemCount++; - xfer->xferUnsignedShort( &particleSystemCount ); - - // particle systems - if( xfer->getXferMode() == XFER_SAVE ) - { - - // walk the particle systems - for( system = m_particleSystems; system; system = system->m_next ) - { - - // write particle system ID - xfer->xferUser( &system->m_particleSystemID, sizeof( ParticleSystemID ) ); - - } // end for, system - - } // end if, save - else - { - ParticleSystemID particleSystemID; - - // the list should be empty at this time - if( m_particleSystems != NULL ) - { - - DEBUG_CRASH(( "ActiveBody::xfer - m_particleSystems should be empty, but is not\n" )); - throw SC_INVALID_DATA; - - } // end if - - // read all data elements - BodyParticleSystem *newEntry; - for( UnsignedShort i = 0; i < particleSystemCount; ++i ) - { - - // read particle system ID - xfer->xferUser( &particleSystemID, sizeof( ParticleSystemID ) ); - - // allocate entry and add to list - newEntry = newInstance(BodyParticleSystem); - newEntry->m_particleSystemID = particleSystemID; - newEntry->m_next = m_particleSystems; // the list will be reversed, but we don't care - m_particleSystems = newEntry; - - } // end for, i - - } // end else, load - - // armor set flags - m_curArmorSetFlags.xfer( xfer ); - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::loadPostProcess( void ) -{ - - // extend base class - BodyModule::loadPostProcess(); - -} // end loadPostProcess +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: ActiveBody.cpp /////////////////////////////////////////////////////////////////////////// +// Author: Colin Day, November 2001 +// Desc: Active bodies have health, they can die and are affected by health +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine +#include "Common/BitFlagsIO.h" +#include "Common/CRCDebug.h" +#include "Common/DamageFX.h" +#include "Common/Player.h" +#include "Common/GameState.h" +#include "Common/GlobalData.h" +#include "Common/PlayerList.h" +#include "Common/Team.h" +#include "Common/Thing.h" +#include "Common/ThingTemplate.h" +#include "Common/Xfer.h" +#include "GameClient/ControlBar.h" +#include "GameClient/Drawable.h" +#include "GameClient/InGameUI.h" +#include "GameClient/ParticleSys.h" +#include "GameLogic/AI.h" +#include "GameLogic/AIPathfind.h" +#include "GameLogic/Armor.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/Object.h" +#include "GameLogic/Damage.h" +#include "GameLogic/PartitionManager.h" +#include "GameLogic/TerrainLogic.h" +#include "GameLogic/Weapon.h" +#include "GameLogic/Module/AIUpdate.h" +#include "GameLogic/Module/ActiveBody.h" +#include "GameLogic/Module/BridgeBehavior.h" +#include "GameLogic/Module/ContainModule.h" +#include "GameLogic/Module/DamageModule.h" +#include "GameLogic/Module/DieModule.h" + + +#ifdef RTS_INTERNAL +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +#define YELLOW_DAMAGE_PERCENT (0.25f) + +// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// + +// ------------------------------------------------------------------------------------------------ +/** Body particle systems are particle systems that are automatically created and attached + * to an object as the damage state changes for that object. We keep a list of these + * so that when we transition from one state to another we can kill any old particle + * systems that we need to before we create new ones */ +// ------------------------------------------------------------------------------------------------ +class BodyParticleSystem : public MemoryPoolObject +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( BodyParticleSystem, "BodyParticleSystem" ) + +public: + + ParticleSystemID m_particleSystemID; ///< the particle system ID + BodyParticleSystem *m_next; ///< next particle system in this body module + +}; + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +BodyParticleSystem::~BodyParticleSystem( void ) +{ + +} // end ~BodyParticleSystem + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------ +static BodyDamageType calcDamageState(Real health, Real maxHealth) +{ + if (!TheGlobalData) + return BODY_PRISTINE; + + Real ratio = health / maxHealth; + + if (ratio > TheGlobalData->m_unitDamagedThresh) + { + return BODY_PRISTINE; + } + else if (ratio > TheGlobalData->m_unitReallyDamagedThresh) + { + return BODY_DAMAGED; + } + else if (ratio > 0.0f) + { + return BODY_REALLYDAMAGED; + } + else + { + return BODY_RUBBLE; + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ActiveBodyModuleData::ActiveBodyModuleData() +{ + m_maxHealth = 0; + m_initialHealth = 0; + m_subdualDamageCap = 0; + m_subdualDamageHealRate = 0; + m_subdualDamageHealAmount = 0; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBodyModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + ModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + { "MaxHealth", INI::parseReal, NULL, offsetof( ActiveBodyModuleData, m_maxHealth ) }, + { "InitialHealth", INI::parseReal, NULL, offsetof( ActiveBodyModuleData, m_initialHealth ) }, + + { "SubdualDamageCap", INI::parseReal, NULL, offsetof( ActiveBodyModuleData, m_subdualDamageCap ) }, + { "SubdualDamageHealRate", INI::parseDurationUnsignedInt, NULL, offsetof( ActiveBodyModuleData, m_subdualDamageHealRate ) }, + { "SubdualDamageHealAmount", INI::parseReal, NULL, offsetof( ActiveBodyModuleData, m_subdualDamageHealAmount ) }, + { 0, 0, 0, 0 } + }; + p.add(dataFieldParse); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ActiveBody::ActiveBody( Thing *thing, const ModuleData* moduleData ) : + BodyModule(thing, moduleData), + m_curDamageFX(NULL), + m_curArmorSet(NULL), + m_frontCrushed(false), + m_backCrushed(false), + m_lastDamageTimestamp(0xffffffff),// So we don't think we just got damaged on the first frame + m_lastHealingTimestamp(0xffffffff),// So we don't think we just got healed on the first frame + m_curDamageState(BODY_PRISTINE), + m_nextDamageFXTime(0), + m_lastDamageFXDone((DamageType)-1), + m_lastDamageCleared(false), + m_particleSystems(NULL), + m_currentSubdualDamage(0), + m_indestructible(false), + m_damageFXOverride(false) +{ + m_currentHealth = getActiveBodyModuleData()->m_initialHealth; + m_prevHealth = getActiveBodyModuleData()->m_initialHealth; + m_maxHealth = getActiveBodyModuleData()->m_maxHealth; + m_initialHealth = getActiveBodyModuleData()->m_initialHealth; + + // force an initially-valid armor setup + validateArmorAndDamageFX(); + // start us in the right state + setCorrectDamageState(); + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ActiveBody::~ActiveBody( void ) +{ +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::onDelete( void ) +{ + + // delete all particle systems + deleteAllParticleSystems(); + +} // end onDelete + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::setCorrectDamageState() +{ + m_curDamageState = calcDamageState(m_currentHealth, m_maxHealth); + + /// @todo srj -- bleah, this is an icky way to do it. oh well. + if (m_curDamageState == BODY_RUBBLE && getObject()->isKindOf(KINDOF_STRUCTURE)) + { + Real rubbleHeight = getObject()->getTemplate()->getStructureRubbleHeight(); + + if (rubbleHeight <= 0.0f) + rubbleHeight = TheGlobalData->m_defaultStructureRubbleHeight; + + /** @todo I had to change this to a Z only version to keep it from disappearing from the + PartitionManager for a frame. That didn't used to happen. + */ + getObject()->setGeometryInfoZ(rubbleHeight); + + // Have to tell pathfind as well, as rubble pathfinds differently. + TheAI->pathfinder()->removeObjectFromPathfindMap(getObject()); + TheAI->pathfinder()->addObjectToPathfindMap(getObject()); + + + // here we make sure nobody collides with us, ever again... //Lorenzen + //THis allows projectiles shot from infantry that are inside rubble to get out of said rubble safely + getObject()->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_COLLISIONS ) ); + + + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::setDamageState( BodyDamageType newState ) +{ + Real ratio = 1.0f; + if( newState == BODY_PRISTINE ) + { + ratio = 1.0f; + } + else if( newState == BODY_DAMAGED ) + { + ratio = TheGlobalData->m_unitDamagedThresh; + } + else if( newState == BODY_REALLYDAMAGED ) + { + ratio = TheGlobalData->m_unitReallyDamagedThresh; + } + else if( newState == BODY_RUBBLE ) + { + ratio = 0.0f; + } + Real desiredHealth = m_maxHealth * ratio - 1;// -1 because < not <= in calcState + desiredHealth = max( desiredHealth, 0.0f ); + internalChangeHealth( desiredHealth - m_currentHealth ); + setCorrectDamageState(); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::validateArmorAndDamageFX() const +{ + const ArmorTemplateSet* set = getObject()->getTemplate()->findArmorTemplateSet(m_curArmorSetFlags); + DEBUG_ASSERTCRASH(set, ("findArmorSet should never return null")); + if (set && set != m_curArmorSet) + { + if (set->getArmorTemplate()) + { + m_curArmor = TheArmorStore->makeArmor(set->getArmorTemplate()); + } + else + { + m_curArmor.clear(); + } + if (!m_damageFXOverride) m_curDamageFX = set->getDamageFX(); // Only set this if override is cleared + m_curArmorSet = set; + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Real ActiveBody::estimateDamage( DamageInfoInput& damageInfo ) const +{ + validateArmorAndDamageFX(); + + //Subdual damage can't affect you if you can't be subdued + if( IsSubdualDamage(damageInfo.m_damageType) && !canBeSubdued() ) + return 0.0f; + + if( damageInfo.m_damageType == DAMAGE_KILL_GARRISONED ) + { + ContainModuleInterface* contain = getObject()->getContain(); + if( contain && contain->getContainCount() > 0 && contain->isGarrisonable() && !contain->isImmuneToClearBuildingAttacks() ) + return 1.0f; + else + return 0.0f; + } + + if( damageInfo.m_damageType == DAMAGE_SNIPER ) + { + if( getObject()->isKindOf( KINDOF_STRUCTURE ) && getObject()->testStatus( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) + { + //If we're a pathfinder shooting a stinger site under construction... don't. Special case code. + return 0.0f; + } + } + + Real amount = m_curArmor.adjustDamage(damageInfo.m_damageType, damageInfo.m_amount); + + return amount; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::doDamageFX( const DamageInfo *damageInfo ) +{ + DamageType damageTypeToUse = damageInfo->in.m_damageType; + if (damageInfo->in.m_damageFXOverride != DAMAGE_UNRESISTABLE ) + { + // Just the visual aspect of damage can be overridden in some cases. + // Unresistable is the default to mean no override, as we are out of bits. + damageTypeToUse = damageInfo->in.m_damageFXOverride; + } + + if (m_curDamageFX) + { + UnsignedInt now = TheGameLogic->getFrame(); + if (damageTypeToUse == m_lastDamageFXDone && m_nextDamageFXTime > now) + return; + Object *source = TheGameLogic->findObjectByID(damageInfo->in.m_sourceID); // might be null, I guess + m_lastDamageFXDone = damageTypeToUse; + m_nextDamageFXTime = now + m_curDamageFX->getDamageFXThrottleTime(damageTypeToUse, source); + m_curDamageFX->doDamageFX(damageTypeToUse, damageInfo->out.m_actualDamageDealt, source, getObject()); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::attemptDamage( DamageInfo *damageInfo ) +{ + validateArmorAndDamageFX(); + + // sanity + if( damageInfo == NULL ) + return; + + if ( m_indestructible ) + return; + + // initialize these, just in case we bail out early + damageInfo->out.m_actualDamageDealt = 0.0f; + damageInfo->out.m_actualDamageClipped = 0.0f; + + // we cannot damage again objects that are already dead + Object* obj = getObject(); + if( obj->isEffectivelyDead() ) + return; + + Object *damager = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID ); + if( damager ) + { + //Store the template so later if the attacking object dies, we use script conditions to look at the + //damager's template inside evaluateTeamAttackedByType or evaluateNameAttackedByType. + damageInfo->in.m_sourceTemplate = damager->getTemplate(); + } + + Bool alreadyHandled = FALSE; + Bool allowModifier = TRUE; + Real amount = m_curArmor.adjustDamage(damageInfo->in.m_damageType, damageInfo->in.m_amount); + + switch( damageInfo->in.m_damageType ) + { + case DAMAGE_HEALING: + { + if( !damageInfo->in.m_kill ) + { + // Healing and Damage are separate, so this shouldn't happen + attemptHealing( damageInfo ); + } + return; + } + + case DAMAGE_KILLPILOT: + { + // This type of damage doesn't actually damage the unit, but it does kill it's + // pilot, in the case of a vehicle. + if( obj->isKindOf( KINDOF_VEHICLE ) ) + { + //Handle special case for combat bike. We actually will kill the bike by + //forcing the rider to leave the bike. That way the bike will automatically + //scuttle and be unusable. + ContainModuleInterface *contain = obj->getContain(); + if( contain && contain->isRiderChangeContain() ) + { + + AIUpdateInterface *ai = obj->getAI(); + + if( ai->isMoving() ) + { + //Bike is moving, so just blow it up instead. + if (damager) + damager->scoreTheKill( obj ); + obj->kill(); + } + else + { + //Removing the rider will scuttle the bike. + Object *rider = *(contain->getContainedItemsList()->begin()); + ai->aiEvacuateInstantly( TRUE, CMD_FROM_AI ); + + //Kill the rider. + if (damager) + damager->scoreTheKill( rider ); + rider->kill(); + } + } + else + { + // Make it unmanned, so units can easily check the ability to "take control of it" + obj->setDisabled( DISABLED_UNMANNED ); + TheGameLogic->deselectObject(obj, PLAYERMASK_ALL, TRUE); + + if ( obj->getAI() ) + obj->getAI()->aiIdle( CMD_FROM_AI ); + + // Convert it to the neutral team so it renders gray giving visual representation that it is unmanned. + obj->setTeam( ThePlayerList->getNeutralPlayer()->getDefaultTeam() ); + } + + //We don't care which team sniped the vehicle... we use this information to flag whether or not + //we captured a vehicle. + ThePlayerList->getNeutralPlayer()->getAcademyStats()->recordVehicleSniped(); + } + alreadyHandled = TRUE; + allowModifier = FALSE; + break; + } + + case DAMAGE_KILL_GARRISONED: + { + // KRIS: READ THIS!!! + // This code is very misleading (but in a good way). One would think this is + // an excellent place to add the hook to kill garrisoned troops. And that is + // a correct assumption. Unfortunately, the vast majority of garrison slayings + // are performed in DumbProjectileBehavior::projectileHandleCollision(), so my + // hope is that this message will save you some research time! + + Int killsToMake = REAL_TO_INT_FLOOR(damageInfo->in.m_amount); + ContainModuleInterface* contain = obj->getContain(); + if( contain && contain->getContainCount() > 0 && contain->isGarrisonable() && !contain->isImmuneToClearBuildingAttacks() ) + { + Int numKilled = 0; + + // garrisonable buildings subvert the normal process here. + const ContainedItemsList* items = contain->getContainedItemsList(); + if (items) + { + for( ContainedItemsList::const_iterator it = items->begin(); (it != items->end()) && (numKilled < killsToMake); it++ ) + { + Object* thingToKill = *it; + if (!thingToKill->isEffectivelyDead() ) + { + if (damager) + damager->scoreTheKill( thingToKill ); + thingToKill->kill(); + ++numKilled; + thingToKill->getControllingPlayer()->getAcademyStats()->recordClearedGarrisonedBuilding(); + } + } // next contained item + + } // if items + } // if a garrisonable thing + alreadyHandled = TRUE; + allowModifier = FALSE; + break; + } + + case DAMAGE_STATUS: + { + // Damage amount is msec time we set the status given in damageStatusType + Real realFramesToStatusFor = ConvertDurationFromMsecsToFrames(amount); + obj->doStatusDamage( damageInfo->in.m_damageStatusType , REAL_TO_INT_CEIL(realFramesToStatusFor) ); + alreadyHandled = TRUE; + allowModifier = FALSE; + break; + } + + case DAMAGE_CHRONO_GUN: + case DAMAGE_CHRONO_UNRESISTABLE: + { + // This handles both gaining chrono damage and recovering from it + + // Note: Should HoldTheLine or Shields apply? (Not for recovery) + if (damageInfo->in.m_damageType != DAMAGE_CHRONO_UNRESISTABLE) { + amount *= m_damageScalar; + } + + Bool wasSubdued = isSubduedChrono(); + + // Increase damage counter + internalAddChronoDamage(amount); + DEBUG_LOG(("ActiveBody::attemptDamage - amount = %f, chronoDmg = %f\n", amount, getCurrentChronoDamageAmount())); + + // Check for disabling threshold + Bool nowSubdued = isSubduedChrono(); + + if (wasSubdued != nowSubdued) + { + // Enable/Disable ; Apply/Remove Visual Effects + onSubdualChronoChange(nowSubdued); + } + + // This will handle continuous art changes such as transparency + getObject()->notifyChronoDamage(amount); + + // Check kill state: + if (getCurrentChronoDamageAmount() > getMaxHealth()) { + damageInfo->in.m_kill = TRUE; + } + else { + alreadyHandled = TRUE; + } + allowModifier = FALSE; + } + } + + if( IsSubdualDamage(damageInfo->in.m_damageType) ) + { + if( !canBeSubdued() ) + return; + + Bool wasSubdued = isSubdued(); + internalAddSubdualDamage(amount); + Bool nowSubdued = isSubdued(); + alreadyHandled = TRUE; + allowModifier = FALSE; + + if( wasSubdued != nowSubdued ) + { + onSubdualChange(nowSubdued); + } + + getObject()->notifySubdualDamage(amount); + } + + if (allowModifier) + { + if( damageInfo->in.m_damageType != DAMAGE_UNRESISTABLE ) + { + // Apply the damage scalar (extra bonuses -- like strategy center defensive battle plan) + // And remember not to adjust unresistable damage, just like the armor code can't. + amount *= m_damageScalar; + } + } + + // sanity check the damage value -- can't apply negative damage + if( amount > 0.0f || damageInfo->in.m_kill ) + { + BodyDamageType oldState = m_curDamageState; + + //If the object is going to die, make sure we damage all remaining health. + if( damageInfo->in.m_kill ) + { + amount = m_currentHealth; + } + + if (!alreadyHandled) + { + // do the damage simplistic damage subtraction + internalChangeHealth( -amount ); + } + +#ifdef ALLOW_SURRENDER +//***************************************************************************************** +//***************************************************************************************** +//THIS CODE HAS BEEN DISABLED FOR THE MULTIPLAYER PLAY TEST!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!** +//***************************************************************************************** +// // if we were "killed" by surrender damage... +// if (damageInfo->in.m_damageType == DAMAGE_SURRENDER && m_currentHealth <= 0.0f && obj->isKindOf(KINDOF_CAN_SURRENDER)) +// { +// AIUpdateInterface* ai = obj->getAIUpdateInterface(); +// if (ai) +// { +// // do no damage, but make it surrender instead. +// m_currentHealth = m_prevHealth; +// const Object* killer = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID ); +// ai->setSurrendered(killer, true); +// return; +// } +// } +//***************************************************************************************** +//***************************************************************************************** +#endif + + // record the actual damage done from this, and when it happened + damageInfo->out.m_actualDamageDealt = amount; + damageInfo->out.m_actualDamageClipped = m_prevHealth - m_currentHealth; + + // then copy the whole DamageInfo struct for easy lookup + // (object pointer loses scope as soon as atteptdamage's caller ends) + // m_lastDamageTimestamp is initialized to FFFFFFFFFF, so doing a < compare is problematic. + // jba. + if (m_lastDamageTimestamp!=TheGameLogic->getFrame() && m_lastDamageTimestamp != TheGameLogic->getFrame()-1) { + m_lastDamageInfo = *damageInfo; + m_lastDamageCleared = false; + m_lastDamageTimestamp = TheGameLogic->getFrame(); + } else { + // Multiple damages applied in one/next frame. We prefer the one that tells who the attacker is. + Object *srcObj1 = TheGameLogic->findObjectByID(m_lastDamageInfo.in.m_sourceID); + Object *srcObj2 = TheGameLogic->findObjectByID(damageInfo->in.m_sourceID); + if (srcObj2) { + if (srcObj1) { + if (srcObj2->isKindOf(KINDOF_VEHICLE) || srcObj2->isKindOf(KINDOF_INFANTRY) || + srcObj2->isFactionStructure()) { + m_lastDamageInfo = *damageInfo; + m_lastDamageCleared = false; + m_lastDamageTimestamp = TheGameLogic->getFrame(); + } + } else { + m_lastDamageInfo = *damageInfo; + m_lastDamageCleared = false; + m_lastDamageTimestamp = TheGameLogic->getFrame(); + } + + } else { + // no change. + } + } + + // Notify the player that they have been attacked by this player + if (m_lastDamageInfo.in.m_sourceID != INVALID_ID) + { + Object *srcObj = TheGameLogic->findObjectByID(m_lastDamageInfo.in.m_sourceID); + if (srcObj) + { + Player *srcPlayer = srcObj->getControllingPlayer(); + obj->getControllingPlayer()->setAttackedBy(srcPlayer->getPlayerIndex()); + } + } + + // if our health has gone down then do run the damage module callback + if( m_currentHealth < m_prevHealth ) + { + for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m) + { + DamageModuleInterface* d = (*m)->getDamage(); + if (!d) + continue; + + d->onDamage( damageInfo ); + } + } + + if (m_curDamageState != oldState) + { + for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m) + { + DamageModuleInterface* d = (*m)->getDamage(); + if (!d) + continue; + + d->onBodyDamageStateChange( damageInfo, oldState, m_curDamageState ); + } + + // @todo: This really feels like it should be in the TransitionFX lists. + if (m_curDamageState == BODY_DAMAGED) + { + AudioEventRTS damaged = *obj->getTemplate()->getSoundOnDamaged(); + damaged.setObjectID(obj->getID()); + TheAudio->addAudioEvent(&damaged); + } + else if (m_curDamageState == BODY_REALLYDAMAGED) + { + AudioEventRTS reallyDamaged = *obj->getTemplate()->getSoundOnReallyDamaged(); + reallyDamaged.setObjectID(obj->getID()); + TheAudio->addAudioEvent(&reallyDamaged); + } + + } + + // Should we play our fear sound? + if( (m_prevHealth / m_maxHealth) > YELLOW_DAMAGE_PERCENT && + (m_currentHealth / m_maxHealth) < YELLOW_DAMAGE_PERCENT && + (m_currentHealth > 0) ) + { + // 25% chance to play + if (GameLogicRandomValue(0, 99) < 25) + { + AudioEventRTS fearSound = *obj->getTemplate()->getVoiceFear(); + fearSound.setPosition( obj->getPosition() ); + fearSound.setPlayerIndex( obj->getControllingPlayer()->getPlayerIndex() ); + TheAudio->addAudioEvent(&fearSound); + } + } + + // check to see if we died + if( m_currentHealth <= 0 && m_prevHealth > 0 ) + { + // Give our killer credit for killing us, if there is one. + if( damager ) + { + damager->scoreTheKill( obj ); + } + + obj->onDie( damageInfo ); + } + } + + doDamageFX(damageInfo); + + // Damaged repulsable civilians scare (repulse) other civs. jba. + if( TheAI->getAiData()->m_enableRepulsors ) + { + if( obj->isKindOf( KINDOF_CAN_BE_REPULSED ) ) + { + obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_REPULSOR ) ); + } + } + + //Retaliate, even if I'm dead -- we'll still get my nearby friends to get revenge!!! + //Also only retaliate if we're controlled by a human player and the thing that attacked me + //is an enemy. + Player *controllingPlayer = obj->getControllingPlayer(); + if( controllingPlayer && controllingPlayer->isLogicalRetaliationModeEnabled() && controllingPlayer->getPlayerType() == PLAYER_HUMAN ) + { + if( shouldRetaliateAgainstAggressor(obj, damager)) + { + PartitionFilterPlayerAffiliation f1( controllingPlayer, ALLOW_ALLIES, true ); + PartitionFilterOnMap filterMapStatus; + PartitionFilter *filters[] = { &f1, &filterMapStatus, 0 }; + + + Real distance = TheAI->getAiData()->m_retaliateFriendsRadius + obj->getGeometryInfo().getBoundingCircleRadius(); + SimpleObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( obj->getPosition(), distance, FROM_CENTER_2D, filters, ITER_FASTEST ); + MemoryPoolObjectHolder hold( iter ); + for( Object *them = iter->first(); them; them = iter->next() ) + { + if (!shouldRetaliate(them)) { + continue; + } + AIUpdateInterface *ai = them->getAI(); + if (ai==NULL) { + continue; + } + //If we have AI and we're mobile, then assist! + if( !them->isKindOf( KINDOF_IMMOBILE )) + { + //But only if we can attack it! + CanAttackResult result = them->getAbleToAttackSpecificObject( ATTACK_NEW_TARGET, damager, CMD_FROM_AI ); + if( result == ATTACKRESULT_POSSIBLE_AFTER_MOVING || result == ATTACKRESULT_POSSIBLE ) + { + ai->aiGuardRetaliate( damager, them->getPosition(), NO_MAX_SHOTS_LIMIT, CMD_FROM_AI ); + } + } + } + } + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool ActiveBody::shouldRetaliateAgainstAggressor(Object *obj, Object *damager) +{ + /* This considers whether obj should invoke his friends to retaliate against damager. + Note that obj could be a structure, so we don't actually check whether obj will + retaliate, as in many cases he wouldn't. */ + if (damager==NULL) { + return false; + } + if (damager->isAirborneTarget()) { + return false; // Don't retaliate against aircraft. [8/25/2003] + } + if (damager->getRelationship( obj ) != ENEMIES) { + return false; // only retaliate against enemies. + } + Real distSqr = ThePartitionManager->getDistanceSquared(obj, damager, FROM_BOUNDINGSPHERE_2D); + if (distSqr > sqr(TheAI->getAiData()->m_maxRetaliateDistance)) { + return false; + } + // Only human players retaliate. [8/25/2003] + if (obj->getControllingPlayer()->getPlayerType() != PLAYER_HUMAN) { + return false; + } + // Drones never retaliate. [8/25/2003] + if (obj->isKindOf(KINDOF_DRONE)) { + return false; + } + return true; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool ActiveBody::shouldRetaliate(Object *obj) +{ + // Cannot retaliate objects dont. [8/25/2003] + if (obj->isKindOf(KINDOF_CANNOT_RETALIATE)) { + return false; + } + if (obj->isKindOf( KINDOF_IMMOBILE )) { + return false; + } + // Drones never retaliate. [8/25/2003] + if (obj->isKindOf(KINDOF_DRONE)) { + return false; + } + // Any unit that isn't idle won't retaliate. [8/25/2003] + if (obj->getAI()) { + if (!obj->getAI()->isIdle()) { + return false; + } + } else { + return false; // Non-ai can't retaliate. [8/26/2003] + } + // Stealthed units don't retaliate unless they're detected. [8/25/2003] + if ( obj->getStatusBits().test( OBJECT_STATUS_STEALTHED ) && + !obj->getStatusBits().test( OBJECT_STATUS_DETECTED ) ) { + return false; + } + // If we're using an ability, don't stop. [8/25/2003] + if (obj->testStatus(OBJECT_STATUS_IS_USING_ABILITY)) { + return false; + } + return true; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::attemptHealing( DamageInfo *damageInfo ) +{ + validateArmorAndDamageFX(); + + // sanity + if( damageInfo == NULL ) + return; + + if( damageInfo->in.m_damageType != DAMAGE_HEALING ) + { + // Healing and Damage are separate, so this shouldn't happen + attemptDamage( damageInfo ); + return; + } + + Object* obj = getObject(); + + // srj sez: sorry, once yer dead, yer dead. + // Special case for bridges, cause the system now things they're dead + ///@todo we need to figure out what has changed so we don't have to hack this (CBD 11-1-2002) + if( obj->isKindOf( KINDOF_BRIDGE ) == FALSE && + obj->isKindOf( KINDOF_BRIDGE_TOWER ) == FALSE && + obj->isEffectivelyDead()) + return; + + // initialize these, just in case we bail out early + damageInfo->out.m_actualDamageDealt = 0.0f; + damageInfo->out.m_actualDamageClipped = 0.0f; + + Real amount = m_curArmor.adjustDamage(damageInfo->in.m_damageType, damageInfo->in.m_amount); + + // sanity check the damage value -- can't apply negative healing + if( amount > 0.0f ) + { + BodyDamageType oldState = m_curDamageState; + + // do the damage simplistic damage ADDITION + internalChangeHealth( amount ); + + // record the actual damage done from this, and when it happened + damageInfo->out.m_actualDamageDealt = amount; + damageInfo->out.m_actualDamageClipped = m_prevHealth - m_currentHealth; + + //then copy the whole DamageInfo struct for easy lookup + //(object pointer loses scope as soon as atteptdamage's caller ends) + m_lastDamageInfo = *damageInfo; + m_lastDamageCleared = false; + m_lastDamageTimestamp = TheGameLogic->getFrame(); + m_lastHealingTimestamp = TheGameLogic->getFrame(); + + // if our health has gone UP then do run the damage module callback + if( m_currentHealth > m_prevHealth ) + { + for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m) + { + DamageModuleInterface* d = (*m)->getDamage(); + if (!d) + continue; + + d->onHealing( damageInfo ); + } + } + + if (m_curDamageState != oldState) + { + for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m) + { + DamageModuleInterface* d = (*m)->getDamage(); + if (!d) + continue; + + d->onBodyDamageStateChange( damageInfo, oldState, m_curDamageState ); + } + } + } + + doDamageFX(damageInfo); +} + +//------------------------------------------------------------------------------------------------- +/** Simple setting of the health value, it does *NOT* track any transition + * states for the event of "damage" or the event of "death". */ +//------------------------------------------------------------------------------------------------- +void ActiveBody::setInitialHealth(Int initialPercent) +{ + + // save the current health as the previous health + m_prevHealth = m_currentHealth; + + Real factor = initialPercent/100.0f; + Real newHealth = factor * m_initialHealth; + + // change the health to the requested percentage. + internalChangeHealth(newHealth - m_currentHealth); + +} + +//------------------------------------------------------------------------------------------------- +/** Simple setting of the health value, it does *NOT* track any transition + * states for the event of "damage" or the event of "death". */ +//------------------------------------------------------------------------------------------------- +void ActiveBody::setMaxHealth( Real maxHealth, MaxHealthChangeType healthChangeType ) +{ + Real prevMaxHealth = m_maxHealth; + m_maxHealth = maxHealth; + m_initialHealth = maxHealth; + + switch( healthChangeType ) + { + case PRESERVE_RATIO: + { + //400/500 (80%) + 100 becomes 480/600 (80%) + //200/500 (40%) - 100 becomes 160/400 (40%) + Real ratio = m_currentHealth / prevMaxHealth; + Real newHealth = maxHealth * ratio; + internalChangeHealth( newHealth - m_currentHealth ); + break; + } + case ADD_CURRENT_HEALTH_TOO: + { + //Add the same amount that we are adding to the max health. + //This could kill you if max health is reduced (if we ever have that ability to add buffer health like in D&D) + //400/500 (80%) + 100 becomes 500/600 (83%) + //200/500 (40%) - 100 becomes 100/400 (25%) + internalChangeHealth( maxHealth - prevMaxHealth ); + break; + } + case SAME_CURRENTHEALTH: + //do nothing + break; + + case FULLY_HEAL: + { + // Set current to the new Max. + //400/500 (80%) + 100 becomes 600/600 (100%) + //200/500 (40%) - 100 becomes 400/400 (100%) + internalChangeHealth(m_maxHealth - m_currentHealth); + break; + } + } + + // + // when max health is getting clipped to a lower value, if our current health + // value is now outside of the max health range we will set it back down to the + // new cap. Note that we are *NOT* going through any healing or damage methods here + // and are doing a direct set + // + if( m_currentHealth > maxHealth ) + { + internalChangeHealth( maxHealth - m_currentHealth ); + } + +} + +// ------------------------------------------------------------------------------------------------ +/** Given the current damage state of the object, evaluate the visual model conditions + * that have a visual impact on the object */ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::evaluateVisualCondition() +{ + + Drawable* draw = getObject()->getDrawable(); + if (draw) + { + draw->reactToBodyDamageStateChange(m_curDamageState); + } + + // + // destroy any particle systems that were attached to our body for the old state + // and create new particle systems for the new state + // + updateBodyParticleSystems(); + +} + +// ------------------------------------------------------------------------------------------------ +/** Create up to maxSystems particle systems of type particleSystemName and attach to bones + * specified by the bone base name. If there are more bones than maxSystems then the + * bones will be randomly selected */ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::createParticleSystems( const AsciiString &boneBaseName, + const ParticleSystemTemplate *systemTemplate, + Int maxSystems ) +{ + Object *us = getObject(); + + // sanity + if( systemTemplate == NULL ) + return; + + // get the bones + enum { MAX_BONES = 16 }; + Coord3D bonePositions[ MAX_BONES ]; + Int numBones = us->getMultiLogicalBonePosition( boneBaseName.str(), + MAX_BONES, + bonePositions, + NULL, + FALSE ); + + // if no bones found nothing else to do + if( numBones == 0 ) + return; + + // + // if we don't have enough bones to go up to maxSystems, we will change maxSystems to be + // the number of bones we actually have (we don't want systems doubling up on bones) + // + if( numBones < maxSystems ) + maxSystems = numBones; + + // + // create an array that we'll use to mark which bone positions have already been used, + // this is necessary when we have more bones than particle systems we're going to + // create, in which case we place the particle systems at random bone locations + // but don't want to repeat any + // + Bool usedBoneIndices[ MAX_BONES ] = { FALSE }; + + // create the particle systems + const Coord3D *pos; + for( Int i = 0; i < maxSystems; ++i ) + { + + // pick a bone index to place this particle system at + // MDC: moving to GameLogicRandomValue. This does not need to be synced, but having it so makes searches *so* much nicer. + // DTEH: Moved back to GameClientRandomValue because of desync problems. July 27th 2003. + Int boneIndex = GameClientRandomValue( 0, maxSystems - i - 1 ); + + // find the actual bone location to use and mark that bone index as used + Int count = 0; + Int j = 0; + for( ; j < numBones; j++ ) + { + + // ignore bone positions that have already been used + if( usedBoneIndices[ j ] == TRUE ) + continue; + + // this spot is available, if count == boneIndex then use this index + if( count == boneIndex ) + { + + pos = &bonePositions[ j ]; + usedBoneIndices[ j ] = TRUE; + break; // exit for j + + } // end if + else + { + + // we won't use this index, increment count until we find a suitable index to use + ++count; + + } // end else + + } // end for, j + + // sanity + DEBUG_ASSERTCRASH( j != numBones, + ("ActiveBody::createParticleSystems, Unable to select particle system index\n") ); + + // create particle system here + ParticleSystem *particleSystem = TheParticleSystemManager->createParticleSystem( systemTemplate ); + if( particleSystem ) + { + + // set the position of the particle system in local object space + particleSystem->setPosition( pos ); + + // attach particle system to object + particleSystem->attachToObject( us ); + + // create a new body particle system entry and keep this particle system in it + BodyParticleSystem *newEntry = newInstance(BodyParticleSystem); + newEntry->m_particleSystemID = particleSystem->getSystemID(); + newEntry->m_next = m_particleSystems; + m_particleSystems = newEntry; + + } // end if + + } // end for, i + +} // end createParticleSystems + +// ------------------------------------------------------------------------------------------------ +/** Delete all the body particle systems */ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::deleteAllParticleSystems( void ) +{ + BodyParticleSystem *nextBodySystem; + ParticleSystem *particleSystem; + + while( m_particleSystems ) + { + + // get this particle system + particleSystem = TheParticleSystemManager->findParticleSystem( m_particleSystems->m_particleSystemID ); + if( particleSystem ) + particleSystem->destroy(); + + // get next system in the body + nextBodySystem = m_particleSystems->m_next; + + // destroy this entry + m_particleSystems->deleteInstance(); + + // set the body systems head to the next + m_particleSystems = nextBodySystem; + + } // end while + +} // end deleteAllParticleSystems + +// ------------------------------------------------------------------------------------------------ +/* This function is called on state changes only. Body Type or Aflameness. */ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::updateBodyParticleSystems( void ) +{ + static const ParticleSystemTemplate *fireSmallTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoFireParticleSmallSystem ); + static const ParticleSystemTemplate *fireMediumTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoFireParticleMediumSystem ); + static const ParticleSystemTemplate *fireLargeTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoFireParticleLargeSystem ); + static const ParticleSystemTemplate *smokeSmallTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoSmokeParticleSmallSystem ); + static const ParticleSystemTemplate *smokeMediumTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoSmokeParticleMediumSystem ); + static const ParticleSystemTemplate *smokeLargeTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoSmokeParticleLargeSystem ); + static const ParticleSystemTemplate *aflameTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoAflameParticleSystem ); + Int countModifier; + const ParticleSystemTemplate *fireSmall; + const ParticleSystemTemplate *fireMedium; + const ParticleSystemTemplate *fireLarge; + const ParticleSystemTemplate *smokeSmall; + const ParticleSystemTemplate *smokeMedium; + const ParticleSystemTemplate *smokeLarge; + + // + // when we're aflame, we use a slightly different set of particle systems that are + // auto created that lends itself to more fire and bigger fire + // + if( getObject()->testStatus( OBJECT_STATUS_AFLAME ) ) + { + + fireSmall = fireMediumTemplate; // small fire becomes medium fire + fireMedium = fireLargeTemplate; // medium fire becomes large fire + fireLarge = fireLargeTemplate; // large fire stays large + smokeSmall = fireSmallTemplate; // small smoke becomes small fire + smokeMedium = fireSmallTemplate; // medium smoke becomes small fire + smokeLarge = fireSmallTemplate; // large smoke becomes small fire + + // we get to make more of them all too + countModifier = 2; + + } // end if + else + { + + // use regular templates + fireSmall = fireSmallTemplate; + fireMedium = fireMediumTemplate; + fireLarge = fireLargeTemplate; + smokeSmall = smokeSmallTemplate; + smokeMedium = smokeMediumTemplate; + smokeLarge = smokeLargeTemplate; + + // we make just the normal amount of these + countModifier = 1; + + } // end else + + // + // remove any particle systems we have currently in the list in favor of any new ones + // that we're going to autopopulate ourselves with + // + deleteAllParticleSystems(); + + // + // create particle systems for the new body state + // + + // small fire bones + createParticleSystems( TheGlobalData->m_autoFireParticleSmallPrefix, + fireSmall, TheGlobalData->m_autoFireParticleSmallMax * countModifier ); + + // medium fire bones + createParticleSystems( TheGlobalData->m_autoFireParticleMediumPrefix, + fireMedium, TheGlobalData->m_autoFireParticleMediumMax * countModifier ); + + // large fire bones + createParticleSystems( TheGlobalData->m_autoFireParticleLargePrefix, + fireLarge, TheGlobalData->m_autoFireParticleLargeMax * countModifier ); + + // small smoke bones + createParticleSystems( TheGlobalData->m_autoSmokeParticleSmallPrefix, + smokeSmall, TheGlobalData->m_autoSmokeParticleSmallMax * countModifier ); + + // medium smoke bones + createParticleSystems( TheGlobalData->m_autoSmokeParticleMediumPrefix, + smokeMedium, TheGlobalData->m_autoSmokeParticleMediumMax * countModifier ); + + // large smoke bones + createParticleSystems( TheGlobalData->m_autoSmokeParticleLargePrefix, + smokeLarge, TheGlobalData->m_autoSmokeParticleLargeMax * countModifier ); + + // actively on fire + if( getObject()->testStatus( OBJECT_STATUS_AFLAME ) ) + createParticleSystems( TheGlobalData->m_autoAflameParticlePrefix, + aflameTemplate, TheGlobalData->m_autoAflameParticleMax * countModifier ); + +} // end updatebodyParticleSystems + +//------------------------------------------------------------------------------------------------- +/** Simple changing of the health value, it does *NOT* track any transition + * states for the event of "damage" or the event of "death". If you + * with to kill an object and give these modules a chance to react + * to that event use the proper damage method calls. + * No game logic should go in here. This is the low level math and flag maintenance. + * Game stuff goes in attemptDamage and attemptHealing. +*/ +//------------------------------------------------------------------------------------------------- +void ActiveBody::internalChangeHealth( Real delta ) +{ + // save the current health as the previous health + m_prevHealth = m_currentHealth; + + // change the health by the delta, it can be positive or negative + m_currentHealth += delta; + + // high end cap + Real maxHealth = m_maxHealth; + if( m_currentHealth > maxHealth ) + m_currentHealth = maxHealth; + + // low end cap + const Real lowEndCap = 0.0f; // low end cap for health, don't go below this + if( m_currentHealth < lowEndCap ) + m_currentHealth = lowEndCap; + + // recalc the damage state + BodyDamageType oldState = m_curDamageState; + setCorrectDamageState(); + + // if our state has changed + if( m_curDamageState != oldState ) + { + + // + // show a visual change in the model for the damage state, we do not show visual changes + // for damage states when things are under construction because we just don't have + // all the art states for that during buildup animation + // + if( !getObject()->getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) + evaluateVisualCondition(); + + } // end if + + // mark the bit according to our health. (if our AI is dead but our health improves, it will + // still re-flag this bit in the AIDeadState every frame.) + getObject()->setEffectivelyDead(m_currentHealth <= 0); + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::internalAddSubdualDamage( Real delta ) +{ + const ActiveBodyModuleData *data = getActiveBodyModuleData(); + + m_currentSubdualDamage += delta; + m_currentSubdualDamage = min(m_currentSubdualDamage, data->m_subdualDamageCap); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::internalAddChronoDamage(Real delta) +{ + // Just increment, we don't need a cap. we kill once maxHealth is reached + //Real chronoDamageCap = m_maxHealth * 2.0; + m_currentChronoDamage += delta; + //m_currentChronoDamage = min(m_currentChronoDamage, chronoDamageCap); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool ActiveBody::canBeSubdued() const +{ + // Any body with subdue listings can be subdued. + return getActiveBodyModuleData()->m_subdualDamageCap > 0; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::onSubdualChange( Bool isNowSubdued ) +{ + if( !getObject()->isKindOf(KINDOF_PROJECTILE) ) + { + Object *me = getObject(); + + if( isNowSubdued ) + { + me->setDisabled(DISABLED_SUBDUED); + + ContainModuleInterface *contain = me->getContain(); + if ( contain ) + contain->orderAllPassengersToIdle( CMD_FROM_AI ); + + } + else + { + me->clearDisabled(DISABLED_SUBDUED); + + if( me->isKindOf( KINDOF_FS_INTERNET_CENTER ) ) + { + //Kris: October 20, 2003 - Patch 1.01 + //Any unit inside an internet center is a hacker! Order + //them to start hacking again. + ContainModuleInterface *contain = me->getContain(); + if ( contain ) + contain->orderAllPassengersToHackInternet( CMD_FROM_AI ); + } + } + } + else if( isNowSubdued )// There is no coming back from being jammed, and projectiles can't even heal, but this makes it clear. + { + ProjectileUpdateInterface *pui = getObject()->getProjectileUpdateInterface(); + if( pui ) + { + pui->projectileNowJammed(); + } + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::onSubdualChronoChange( Bool isNowSubdued ) +{ + // TODO: Apply/Remove visual effects + + Object *me = getObject(); + + if( isNowSubdued ) + { + me->setDisabled(DISABLED_CHRONO); + + ContainModuleInterface *contain = me->getContain(); + if ( contain ) + contain->orderAllPassengersToIdle( CMD_FROM_AI ); + } + else + { + me->clearDisabled(DISABLED_CHRONO); + + if (me->isKindOf(KINDOF_FS_INTERNET_CENTER)) + { + //Kris: October 20, 2003 - Patch 1.01 + //Any unit inside an internet center is a hacker! Order + //them to start hacking again. + ContainModuleInterface* contain = me->getContain(); + if (contain) + contain->orderAllPassengersToHackInternet(CMD_FROM_AI); + } + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool ActiveBody::isSubduedChrono() const +{ + return (m_maxHealth * TheGlobalData->m_chronoDamageDisableThreshold) <= m_currentChronoDamage; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool ActiveBody::isSubdued() const +{ + return m_maxHealth <= m_currentSubdualDamage; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Real ActiveBody::getHealth() const +{ + return m_currentHealth; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +BodyDamageType ActiveBody::getDamageState() const +{ + return m_curDamageState; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Real ActiveBody::getMaxHealth() const +{ + return m_maxHealth; +} ///< return max health + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +UnsignedInt ActiveBody::getSubdualDamageHealRate() const +{ + return getActiveBodyModuleData()->m_subdualDamageHealRate; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Real ActiveBody::getSubdualDamageHealAmount() const +{ + return getActiveBodyModuleData()->m_subdualDamageHealAmount; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool ActiveBody::hasAnySubdualDamage() const +{ + return m_currentSubdualDamage > 0; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +UnsignedInt ActiveBody::getChronoDamageHealRate() const +{ + return TheGlobalData->m_chronoDamageHealRate; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Real ActiveBody::getChronoDamageHealAmount() const +{ + DEBUG_LOG(("ActiveBody::getChronoDamageHealAmount() - maxHealth = %f\n", m_maxHealth)); + return m_maxHealth * TheGlobalData->m_chronoDamageHealAmount; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool ActiveBody::hasAnyChronoDamage() const +{ + return m_currentChronoDamage > 0; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Real ActiveBody::getInitialHealth() const +{ + return m_initialHealth; +} // return initial health + + +// ------------------------------------------------------------------------------------------------ +/** Set or unset the overridable indestructible flag in the body */ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::setIndestructible( Bool indestructible ) +{ + + m_indestructible = indestructible; + + // for bridges, we mirror this state on its towers + Object *us = getObject(); + if( us->isKindOf( KINDOF_BRIDGE ) ) + { + BridgeBehaviorInterface *bbi = BridgeBehavior::getBridgeBehaviorInterfaceFromObject( us ); + if( bbi ) + { + Object *tower; + + // get tower + for( Int i = 0; i < BRIDGE_MAX_TOWERS; ++i ) + { + + tower = TheGameLogic->findObjectByID( bbi->getTowerID( BridgeTowerType(i) ) ); + if( tower ) + { + BodyModuleInterface *body = tower->getBodyModule(); + + if( body ) + body->setIndestructible( indestructible ); + + } // end if + + } // end for, i + + } // end if + + } // end if + +} // end setIndestructible + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::onVeterancyLevelChanged( VeterancyLevel oldLevel, VeterancyLevel newLevel, Bool provideFeedback ) +{ + if (oldLevel == newLevel) + return; + + if (oldLevel < newLevel) + { + if( provideFeedback ) + { + AudioEventRTS veterancyChanged; + switch (newLevel) + { + case LEVEL_VETERAN: + veterancyChanged = *getObject()->getTemplate()->getSoundPromotedVeteran(); + break; + case LEVEL_ELITE: + veterancyChanged = *getObject()->getTemplate()->getSoundPromotedElite(); + break; + case LEVEL_HEROIC: + veterancyChanged = *getObject()->getTemplate()->getSoundPromotedHero(); + break; + } + + veterancyChanged.setObjectID(getObject()->getID()); + TheAudio->addAudioEvent(&veterancyChanged); + } + + //Also mark the UI dirty -- incase the object is selected or contained. + Object *obj = getObject(); + Drawable *draw = TheInGameUI->getFirstSelectedDrawable(); + if( draw ) + { + Object *checkOwner = draw->getObject(); + if( checkOwner == obj ) + { + //Our selected object has been promoted! + TheControlBar->markUIDirty(); + } + else + { + const Object *containedBy = obj->getContainedBy(); + if( containedBy && TheInGameUI->getSelectCount() == 1 ) + { + Object *checkOwner = draw->getObject(); + if( checkOwner == containedBy ) + { + //But only if the contained by object is containing me! + TheControlBar->markUIDirty(); + } + } + } + } + } + + Real oldBonus = TheGlobalData->m_healthBonus[oldLevel]; + Real newBonus = TheGlobalData->m_healthBonus[newLevel]; + Real mult = newBonus / oldBonus; + + // get this before calling setMaxHealth, since it can clip curHealth + //Real newHealth = m_currentHealth * mult; + + // change the max + setMaxHealth(m_maxHealth * mult, PRESERVE_RATIO ); + + // now change the cur (setMaxHealth now handles it) + //internalChangeHealth( newHealth - m_currentHealth ); + + switch (newLevel) + { + case LEVEL_REGULAR: + clearArmorSetFlag(ARMORSET_VETERAN); + clearArmorSetFlag(ARMORSET_ELITE); + clearArmorSetFlag(ARMORSET_HERO); + break; + case LEVEL_VETERAN: + setArmorSetFlag(ARMORSET_VETERAN); + clearArmorSetFlag(ARMORSET_ELITE); + clearArmorSetFlag(ARMORSET_HERO); + break; + case LEVEL_ELITE: + clearArmorSetFlag(ARMORSET_VETERAN); + setArmorSetFlag(ARMORSET_ELITE); + clearArmorSetFlag(ARMORSET_HERO); + break; + case LEVEL_HEROIC: + clearArmorSetFlag(ARMORSET_VETERAN); + clearArmorSetFlag(ARMORSET_ELITE); + setArmorSetFlag(ARMORSET_HERO); + break; + } +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::setAflame( Bool ) +{ + + // + // All this does now is act like a major body state change. It is called after Aflame has been + // set or cleared as an Object Status + // + updateBodyParticleSystems(); + +} +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::overrideDamageFX(DamageFX* damageFX) +{ + if (damageFX != NULL) { + m_curDamageFX = damageFX; + m_damageFXOverride = true; + } + else { + m_curDamageFX = NULL; + m_damageFXOverride = false; + + // Restore DamageFX from current armorset + const ArmorTemplateSet* set = getObject()->getTemplate()->findArmorTemplateSet(m_curArmorSetFlags); + if (set) + { + m_curDamageFX = set->getDamageFX(); + } + } + DEBUG_LOG((">>>ActiveBody: overrideDamageFX - new m_curDamageFX = %d, m_damageFXOverride = %d\n", + m_curDamageFX, m_damageFXOverride)); +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::crc( Xfer *xfer ) +{ + + // extend base class + BodyModule::crc( xfer ); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // base class + BodyModule::xfer( xfer ); + + // current health + xfer->xferReal( &m_currentHealth ); + + xfer->xferReal( &m_currentSubdualDamage ); + + // previous health + xfer->xferReal( &m_prevHealth ); + + // max health + xfer->xferReal( &m_maxHealth ); + + // initial health + xfer->xferReal( &m_initialHealth ); + + // current damage state + xfer->xferUser( &m_curDamageState, sizeof( BodyDamageType ) ); + + // next damage fx time + xfer->xferUnsignedInt( &m_nextDamageFXTime ); + + // last damage fx done + xfer->xferUser( &m_lastDamageFXDone, sizeof( DamageType ) ); + + // last damage info + xfer->xferSnapshot( &m_lastDamageInfo ); + + // last damage timestamp + xfer->xferUnsignedInt( &m_lastDamageTimestamp ); + + // last damage timestamp + xfer->xferUnsignedInt( &m_lastHealingTimestamp ); + + // front crushed + xfer->xferBool( &m_frontCrushed ); + + // back crushed + xfer->xferBool( &m_backCrushed ); + + // last damaged cleared + xfer->xferBool( &m_lastDamageCleared ); + + // indestructible + xfer->xferBool( &m_indestructible ); + + // particle system count + BodyParticleSystem *system; + UnsignedShort particleSystemCount = 0; + for( system = m_particleSystems; system; system = system->m_next ) + particleSystemCount++; + xfer->xferUnsignedShort( &particleSystemCount ); + + // particle systems + if( xfer->getXferMode() == XFER_SAVE ) + { + + // walk the particle systems + for( system = m_particleSystems; system; system = system->m_next ) + { + + // write particle system ID + xfer->xferUser( &system->m_particleSystemID, sizeof( ParticleSystemID ) ); + + } // end for, system + + } // end if, save + else + { + ParticleSystemID particleSystemID; + + // the list should be empty at this time + if( m_particleSystems != NULL ) + { + + DEBUG_CRASH(( "ActiveBody::xfer - m_particleSystems should be empty, but is not\n" )); + throw SC_INVALID_DATA; + + } // end if + + // read all data elements + BodyParticleSystem *newEntry; + for( UnsignedShort i = 0; i < particleSystemCount; ++i ) + { + + // read particle system ID + xfer->xferUser( &particleSystemID, sizeof( ParticleSystemID ) ); + + // allocate entry and add to list + newEntry = newInstance(BodyParticleSystem); + newEntry->m_particleSystemID = particleSystemID; + newEntry->m_next = m_particleSystems; // the list will be reversed, but we don't care + m_particleSystems = newEntry; + + } // end for, i + + } // end else, load + + // armor set flags + m_curArmorSetFlags.xfer( xfer ); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::loadPostProcess( void ) +{ + + // extend base class + BodyModule::loadPostProcess(); + +} // end loadPostProcess diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Die/DieModule.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Die/DieModule.cpp index 2dcea456b2b..0e333636988 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Die/DieModule.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Die/DieModule.cpp @@ -31,6 +31,7 @@ #define DEFINE_OBJECT_STATUS_NAMES #include "Common/Xfer.h" +#include "Common/GlobalData.h" #include "GameClient/Drawable.h" #include "GameLogic/ExperienceTracker.h" #include "GameLogic/GameLogic.h" @@ -47,10 +48,13 @@ //------------------------------------------------------------------------------------------------- -DieMuxData::DieMuxData() : - m_deathTypes(DEATH_TYPE_FLAGS_ALL), - m_veterancyLevels(VETERANCY_LEVEL_FLAGS_ALL) -{ +DieMuxData::DieMuxData() { + m_deathTypes = DEATH_TYPE_FLAGS_ALL; + m_veterancyLevels = VETERANCY_LEVEL_FLAGS_ALL; + + if (TheGlobalData) { + m_deathTypes &= ~TheGlobalData->m_defaultExcludedDeathTypes; + } } //------------------------------------------------------------------------------------------------- diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Helper/ChronoDamageHelper.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Helper/ChronoDamageHelper.cpp new file mode 100644 index 00000000000..8d7888ca977 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Helper/ChronoDamageHelper.cpp @@ -0,0 +1,133 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: ChronoDamageHelper.h //////////////////////////////////////////////////////////////////////// +// Author: Andi W, July 2025 +// Desc: Object helper - Clears chrono disable status and heals chrono damage since Body modules can't have Updates +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" +#include "Common/Xfer.h" + +#include "GameLogic/Object.h" +#include "GameLogic/Module/BodyModule.h" +#include "GameLogic/Module/ChronoDamageHelper.h" + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +ChronoDamageHelper::ChronoDamageHelper( Thing *thing, const ModuleData *modData ) : ObjectHelper( thing, modData ) +{ + m_healingStepCountdown = 0; + + setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +ChronoDamageHelper::~ChronoDamageHelper( void ) +{ + +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +UpdateSleepTime ChronoDamageHelper::update() +{ + BodyModuleInterface *body = getObject()->getBodyModule(); + + DEBUG_LOG(("ChronoDamageHelper::update() - m_healingStepCountdown = %d, healRate = %d, healAmount = %f\n", + m_healingStepCountdown, + body->getChronoDamageHealRate(), body->getChronoDamageHealAmount())); + + m_healingStepCountdown--; + if( m_healingStepCountdown > 0 ) + return UPDATE_SLEEP_NONE; + + m_healingStepCountdown = body->getChronoDamageHealRate(); + + DamageInfo removeSubdueDamage; + removeSubdueDamage.in.m_damageType = DAMAGE_CHRONO_UNRESISTABLE; + removeSubdueDamage.in.m_amount = -body->getChronoDamageHealAmount(); + body->attemptDamage(&removeSubdueDamage); + + if( body->hasAnyChronoDamage() ) + return UPDATE_SLEEP_NONE; + else + return UPDATE_SLEEP_FOREVER; +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void ChronoDamageHelper::notifyChronoDamage( Real amount ) +{ + if( amount > 0 ) + { + m_healingStepCountdown = getObject()->getBodyModule()->getChronoDamageHealRate(); + setWakeFrame(getObject(), UPDATE_SLEEP_NONE); + } +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void ChronoDamageHelper::crc( Xfer *xfer ) +{ + + // object helper crc + ObjectHelper::crc( xfer ); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info; + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void ChronoDamageHelper::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // object helper base class + ObjectHelper::xfer( xfer ); + + xfer->xferUnsignedInt( &m_healingStepCountdown ); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void ChronoDamageHelper::loadPostProcess( void ) +{ + + // object helper base class + ObjectHelper::loadPostProcess(); + +} // end loadPostProcess + diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp index 5658e8b600e..b935559a4ed 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp @@ -1,2844 +1,2844 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: Locomotor.cpp /////////////////////////////////////////////////////////////////////////////// -// Author: Steven Johnson, Feb 2002 -// Desc: Locomotor descriptions -/////////////////////////////////////////////////////////////////////////////////////////////////// - - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -#define DEFINE_SURFACECATEGORY_NAMES -#define DEFINE_LOCO_Z_NAMES -#define DEFINE_LOCO_APPEARANCE_NAMES - -#include "Common/INI.h" -#include "GameLogic/GameLogic.h" -#include "GameLogic/PartitionManager.h" -#include "GameLogic/Locomotor.h" -#include "GameLogic/Object.h" -#include "GameLogic/AI.h" -#include "GameLogic/AIPathfind.h" -#include "GameLogic/Module/PhysicsUpdate.h" -#include "GameLogic/Module/BodyModule.h" -#include "GameLogic/Module/AIUpdate.h" - -#ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -static const Real DONUT_TIME_DELAY_SECONDS=2.5f; -static const Real DONUT_DISTANCE=4.0*PATHFIND_CELL_SIZE_F; - - -#define MAX_BRAKING_FACTOR 5.0f -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -LocomotorStore *TheLocomotorStore = NULL; ///< the Locomotor store definition - -const Real BIGNUM = 99999.0f; - -static const char *TheLocomotorPriorityNames[] = -{ - "MOVES_BACK", - "MOVES_MIDDLE", - "MOVES_FRONT", - - NULL -}; - - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------------- -static Real calcSlowDownDist(Real curSpeed, Real desiredSpeed, Real maxBraking) -{ - Real delta = curSpeed - desiredSpeed; - if (delta <= 0) - return 0.0f; - - Real dist = (sqr(delta) / fabs(maxBraking)) * 0.5f; - - // use a little fudge so that things can stop "on a dime" more easily... - const Real FUDGE = 1.05f; - return dist * FUDGE; -} - -//----------------------------------------------------------------------------- -inline Bool isNearlyZero(Real a) -{ - const Real TINY_EPSILON = 0.001f; - return fabs(a) < TINY_EPSILON; -} - -//----------------------------------------------------------------------------- -inline Bool isNearly(Real a, Real val) -{ - const Real TINY_EPSILON = 0.001f; - return fabs(a - val) < TINY_EPSILON; -} - -//----------------------------------------------------------------------------- -// return the angle delta (in 3-space) we turned. -static Real tryToRotateVector3D( - Real maxAngle, // if negative, it's a percent (0...1) of the dist to rotate 'em - const Vector3& inCurDir, - const Vector3& inGoalDir, - Vector3& actualDir -) -{ - if (isNearlyZero(maxAngle)) - { - actualDir = inCurDir; - return 0.0f; - } - - Vector3 curDir = inCurDir; - curDir.Normalize(); - - Vector3 goalDir = inGoalDir; - goalDir.Normalize(); - - // dot of two unit vectors is cos of angle between them. - Real cosine = Vector3::Dot_Product(curDir, goalDir); - // bound it in case of numerical error - Real angleBetween = (Real)ACos(clamp(-1.0f, cosine, 1.0f)); - - if (maxAngle < 0) - { - maxAngle = -maxAngle * angleBetween; - if (isNearlyZero(maxAngle)) - { - actualDir = inCurDir; - return 0.0f; - } - } - - if (fabs(angleBetween) <= maxAngle) - { - // close enough - actualDir = goalDir; - } - else - { - // nah, try as much as we can in the right dir. - // we need to rotate around the axis perpendicular to these two vecs. - // but: cross of two vectors is the perpendicular axis! -#ifdef ALLOW_TEMPORARIES - Vector3 objCrossGoal = Vector3::Cross_Product(curDir, goalDir); - objCrossGoal.Normalize(); -#else - Vector3 objCrossGoal; - Vector3::Normalized_Cross_Product(curDir, goalDir, &objCrossGoal); -#endif - - angleBetween = maxAngle; - Matrix3D rotMtx(objCrossGoal, angleBetween); - actualDir = rotMtx.Rotate_Vector(curDir); - } - - return angleBetween; -} - -//------------------------------------------------------------------------------------------------- -static Real tryToOrientInThisDirection3D(Object* obj, Real maxTurnRate, const Vector3& desiredDir) -{ - Vector3 actualDir; - Real relAngle = tryToRotateVector3D(maxTurnRate, obj->getTransformMatrix()->Get_X_Vector(), desiredDir, actualDir); - if (relAngle != 0.0f) - { - Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z); - - Matrix3D newXform; - newXform.buildTransformMatrix( objPos, actualDir ); - - obj->setTransformMatrix( &newXform ); - } - return relAngle; -} - -//------------------------------------------------------------------------------------------------- -inline Real tryToOrientInThisDirection3D(Object* obj, Real maxTurnRate, const Coord3D* dir) -{ - return tryToOrientInThisDirection3D(obj, maxTurnRate, Vector3(dir->x, dir->y, dir->z)); -} - -//----------------------------------------------------------------------------- -static void calcDirectionToApplyThrust( - const Object* obj, - const PhysicsBehavior* physics, - const Coord3D& ingoalPos, - Real maxAccel, - Vector3& goalDir -) -{ - /* - our meta-goal here is to calculate the direction we should apply our motive force - in order to minimize the angle between (our velocity) and (direction towards goalpos). - - this is complicated by the fact that we generally have an intrinsic velocity already, - that must be accounted for, and by the fact that we can only apply force in our - forward-x-direction (with a thrust-angle-range), and (due to limited range) might not - be able to apply the force in the optimal direction! - */ - - // convert to Vector3, to use all its handy stuff - Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z); - Vector3 goalPos(ingoalPos.x, ingoalPos.y, ingoalPos.z); - - Vector3 vecToGoal = goalPos - objPos; - if (isNearlyZero(vecToGoal.Length2())) - { - // goal pos is essentially same as current pos, so just stay the same & return - goalDir = obj->getTransformMatrix()->Get_X_Vector(); - return; - } - - /* - get our cur vel into a useful Vector3 form - */ - Vector3 curVel(physics->getVelocity()->x, physics->getVelocity()->y, physics->getVelocity()->z); - - // add gravity to our vel so that we account for it in our calcs - curVel.Z += TheGlobalData->m_gravity; - - Bool foundSolution = false; - Real distToGoalSqr = vecToGoal.Length2(); - Real distToGoal = sqrt(distToGoalSqr); - Real curVelMagSqr = curVel.Length2(); - Real curVelMag = sqrt(curVelMagSqr); - Real maxAccelSqr = sqr(maxAccel); - - Real denom = curVelMagSqr - maxAccelSqr; - if (!isNearlyZero(denom)) - { - // solve the (greatly simplified) quadratic... - Real t = (distToGoal * (curVelMag + maxAccel)) / denom; - Real t2 = (distToGoal * (curVelMag - maxAccel)) / denom; - if (t >= 0 || t2 >= 0) - { - // choose the smallest positive t. - if (t < 0 || (t2 >= 0 && t2 < t)) - t = t2; - - // plug it in. - if (!isNearlyZero(t)) - { - goalDir.X = (vecToGoal.X / t) - curVel.X; - goalDir.Y = (vecToGoal.Y / t) - curVel.Y; - goalDir.Z = (vecToGoal.Z / t) - curVel.Z; - goalDir.Normalize(); - foundSolution = true; - } - } - } - if (!foundSolution) - { - // Doh... no (useful) solution. revert to dumb. - goalDir = vecToGoal; - goalDir.Normalize(); - } - -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------------- -LocomotorTemplate::LocomotorTemplate() -{ - // these values mean "make the same as undamaged if not explicitly specified" - m_maxSpeedDamaged = -1.0f; - m_maxTurnRateDamaged = -1.0f; - m_accelerationDamaged = -1.0f; - m_liftDamaged = -1.0f; - - m_surfaces = 0; - m_maxSpeed = 0.0f; - m_maxTurnRate = 0.0f; - m_acceleration = 0.0f; - m_lift = 0.0f; - m_braking = BIGNUM; - m_minSpeed = 0.0f; - m_minTurnSpeed = BIGNUM; - m_behaviorZ = Z_NO_Z_MOTIVE_FORCE; - m_appearance = LOCO_OTHER; - m_movePriority = LOCO_MOVES_MIDDLE; - m_preferredHeight = 0; - m_preferredHeightDamping = 1.0f; - m_circlingRadius = 0; - - m_maxThrustAngle = 0; - m_speedLimitZ = 999999.0f; - m_extra2DFriction = 0.0f; - - m_accelPitchLimit = 0; - m_decelPitchLimit = 0; - m_bounceKick = 0; - -// m_pitchStiffness = 0; -// m_rollStiffness = 0; -// m_pitchDamping = 0; -// m_rollDamping = 0; -// it's highly unlikely you want zero for the defaults for stiffness and damping... (srj) -// for stiffness: stiffness of the "springs" in the suspension 0 = no stiffness, 1 = totally stiff (huh huh, he said "stiff") -// for damping: 0=perfect spring, bounces forever. 1=glued to terrain. - m_pitchStiffness = 0.1f; - m_rollStiffness = 0.1f; - m_pitchDamping = 0.9f; - m_rollDamping = 0.9f; - m_forwardVelCoef = 0; - m_pitchByZVelCoef = 0; - m_thrustRoll = 0.0f; - m_wobbleRate = 0.0f; - m_minWobble = 0.0f; - m_maxWobble = 0.0f; - m_lateralVelCoef = 0; - m_forwardAccelCoef = 0; - m_lateralAccelCoef = 0; - m_uniformAxialDamping = 1.0f; - m_turnPivotOffset = 0; - m_apply2DFrictionWhenAirborne = false; - m_downhillOnly = false; - m_allowMotiveForceWhileAirborne = false; - m_locomotorWorksWhenDead = false; - m_airborneTargetingHeight = INT_MAX; - m_stickToGround = false; - m_canMoveBackward = false; - m_hasSuspension = false; - m_wheelTurnAngle = 0; - m_maximumWheelExtension = 0; - m_maximumWheelCompression = 0; - m_closeEnoughDist = 1.0f; - m_isCloseEnoughDist3D = FALSE; - m_ultraAccurateSlideIntoPlaceFactor = 0.0f; - - m_wanderWidthFactor = 0.0f; - m_wanderLengthFactor = 1.0f; - m_wanderAboutPointRadius = 0.0f; - - m_rudderCorrectionDegree = 0.0f; - m_rudderCorrectionRate = 0.0f; - m_elevatorCorrectionDegree = 0.0f; - m_elevatorCorrectionRate = 0.0f; - -} - -//------------------------------------------------------------------------------------------------- -LocomotorTemplate::~LocomotorTemplate() -{ - -} - -//------------------------------------------------------------------------------------------------- -void LocomotorTemplate::validate() -{ - // this is ok; parachutes need it! - //DEBUG_ASSERTCRASH(m_lift == 0.0f || m_lift > fabs(TheGlobalData->m_gravity), ("Lift is too low to counteract gravity!")); - //DEBUG_ASSERTCRASH(m_liftDamaged == 0.0f || m_liftDamaged > fabs(TheGlobalData->m_gravity), ("LiftDamaged is too low to counteract gravity!")); - //DEBUG_ASSERTCRASH(m_preferredHeight == 0.0f || (m_behaviorZ == Z_SURFACE_RELATIVE_HEIGHT || m_behaviorZ == Z_ABSOLUTE_HEIGHT || m_appearance == LOCO_THRUST), - // ("You must use Z_SURFACE_RELATIVE_HEIGHT or Z_ABSOLUTE_HEIGHT (or THRUST) to use preferredHeight")); - - // for 'damaged' stuff that was omitted, set 'em to be the same as 'undamaged'... - if (m_maxSpeedDamaged < 0.0f) - m_maxSpeedDamaged = m_maxSpeed; - - if (m_maxTurnRateDamaged < 0.0f) - m_maxTurnRateDamaged = m_maxTurnRate; - - if (m_accelerationDamaged < 0.0f) - m_accelerationDamaged = m_acceleration; - - if (m_liftDamaged < 0.0f) - m_liftDamaged = m_lift; - - if (m_appearance == LOCO_WINGS) - { - if (m_minSpeed <= 0.0f) - { - DEBUG_CRASH(("WINGS should always have positive minSpeeds (otherwise, they hover)")); - m_minSpeed = 0.01f; - } - if (m_minTurnSpeed <= 0.0f) - { - DEBUG_CRASH(("WINGS should always have positive minTurnSpeed")); - m_minTurnSpeed = 0.01f; - } - } - - if (m_appearance == LOCO_THRUST) - { - if (m_behaviorZ != Z_NO_Z_MOTIVE_FORCE || - m_lift != 0.0f || - m_liftDamaged != 0.0f) - { - DEBUG_CRASH(("THRUST locos may not use ZAxisBehavior or lift!\n")); - throw INI_INVALID_DATA; - } - if (m_maxSpeed <= 0.0f) - { - // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing - DEBUG_LOG(("THRUST locos may not have zero m_maxSpeed; healing...\n")); - m_maxSpeed = 0.01f; - } - if (m_maxSpeedDamaged <= 0.0f) - { - // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing - DEBUG_LOG(("THRUST locos may not have zero m_maxSpeedDamaged; healing...\n")); - m_maxSpeedDamaged = 0.01f; - } - if (m_minSpeed <= 0.0f) - { - // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing - DEBUG_LOG(("THRUST locos may not have zero m_minSpeed; healing...\n")); - m_minSpeed = 0.01f; - } - } -} - -//------------------------------------------------------------------------------------------------- -static void parseFrictionPerSec( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - Real fricPerSec = INI::scanReal(ini->getNextToken()); - Real fricPerFrame = fricPerSec * SECONDS_PER_LOGICFRAME_REAL; - *(Real *)store = fricPerFrame; -} - -//------------------------------------------------------------------------------------------------- -const FieldParse* LocomotorTemplate::getFieldParse() const -{ - static const FieldParse TheFieldParse[] = - { - { "Surfaces", INI::parseBitString32, TheLocomotorSurfaceTypeNames, offsetof(LocomotorTemplate, m_surfaces) }, - { "Speed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_maxSpeed) }, - { "SpeedDamaged", INI::parseVelocityReal, NULL, offsetof( LocomotorTemplate, m_maxSpeedDamaged ) }, - { "TurnRate", INI::parseAngularVelocityReal, NULL, offsetof(LocomotorTemplate, m_maxTurnRate) }, - { "TurnRateDamaged", INI::parseAngularVelocityReal, NULL, offsetof( LocomotorTemplate, m_maxTurnRateDamaged ) }, - { "Acceleration", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_acceleration) }, - { "AccelerationDamaged", INI::parseAccelerationReal, NULL, offsetof( LocomotorTemplate, m_accelerationDamaged ) }, - { "Lift", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_lift) }, - { "LiftDamaged", INI::parseAccelerationReal, NULL, offsetof( LocomotorTemplate, m_liftDamaged ) }, - { "Braking", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_braking) }, - { "MinSpeed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_minSpeed) }, - { "MinTurnSpeed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_minTurnSpeed) }, - { "PreferredHeight", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_preferredHeight) }, - { "PreferredHeightDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_preferredHeightDamping) }, - { "CirclingRadius", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_circlingRadius) }, - { "Extra2DFriction", parseFrictionPerSec, NULL, offsetof(LocomotorTemplate, m_extra2DFriction) }, - { "SpeedLimitZ", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_speedLimitZ) }, - { "MaxThrustAngle", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_maxThrustAngle) }, // yes, angle, not angular-vel - { "ZAxisBehavior", INI::parseIndexList, TheLocomotorBehaviorZNames, offsetof(LocomotorTemplate, m_behaviorZ) }, - { "Appearance", INI::parseIndexList, TheLocomotorAppearanceNames, offsetof(LocomotorTemplate, m_appearance) }, \ - { "GroupMovementPriority", INI::parseIndexList, TheLocomotorPriorityNames, offsetof(LocomotorTemplate, m_movePriority) }, \ - - { "AccelerationPitchLimit", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_accelPitchLimit) }, - { "DecelerationPitchLimit", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_decelPitchLimit) }, - { "BounceAmount", INI::parseAngularVelocityReal, NULL, offsetof(LocomotorTemplate, m_bounceKick) }, - { "PitchStiffness", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchStiffness) }, - { "RollStiffness", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rollStiffness) }, - { "PitchDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchDamping) }, - { "RollDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rollDamping) }, - { "ThrustRoll", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_thrustRoll) }, - { "ThrustWobbleRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wobbleRate) }, - { "ThrustMinWobble", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_minWobble) }, - { "ThrustMaxWobble", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maxWobble) }, - { "PitchInDirectionOfZVelFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchByZVelCoef) }, - { "ForwardVelocityPitchFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_forwardVelCoef) }, - { "LateralVelocityRollFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_lateralVelCoef) }, - { "ForwardAccelerationPitchFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_forwardAccelCoef) }, - { "LateralAccelerationRollFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_lateralAccelCoef) }, - { "UniformAxialDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_uniformAxialDamping) }, - { "TurnPivotOffset", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_turnPivotOffset) }, - { "Apply2DFrictionWhenAirborne", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_apply2DFrictionWhenAirborne) }, - { "DownhillOnly", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_downhillOnly) }, - { "AllowAirborneMotiveForce", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_allowMotiveForceWhileAirborne) }, - { "LocomotorWorksWhenDead", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_locomotorWorksWhenDead) }, - { "AirborneTargetingHeight", INI::parseInt, NULL, offsetof( LocomotorTemplate, m_airborneTargetingHeight ) }, - { "StickToGround", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_stickToGround) }, - { "CanMoveBackwards", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_canMoveBackward) }, - { "HasSuspension", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_hasSuspension) }, - { "FrontWheelTurnAngle", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_wheelTurnAngle) }, - { "MaximumWheelExtension", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maximumWheelExtension) }, - { "MaximumWheelCompression", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maximumWheelCompression) }, - { "CloseEnoughDist", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_closeEnoughDist) }, - { "CloseEnoughDist3D", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_isCloseEnoughDist3D) }, - { "SlideIntoPlaceTime", INI::parseDurationReal, NULL, offsetof(LocomotorTemplate, m_ultraAccurateSlideIntoPlaceFactor) }, - - { "WanderWidthFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderWidthFactor) }, - { "WanderLengthFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderLengthFactor) }, - { "WanderAboutPointRadius", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderAboutPointRadius) }, - - { "RudderCorrectionDegree", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rudderCorrectionDegree) }, - { "RudderCorrectionRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rudderCorrectionRate) }, - { "ElevatorCorrectionDegree", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_elevatorCorrectionDegree) }, - { "ElevatorCorrectionRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_elevatorCorrectionRate) }, - { NULL, NULL, NULL, 0 } // keep this last - - }; - return TheFieldParse; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -LocomotorStore::LocomotorStore() -{ -} - -//------------------------------------------------------------------------------------------------- -LocomotorStore::~LocomotorStore() -{ - // delete all the templates, then clear out the table. - LocomotorTemplateMap::iterator it; - for (it = m_locomotorTemplates.begin(); it != m_locomotorTemplates.end(); ++it) { - it->second->deleteInstance(); - } - - m_locomotorTemplates.clear(); -} - -//------------------------------------------------------------------------------------------------- -LocomotorTemplate* LocomotorStore::findLocomotorTemplate(NameKeyType namekey) -{ - if (namekey == NAMEKEY_INVALID) - return NULL; - - LocomotorTemplateMap::iterator it = m_locomotorTemplates.find(namekey); - if (it == m_locomotorTemplates.end()) - return NULL; - else - return (*it).second; -} - -//------------------------------------------------------------------------------------------------- -const LocomotorTemplate* LocomotorStore::findLocomotorTemplate(NameKeyType namekey) const -{ - if (namekey == NAMEKEY_INVALID) - return NULL; - - LocomotorTemplateMap::const_iterator it = m_locomotorTemplates.find(namekey); - if (it == m_locomotorTemplates.end()) - { - return NULL; - } - else - { - return (*it).second; - } -} - -//------------------------------------------------------------------------------------------------- -void LocomotorStore::update() -{ -} - -//------------------------------------------------------------------------------------------------- -void LocomotorStore::reset() -{ - // cleanup overrides. - LocomotorTemplateMap::iterator it; - for (it = m_locomotorTemplates.begin(); it != m_locomotorTemplates.end(); ) { - Overridable *locoTemp = it->second->deleteOverrides(); - if (!locoTemp) - { - m_locomotorTemplates.erase(it); - } - else - { - ++it; - } - } -} - -//------------------------------------------------------------------------------------------------- -LocomotorTemplate *LocomotorStore::newOverride( LocomotorTemplate *locoTemplate ) -{ - if (locoTemplate == NULL) - return NULL; - - // allocate new template - LocomotorTemplate *newTemplate = newInstance(LocomotorTemplate); - - // copy data from final override to 'newTemplate' as a set of initial default values - *newTemplate = *locoTemplate; - locoTemplate->setNextOverride(newTemplate); - - newTemplate->markAsOverride(); - - // return the newly created override for us to set values with etc - return newTemplate; - -} // end newOverride - -//------------------------------------------------------------------------------------------------- -/*static*/ void LocomotorStore::parseLocomotorTemplateDefinition(INI* ini) -{ - if (!TheLocomotorStore) - throw INI_INVALID_DATA; - - Bool isOverride = false; - // read the Locomotor name - const char* token = ini->getNextToken(); - NameKeyType namekey = NAMEKEY(token); - - LocomotorTemplate *loco = TheLocomotorStore->findLocomotorTemplate(namekey); - if (loco) { - if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES) { - loco = TheLocomotorStore->newOverride((LocomotorTemplate*) loco->friend_getFinalOverride()); - } - isOverride = true; - } else { - loco = newInstance(LocomotorTemplate); - if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES) { - loco->markAsOverride(); - } - } - - loco->friend_setName(token); - ini->initFromINI(loco, loco->getFieldParse()); - loco->validate(); - - // if this is an override, then we want the pointer on the existing named locomotor to point us - // to the override, so don't add it to the map. - if (!isOverride) - TheLocomotorStore->m_locomotorTemplates[namekey] = loco; -} - -//------------------------------------------------------------------------------------------------- -/*static*/ void INI::parseLocomotorTemplateDefinition( INI* ini ) -{ - LocomotorStore::parseLocomotorTemplateDefinition(ini); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- -Locomotor::Locomotor(const LocomotorTemplate* tmpl) -{ - m_template = tmpl; - m_brakingFactor = 1.0f; - m_maxLift = BIGNUM; - m_maxSpeed = BIGNUM; - m_maxAccel = BIGNUM; - m_maxBraking = BIGNUM; - m_maxTurnRate = BIGNUM; - m_flags = 0; - m_closeEnoughDist = m_template->m_closeEnoughDist; - setFlag(IS_CLOSE_ENOUGH_DIST_3D, m_template->m_isCloseEnoughDist3D); -#ifdef CIRCLE_FOR_LANDING - m_circleThresh = 0.0f; -#endif - m_preferredHeight = m_template->m_preferredHeight; - m_preferredHeightDamping = m_template->m_preferredHeightDamping; - - m_angleOffset = GameLogicRandomValueReal(-PI/6, PI/6); - m_offsetIncrement = (PI/40) * (GameLogicRandomValueReal(0.8f, 1.2f)/m_template->m_wanderLengthFactor); - setFlag(OFFSET_INCREASING, GameLogicRandomValue(0,1)); - m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; - - m_speedMultiplier = 1.0; -} - -//------------------------------------------------------------------------------------------------- -Locomotor::Locomotor(const Locomotor& that) -{ - //Added By Sadullah Nader - //Initializations - m_angleOffset = 0.0f; - m_maintainPos.zero(); - - // - - m_template = that.m_template; - m_brakingFactor = that.m_brakingFactor; - m_maxLift = that.m_maxLift; - m_maxSpeed = that.m_maxSpeed; - m_maxAccel = that.m_maxAccel; - m_maxBraking = that.m_maxBraking; - m_maxTurnRate = that.m_maxTurnRate; - m_flags = that.m_flags; - m_closeEnoughDist = that.m_closeEnoughDist; -#ifdef CIRCLE_FOR_LANDING - m_circleThresh = that.m_circleThresh; -#endif - m_preferredHeight = that.m_preferredHeight; - m_preferredHeightDamping = that.m_preferredHeightDamping; - m_angleOffset = that.m_angleOffset; - m_offsetIncrement = that.m_offsetIncrement; -} - -//------------------------------------------------------------------------------------------------- -Locomotor& Locomotor::operator=(const Locomotor& that) -{ - if (this != &that) - { - m_template = that.m_template; - m_brakingFactor = that.m_brakingFactor; - m_maxLift = that.m_maxLift; - m_maxSpeed = that.m_maxSpeed; - m_maxAccel = that.m_maxAccel; - m_maxBraking = that.m_maxBraking; - m_maxTurnRate = that.m_maxTurnRate; - m_flags = that.m_flags; - m_closeEnoughDist = that.m_closeEnoughDist; -#ifdef CIRCLE_FOR_LANDING - m_circleThresh = that.m_circleThresh; -#endif - m_preferredHeight = that.m_preferredHeight; - m_preferredHeightDamping = that.m_preferredHeightDamping; - } - return *this; -} - -//------------------------------------------------------------------------------------------------- -Locomotor::~Locomotor() -{ -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void Locomotor::crc( Xfer *xfer ) -{ - -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void Locomotor::xfer( Xfer *xfer ) -{ - // version - const XferVersion currentVersion = 2; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - if (version>=2) { - xfer->xferUnsignedInt(&m_donutTimer); - } - - xfer->xferCoord3D(&m_maintainPos); - xfer->xferReal(&m_brakingFactor); - xfer->xferReal(&m_maxLift); - xfer->xferReal(&m_maxSpeed); - xfer->xferReal(&m_maxAccel); - xfer->xferReal(&m_maxBraking); - xfer->xferReal(&m_maxTurnRate); - xfer->xferReal(&m_closeEnoughDist); -#ifdef CIRCLE_FOR_LANDING - DEBUG_CRASH(("not supported, must fix me")); -#endif - xfer->xferUnsignedInt(&m_flags); - xfer->xferReal(&m_preferredHeight); - xfer->xferReal(&m_preferredHeightDamping); - xfer->xferReal(&m_angleOffset); - xfer->xferReal(&m_offsetIncrement); - - xfer->xferReal(&m_speedMultiplier); - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void Locomotor::loadPostProcess( void ) -{ - -} // end loadPostProcess - -//------------------------------------------------------------------------------------------------- -void Locomotor::startMove(void) -{ - // Reset the donut timer. - m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::getMaxSpeedForCondition(BodyDamageType condition) const -{ - Real speed; - - if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) - speed = m_template->m_maxSpeed; - else - speed = m_template->m_maxSpeedDamaged; - - speed *= m_speedMultiplier; - - if (speed > m_maxSpeed) - speed = m_maxSpeed; - - return speed; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::getMaxTurnRate(BodyDamageType condition) const -{ - Real turn; - - if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) - turn = m_template->m_maxTurnRate; - else - turn = m_template->m_maxTurnRateDamaged; - - turn *= m_speedMultiplier; - - if (turn > m_maxTurnRate) - turn = m_maxTurnRate; - - const Real TURN_FACTOR = 2; - if (getFlag(ULTRA_ACCURATE)) - turn *= TURN_FACTOR; // monster turning ability - - return turn; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::getMaxAcceleration(BodyDamageType condition) const -{ - Real accel; - - if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) - accel = m_template->m_acceleration; - else - accel = m_template->m_accelerationDamaged; - - accel *= m_speedMultiplier; - - if (accel > m_maxAccel) - accel = m_maxAccel; - - return accel; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::getBraking() const -{ - Real braking = m_template->m_braking; - - braking *= m_speedMultiplier; - - if (braking > m_maxBraking) - braking = m_maxBraking; - - return braking; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::getMaxLift(BodyDamageType condition) const -{ - Real lift; - - if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) - lift = m_template->m_lift; - else - lift = m_template->m_liftDamaged; - - lift *= m_speedMultiplier; - - if (lift > m_maxLift) - lift = m_maxLift; - - return lift; -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::locoUpdate_moveTowardsAngle(Object* obj, Real goalAngle) -{ - setFlag(MAINTAIN_POS_IS_VALID, false); - - if (obj == NULL || m_template == NULL) - return; - - PhysicsBehavior *physics = obj->getPhysics(); - if (physics == NULL) - { - DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); - return; - } - - // Skip moveTowardsAngle if physics say you're stunned - if(physics->getIsStunned()) - { - return; - } - -#ifdef DEBUG_OBJECT_ID_EXISTS -// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_moveTowardsAngle %f (%f deg), spd %f (%f)\n",goalAngle,goalAngle*180/PI,physics->getSpeed(),physics->getForwardSpeed2D())); -#endif - - Real minSpeed = getMinSpeed(); - if (minSpeed > 0) - { - // can't stay in one place; move in the desired direction at min speed. - Coord3D desiredPos = *obj->getPosition(); - desiredPos.x += Cos(goalAngle) * minSpeed * 2; - desiredPos.y += Sin(goalAngle) * minSpeed * 2; - // pass a huge num for "dist to goal", so that we don't think we're nearing - // our destination and thus slow down... - const Real onPathDistToGoal = 99999.0f; - Bool blocked = false; - locoUpdate_moveTowardsPosition(obj, desiredPos, onPathDistToGoal, minSpeed, &blocked); - - // don't need to call handleBehaviorZ() here, since locoUpdate_moveTowardsPosition() will do so - return; - } - else - { - DEBUG_ASSERTCRASH(m_template->m_appearance != LOCO_THRUST, ("THRUST should always have minspeeds!\n")); - Coord3D desiredPos = *obj->getPosition(); - desiredPos.x += Cos(goalAngle) * 1000.0f; - desiredPos.y += Sin(goalAngle) * 1000.0f; - PhysicsTurningType rotating = rotateTowardsPosition(obj, desiredPos); - physics->setTurning(rotating); - handleBehaviorZ(obj, physics, *obj->getPosition()); - } - -} - -//------------------------------------------------------------------------------------------------- -PhysicsTurningType Locomotor::rotateTowardsPosition(Object* obj, const Coord3D& goalPos, Real *relAngle) -{ - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real turnRate = getMaxTurnRate(bdt); - - PhysicsTurningType rotating = rotateObjAroundLocoPivot(obj, goalPos, turnRate, relAngle); - return rotating; -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::setPhysicsOptions(Object* obj) -{ - PhysicsBehavior *physics = obj->getPhysics(); - if (physics == NULL) - { - DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); - return; - } - - // crank up the friction in ultra-accurate mode to increase movement precision. - const Real EXTRA_FRIC = 0.5f; - Real extraExtraFriction = getFlag(ULTRA_ACCURATE) ? EXTRA_FRIC : 0.0f; - physics->setExtraFriction(m_template->m_extra2DFriction + extraExtraFriction); - physics->setAllowAirborneFriction(getApply2DFrictionWhenAirborne()); // you'd think we wouldn't want friction in the air, but it's needed for realistic behavior. - physics->setStickToGround(getStickToGround()); // walking guys aren't allowed to catch huge (or even small) air. -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalPos, - Real onPathDistToGoal, Real desiredSpeed, Bool *blocked) -{ - setFlag(MAINTAIN_POS_IS_VALID, false); - - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxSpeed = getMaxSpeedForCondition(bdt); - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - Real distToStopAtMaxSpeed = (maxSpeed/getBraking()) * (maxSpeed)/2.0f; - if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > distToStopAtMaxSpeed) - { - setFlag(IS_BRAKING, false); - m_brakingFactor = 1.0f; - } - PhysicsBehavior *physics = obj->getPhysics(); - if (physics == NULL) - { - DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); - return; - } - - // Skip moveTowardsPosition if physics say you're stunned - if(physics->getIsStunned()) - { - return; - } - -#ifdef DEBUG_OBJECT_ID_EXISTS -// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_moveTowardsPosition %f %f %f (dtg %f, spd %f), speed %f (%f)\n",goalPos.x,goalPos.y,goalPos.z,onPathDistToGoal,desiredSpeed,physics->getSpeed(),physics->getForwardSpeed2D())); -#endif - - // - // do not allow for invalid positions that the pathfinder cannot handle ... for airborne - // objects we don't need the pathfinder so we'll ignore this - // - if( BitIsSet( m_template->m_surfaces, LOCOMOTORSURFACE_AIR ) == false && - !TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, obj->getPosition()) && - !getFlag(ALLOW_INVALID_POSITION)) - { - // Somehow, we have gotten to an invalid location. - if (fixInvalidPosition(obj, physics)) - { - // the we adjusted us toward a legal position, so just return. - return; - } - } - - // If the actual distance is farther, then use the actual distance so we get there. - Real dx = goalPos.x - obj->getPosition()->x; - Real dy = goalPos.y - obj->getPosition()->y; - Real dz = goalPos.z - obj->getPosition()->z; - Real dist = sqrt(dx*dx+dy*dy); - if (dist>onPathDistToGoal) - { - if (!obj->isKindOf(KINDOF_PROJECTILE) && dist>2*onPathDistToGoal) - { - setFlag(IS_BRAKING, true); - } - onPathDistToGoal = dist; - } - - Coord3D nullAccel; - - Bool treatAsAirborne = false; - Coord3D pos = *obj->getPosition(); - Real heightAboveSurface = pos.z - TheTerrainLogic->getLayerHeight(pos.x, pos.y, obj->getLayer()); - - if( obj->getStatusBits().test( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) ) - { - heightAboveSurface -= obj->getCarrierDeckHeight(); - } - - if (heightAboveSurface > -(3*3)*TheGlobalData->m_gravity) - { - // If we get high enough to stay up for 3 frames, then we left the ground. - treatAsAirborne = true; - } - // We apply a zero acceleration to all units, as the call to - // applyMotiveForce flags an object as being "driven" by a locomotor, rather - // than being pushed around by objects bumping it. - nullAccel.x = nullAccel.y = nullAccel.z = 0; - physics->applyMotiveForce(&nullAccel); - - if (*blocked) - { - if (desiredSpeed > physics->getVelocityMagnitude()) - { - *blocked = false; - } - if (treatAsAirborne && BitIsSet( m_template->m_surfaces, LOCOMOTORSURFACE_AIR ) ) - { - // Airborne flying objects don't collide for now. jba. - *blocked = false; - } - } - - if (*blocked) - { - physics->scrubVelocity2D(desiredSpeed); // stop if we are about to run into the blocking object. - Real turnRate = getMaxTurnRate(obj->getBodyModule()->getDamageState()); - if (m_template->m_wanderWidthFactor == 0.0f) - { - *blocked = (TURN_NONE != rotateObjAroundLocoPivot(obj, goalPos, turnRate)); - } - - // it is very important to be sure to call this in all situations, even if not moving in 2d space. - handleBehaviorZ(obj, physics, goalPos); - return; - } - - if ( -// srj sez: I don't know why we didn't want HOVERs to allow to "brake". -// we actually really want them to, because it allows much more precise destination positioning. -// m_template->m_appearance == LOCO_HOVER || - m_template->m_appearance == LOCO_WINGS) - { - setFlag(IS_BRAKING, false); - } - - Bool wasBraking = obj->getStatusBits().test( OBJECT_STATUS_BRAKING ); - - physics->setTurning(TURN_NONE); - if (getAllowMotiveForceWhileAirborne() || !treatAsAirborne) - { - switch (m_template->m_appearance) - { - case LOCO_LEGS_TWO: - moveTowardsPositionLegs(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_CLIMBER: - moveTowardsPositionClimb(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_WHEELS_FOUR: - case LOCO_MOTORCYCLE: - moveTowardsPositionWheels( obj, physics, goalPos, onPathDistToGoal, desiredSpeed ); - break; - case LOCO_TREADS: - moveTowardsPositionTreads(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_HOVER: - moveTowardsPositionHover(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_WINGS: - moveTowardsPositionWings(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_THRUST: - moveTowardsPositionThrust(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_OTHER: - default: - moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - } - } - - handleBehaviorZ(obj, physics, goalPos); - // Objects that are braking don't follow the normal physics, so they end up at their destination exactly. - obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ), getFlag(IS_BRAKING) ); - - if (wasBraking) - { - #define MIN_VEL (PATHFIND_CELL_SIZE_F/(LOGICFRAMES_PER_SECOND)) - - Coord3D pos = *obj->getPosition(); - if (obj->isKindOf(KINDOF_PROJECTILE)) - { - // Projectiles never stop braking once they start. jba. - obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ) ); - // Projectiles cheat in 3 dimensions. - dist = sqrt(dx*dx+dy*dy+dz*dz); - Real vel = physics->getVelocityMagnitude(); - if (vel < MIN_VEL) - vel = MIN_VEL; - if (vel > dist) - vel = dist; // do not overcompensate! - // Normalize. - if (dist > 0.001f) - { - dist = 1.0f / dist; - dx *= dist; - dy *= dist; - dz *= dist; - - // DEBUG_LOG((">>> Locomotor Braking - d(xyz) = %f / %f / %f\n", dx * vel, dy * vel, dz * vel)); - - pos.x += dx * vel; - pos.y += dy * vel; - pos.z += dz * vel; - } - } - else - { - // not projectiles only cheat in x & y. - // Normalize. - if (dist > 0.001f) - { - Real vel = fabs(physics->getForwardSpeed2D()); - if (vel < MIN_VEL) - vel = MIN_VEL; - if (vel > dist) - vel = dist; // do not overcompensate! - dist = 1.0f / dist; - dx *= dist; - dy *= dist; - pos.x += dx * vel; - pos.y += dy * vel; - } - } - obj->setPosition(&pos); - } - -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxSpeed = getMaxSpeedForCondition(bdt); - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - Real maxAcceleration = getMaxAcceleration(bdt); - - // Locomotion for treaded vehicles, ie tanks. - - // - // Orient toward goal position - // -// Real angle = obj->getOrientation(); -// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); -// Real desiredAngle = angle + relAngle; - Real relAngle ; - PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos, &relAngle); - physics->setTurning(rotating); - - // - // Modulate speed according to turning. The more we have to turn, the slower we go - // - const Real QUAETERPI = PI / 4.0f; - Real angleCoeff = (Real)fabs( relAngle ) / QUAETERPI; - if (angleCoeff > 1.0f) - angleCoeff = 1.0; - - - Real dx = obj->getPosition()->x - goalPos.x; - Real dy = obj->getPosition()->y - goalPos.y; - - - Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; - - -// if (speed < m_minTurnSpeed) -// speed = m_minTurnSpeed; - - Real actualSpeed = physics->getForwardSpeed2D(); - Real slowDownTime = actualSpeed / getBraking(); - Real slowDownDist = (actualSpeed/1.50f) * slowDownTime; - - if (sqr(dx)+sqr(dy) 0.05) { - goalSpeed = actualSpeed*0.6f; - } - - if (onPathDistToGoal < slowDownDist && !getFlag(IS_BRAKING) && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - { - setFlag(IS_BRAKING, true); - m_brakingFactor = 1.1f; - } - - if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > 2.0*slowDownDist) - { - setFlag(IS_BRAKING, false); - } - - if (getFlag(IS_BRAKING)) - { - m_brakingFactor = slowDownDist/onPathDistToGoal; - m_brakingFactor *= m_brakingFactor; - if (m_brakingFactor>MAX_BRAKING_FACTOR) { - m_brakingFactor = MAX_BRAKING_FACTOR; - } - if (slowDownDist>onPathDistToGoal) { - goalSpeed = actualSpeed-getBraking(); - if (goalSpeed<0.0f) goalSpeed= 0.0f; - } else if (slowDownDist>onPathDistToGoal*0.75f) { - goalSpeed = actualSpeed-getBraking()/2.0f; - if (goalSpeed<0.0f) goalSpeed = 0.0f; - } else { - goalSpeed = actualSpeed; - } - } - - - //DEBUG_LOG(("Actual speed %f, Braking factor %f, slowDownDist %f, Pathdist %f, goalSpeed %f\n", - // actualSpeed, m_brakingFactor, slowDownDist, onPathDistToGoal, goalSpeed)); - - // - // Maintain goal speed - // - Real speedDelta = goalSpeed - actualSpeed; - if (speedDelta != 0.0f) - { - Real mass = physics->getMass(); - Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -m_brakingFactor*getBraking(); - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - const Coord3D *dir = obj->getUnitDirectionVector2D(); - - Coord3D force; - force.x = accelForce * dir->x; - force.y = accelForce * dir->y; - force.z = 0.0f; - - // apply forces to object - physics->applyMotiveForce( &force ); - } -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxSpeed = getMaxSpeedForCondition(bdt); - Real maxTurnRate = getMaxTurnRate(bdt); - Real maxAcceleration = getMaxAcceleration(bdt); - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - // Locomotion for wheeled vehicles, ie trucks. - // - // See if we are turning. If so, use the min turn speed. - // - Real turnSpeed = m_template->m_minTurnSpeed; - Real angle = obj->getOrientation(); -// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); -// Real desiredAngle = angle + relAngle; - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); - Real relAngle = stdAngleDiff(desiredAngle, angle); - - Bool moveBackwards = false; - - // Wheeled vehicles can only turn while moving, so make sure the turn speed is reasonable. - if (turnSpeed < maxSpeed/4.0f) - { - turnSpeed = maxSpeed/4.0f; - } - - - Real actualSpeed = physics->getForwardSpeed2D(); - Bool do3pointTurn = false; -#if 1 - if (actualSpeed==0.0f) { - setFlag(MOVING_BACKWARDS, false); - if (m_template->m_canMoveBackward && fabs(relAngle) > PI/2) { - setFlag(MOVING_BACKWARDS, true ); - setFlag(DOING_THREE_POINT_TURN, onPathDistToGoal>5*obj->getGeometryInfo().getMajorRadius()); - } - - } - if (getFlag(MOVING_BACKWARDS)) { - if (fabs(relAngle) < PI/2) { - moveBackwards = false; - setFlag(MOVING_BACKWARDS, false); - } else { - moveBackwards = true; - setFlag(DOING_THREE_POINT_TURN, onPathDistToGoal>5*obj->getGeometryInfo().getMajorRadius()); - do3pointTurn = getFlag(DOING_THREE_POINT_TURN); - if (!do3pointTurn) { - desiredAngle = stdAngleDiff(desiredAngle, PI); - relAngle = stdAngleDiff(desiredAngle, angle); - } - } - } -#endif - - const Real SMALL_TURN = PI / 20.0f; - if ((Real)fabs( relAngle ) > SMALL_TURN) - { - if (desiredSpeed>turnSpeed) - { - desiredSpeed = turnSpeed; - } - } - - Real goalSpeed = desiredSpeed; - if (moveBackwards) { - actualSpeed = -actualSpeed; - } - - - - Real slowDownTime = actualSpeed / getBraking() + 1.0f; - Real slowDownDist = (actualSpeed/1.5f) * slowDownTime + actualSpeed; - Real effectiveSlowDownDist = slowDownDist; - if (effectiveSlowDownDist < 1*PATHFIND_CELL_SIZE) { - effectiveSlowDownDist = 1*PATHFIND_CELL_SIZE; - } - - - const Real FIFTEEN_DEGREES = PI / 12.0f; - const Real PROJECT_FRAMES = LOGICFRAMES_PER_SECOND/2; // Project out 1/2 second. - if (fabs( relAngle ) > FIFTEEN_DEGREES) - { - // If we're turning more than 10 degrees, check & see if we're moving into "impassable territory" - Real distance = PROJECT_FRAMES * (goalSpeed+actualSpeed)/2.0f; - Real targetAngle = obj->getOrientation(); - Real turnFactor = ((goalSpeed+actualSpeed)/2.0f)/turnSpeed; - if (turnFactor > 1.0f) - turnFactor = 1.0f; - Real turnAmount = PROJECT_FRAMES*turnFactor*maxTurnRate/4.0f; - if (relAngle < 0) - { - targetAngle -= turnAmount; - } - else - { - targetAngle += turnAmount; - } - Coord3D offset; - offset.x = Cos(targetAngle)*distance; - offset.y = Sin(targetAngle)*distance; - offset.z = 0; - - const Coord3D* pos = obj->getPosition(); - - Coord3D nextPos; - nextPos.x = pos->x+offset.x; - nextPos.y = pos->y+offset.y; - nextPos.z = pos->z; - - pos = obj->getPosition(); - - Coord3D halfPos; - halfPos.x = pos->x+offset.x/2; - halfPos.y = pos->y+offset.y/2; - halfPos.z = pos->z; - - if (!TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &halfPos) || - !TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &nextPos)) - { - PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos); - physics->setTurning(rotating); - - // apply a zero force to object so that it acts "driven" - Coord3D force; - force.zero(); - physics->applyMotiveForce( &force ); - return; - } - - } - - if (onPathDistToGoal < effectiveSlowDownDist && !getFlag(IS_BRAKING) && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - { - setFlag(IS_BRAKING, true); - m_brakingFactor = 1.1f; - } - - - if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > 2.0*slowDownDist) - { - setFlag(IS_BRAKING, false); - } - - if (onPathDistToGoal > DONUT_DISTANCE) { - m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; - } else { - if (m_donutTimer < TheGameLogic->getFrame()) { - setFlag(IS_BRAKING, true); - } - } - - if (getFlag(IS_BRAKING)) - { - m_brakingFactor = slowDownDist/onPathDistToGoal; - m_brakingFactor *= m_brakingFactor; - if (m_brakingFactor>MAX_BRAKING_FACTOR) { - m_brakingFactor = MAX_BRAKING_FACTOR; - } - m_brakingFactor = 1.0f; - if (slowDownDist>onPathDistToGoal) { - goalSpeed = actualSpeed-getBraking(); - if (goalSpeed<0.0f) goalSpeed= 0.0f; - } else if (slowDownDist>onPathDistToGoal*0.75f) { - goalSpeed = actualSpeed-getBraking()/2.0f; - if (goalSpeed<0.0f) goalSpeed = 0.0f; - } else { - goalSpeed = actualSpeed; - } - } - - - //DEBUG_LOG(("Actual speed %f, Braking factor %f, slowDownDist %f, Pathdist %f, goalSpeed %f\n", - // actualSpeed, m_brakingFactor, slowDownDist, onPathDistToGoal, goalSpeed)); - - - // Wheeled can only turn while moving. - Real turnFactor = actualSpeed/turnSpeed; - if (turnFactor<0) { - turnFactor = -turnFactor; // in case we're sliding backwards in a 3 pt turn. - } - if (turnFactor > 1.0f) - turnFactor = 1.0f; - Real turnAmount = turnFactor*maxTurnRate; - - PhysicsTurningType rotating; - if (moveBackwards && !do3pointTurn) { - Coord3D backwardPos = *obj->getPosition(); - backwardPos.x += -(goalPos.x - obj->getPosition()->x); - backwardPos.y += -(goalPos.y - obj->getPosition()->y); - rotating = rotateObjAroundLocoPivot(obj, backwardPos, turnAmount); - } else { - rotating = rotateObjAroundLocoPivot(obj, goalPos, turnAmount); - } - - physics->setTurning(rotating); - - // - // Maintain goal speed - // - Real speedDelta = goalSpeed - actualSpeed; - if (moveBackwards) { - speedDelta = -goalSpeed+actualSpeed; - } - if (speedDelta != 0.0f) - { - Real mass = physics->getMass(); - Real acceleration; - if (moveBackwards) { - acceleration = (speedDelta < 0.0f) ? -maxAcceleration : m_brakingFactor*getBraking(); - } else { - acceleration = (speedDelta > 0.0f) ? maxAcceleration : -m_brakingFactor*getBraking(); - } - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - //DEBUG_LOG(("Braking %d, actualSpeed %f, goalSpeed %f, delta %f, accel %f\n", getFlag(IS_BRAKING), - //actualSpeed, goalSpeed, speedDelta, accelForce)); - - const Coord3D *dir = obj->getUnitDirectionVector2D(); - - Coord3D force; - force.x = accelForce * dir->x; - force.y = accelForce * dir->y; - force.z = 0.0f; - - // apply forces to object - physics->applyMotiveForce( &force ); - } - -} -//------------------------------------------------------------------------------------------------- -Bool Locomotor::fixInvalidPosition(Object* obj, PhysicsBehavior *physics) -{ - if (obj->isKindOf(KINDOF_DOZER)) { - // don't fix him. - return false; - } -#define no_IGNORE_INVALID -#ifdef IGNORE_INVALID - // Right now we ignore invalid positions, so when units clip the edge of a building or cliff - // they don't get stuck. jba. 12SEPT02 - return false; -#else - Int dx = 0; - Int dy = 0; - Int i, j; - for (j=-1; j<2; j++) { - for (i=-1; i<2; i++) { - Coord3D thePos = *obj->getPosition(); - thePos.x += i*PATHFIND_CELL_SIZE_F; - thePos.y += j*PATHFIND_CELL_SIZE_F; - if (!TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &thePos)) { - if (i<0) dx += 1; - if (i>0) dx -= 1; - if (j<0) dy += 1; - if (j>0) dy -= 1; - } - } - } - if (dx || dy) { - - Coord3D correction; - correction.x = dx*physics->getMass()/5; - correction.y = dy*physics->getMass()/5; - correction.z = 0; - - Coord3D correctionNormalized = correction; - correctionNormalized.normalize(); - - Coord3D velocity; - // Kill current velocity in the direction of the correction. - velocity = *physics->getVelocity(); - Real dot = (velocity.x*correctionNormalized.x) + (velocity.y*correctionNormalized.y); - if (dot>.25f) { - // It was already leaving. - return false; - } - - - // Kill current accel - //physics->clearAcceleration(); - - if (dot<0) { - dot = sqrt(-dot); - correctionNormalized.x *= dot*physics->getMass(); - correctionNormalized.y *= dot*physics->getMass(); - physics->applyMotiveForce(&correctionNormalized); - } - - // apply correction. - physics->applyMotiveForce(&correction); - return true; - } - return false; -#endif -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::calcMinTurnRadius(BodyDamageType condition, Real* timeToTravelThatDist) const -{ - Real minSpeed = getMinSpeed(); // in dist/frame - Real maxTurnRate = getMaxTurnRate(condition); // in rads/frame - - /* - our minimum circumference will be like so: - - Real minTurnCircum = maxSpeed * (2*PI / maxTurnRate); - - so therefore our minimum turn radius is: - - Real minTurnRadius = minTurnCircum / 2*PI; - - so we just eliminate the middleman: - */ - // if we can't turn, return a huge-but-finite radius rather than NAN... - Real minTurnRadius = (maxTurnRate > 0.0f) ? minSpeed / maxTurnRate : BIGNUM; - - if (timeToTravelThatDist) - *timeToTravelThatDist = (minSpeed > 0.0f) ? (minTurnRadius / minSpeed) : 0.0f; - - return minTurnRadius; -} - - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - if (getIsDownhillOnly() && obj->getPosition()->z < goalPos.z) - { - return; - } - - Real maxAcceleration = getMaxAcceleration( obj->getBodyModule()->getDamageState() ); - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState() ); - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - // Locomotion for infantry. - // - // Orient toward goal position - // - Real actualSpeed = physics->getForwardSpeed2D(); - Real angle = obj->getOrientation(); -// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); -// Real desiredAngle = angle + relAngle; - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); - - if (m_template->m_wanderWidthFactor != 0.0f) { - Real angleLimit = PI/8 * m_template->m_wanderWidthFactor; - // This is the wander offline code - it forces the desired angle away from the goal, so we wander back & forth. jba. - if (getFlag(OFFSET_INCREASING)) { - m_angleOffset += m_offsetIncrement*actualSpeed; - if (m_angleOffset > angleLimit) { - setFlag(OFFSET_INCREASING, false); - } - } else { - m_angleOffset -= m_offsetIncrement*actualSpeed; - if (m_angleOffset<-angleLimit) { - setFlag(OFFSET_INCREASING, true); - } - } - desiredAngle = normalizeAngle(desiredAngle+m_angleOffset); - } - - Real relAngle = stdAngleDiff(desiredAngle, angle); - locoUpdate_moveTowardsAngle(obj, desiredAngle); - - // - // Modulate speed according to turning. The more we have to turn, the slower we go - // - const Real QUARTERPI = PI / 4.0f; - Real angleCoeff = (Real)fabs( relAngle ) / (QUARTERPI); - if (angleCoeff > 1.0f) - angleCoeff = 1.0; - - Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; - - //Real slowDownDist = (actualSpeed - m_template->m_minSpeed) / getBraking(); - Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); - if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - { - goalSpeed = m_template->m_minSpeed; - } - - - - // - // Maintain goal speed - // - Real speedDelta = goalSpeed - actualSpeed; - if (speedDelta != 0.0f) - { - Real mass = physics->getMass(); - Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - const Coord3D *dir = obj->getUnitDirectionVector2D(); - - Coord3D force; - force.x = accelForce * dir->x; - force.y = accelForce * dir->y; - force.z = 0.0f; - - - - // apply forces to object - physics->applyMotiveForce( &force ); - } -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - Real maxAcceleration = getMaxAcceleration( obj->getBodyModule()->getDamageState() ); - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState() ); - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - // Locomotion for climbing infantry. - - - Bool moveBackwards = false; - - Real dx, dy, dz; - - Coord3D pos = *obj->getPosition(); - - dx = pos.x - goalPos.x; - dy = pos.y - goalPos.y; - dz = pos.z - goalPos.z; - if (dz*dz > sqr(PATHFIND_CELL_SIZE_F)) { - setFlag(CLIMBING, true); - } - if (fabs(dz)<1) { - setFlag(CLIMBING, false); - } - - - //setFlag(CLIMBING, true); - - if (getFlag(CLIMBING)) { - Coord3D delta = goalPos; - delta.x -= pos.x; - delta.y -= pos.y; - delta.z = 0; - delta.normalize(); - delta.x += pos.x; - delta.y += pos.y; - delta.z = TheTerrainLogic->getGroundHeight(delta.x, delta.y); - if (delta.z < pos.z-0.1) { - moveBackwards = true; - } - - Real groundSlope = fabs(delta.z - pos.z); - if (groundSlope<1.0f) groundSlope = 1.0f; - - if (groundSlope>1.0f) { - desiredSpeed /= groundSlope*4; - } - } - setFlag(MOVING_BACKWARDS, moveBackwards); - - // - // Orient toward goal position - // - Real angle = obj->getOrientation(); -// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); -// Real desiredAngle = angle + relAngle; - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); - Real relAngle = stdAngleDiff(desiredAngle, angle); - - if (moveBackwards) { - desiredAngle = stdAngleDiff(desiredAngle, PI); - relAngle = stdAngleDiff(desiredAngle, angle); - } - - locoUpdate_moveTowardsAngle(obj, desiredAngle); - - // - // Modulate speed according to turning. The more we have to turn, the slower we go - // - const Real QUARTERPI = PI / 4.0f; - Real angleCoeff = (Real)fabs( relAngle ) / (QUARTERPI); - if (angleCoeff > 1.0f) - angleCoeff = 1.0; - - Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; - - Real actualSpeed = physics->getForwardSpeed2D(); - - if (moveBackwards) { - actualSpeed = -actualSpeed; - } - - //Real slowDownDist = (actualSpeed - m_template->m_minSpeed) / getBraking(); - Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); - if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - { - goalSpeed = m_template->m_minSpeed; - } - - // - // Maintain goal speed - // - Real speedDelta = goalSpeed - actualSpeed; - if (moveBackwards) { - speedDelta = -goalSpeed+actualSpeed; - } - if (speedDelta != 0.0f) - { - Real mass = physics->getMass(); - Real acceleration; - if (moveBackwards) { - acceleration = (speedDelta < 0.0f) ? -maxAcceleration : getBraking(); - } else { - acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); - } - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - const Coord3D *dir = obj->getUnitDirectionVector2D(); - - Coord3D force; - force.x = accelForce * dir->x; - force.y = accelForce * dir->y; - force.z = 0.0f; - - // apply forces to object - physics->applyMotiveForce( &force ); - } -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ -#ifdef CIRCLE_FOR_LANDING - if (m_circleThresh > 0.0f) - { - // if we are going a mostly-vertical maneuver, circle in order to - // gain/lose altitude, then resume course... - const Coord3D* pos = obj->getPosition(); - Real dx = goalPos.x - pos->x; - Real dy = goalPos.y - pos->y; - Real dz = goalPos.z - pos->z; - if (fabs(dz) > m_circleThresh) - { - // aim for the spot on the opposite side of the circle. - - // find the direction towards our goal pos - Real angleTowardPos = - (isNearlyZero(dx) && isNearlyZero(dy)) ? - obj->getOrientation() : - atan2(dy, dx); - - Real aimDir = (PI - PI/8); - angleTowardPos += aimDir; - - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real turnRadius = calcMinTurnRadius(bdt, NULL) * 4; - - // project a spot "radius" dist away from it, in that dir - Coord3D desiredPos = goalPos; - desiredPos.x += Cos(angleTowardPos) * turnRadius; - desiredPos.y += Sin(angleTowardPos) * turnRadius; - moveTowardsPositionOther(obj, physics, desiredPos, 0, desiredSpeed); - return; - } - } -#endif - - // handle the 2D component. - moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionHover(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - // handle the 2D component. - moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - - // Only hover locomotors care about their OverWater special effects. (OverWater also affects speed, so this is not a client thing) - Coord3D newPosition = *obj->getPosition(); - if( TheTerrainLogic->isUnderwater( newPosition.x, newPosition.y ) ) - { - if( ! getFlag( OVER_WATER ) ) - { - // Change my model condition because I used to not be over water, but now I am - setFlag( OVER_WATER, TRUE ); - obj->setModelConditionState( MODELCONDITION_OVER_WATER ); - } - } - else - { - if( getFlag( OVER_WATER ) ) - { - // Here, I was, but now I'm not - setFlag( OVER_WATER, FALSE ); - obj->clearModelConditionState( MODELCONDITION_OVER_WATER ); - } - } -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - - Real maxForwardSpeed = getMaxSpeedForCondition(bdt); - desiredSpeed = clamp(m_template->m_minSpeed, desiredSpeed, maxForwardSpeed); - Real actualForwardSpeed = physics->getForwardSpeed3D(); - - if (getBraking() > 0) - { - //Real slowDownDist = (actualForwardSpeed - m_template->m_minSpeed) / getBraking(); - Real slowDownDist = calcSlowDownDist(actualForwardSpeed, m_template->m_minSpeed, getBraking()); - if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - desiredSpeed = m_template->m_minSpeed; - } - - Coord3D localGoalPos = goalPos; -#ifdef USE_ZDIR_DAMPING - Real zDirDamping = 0.0f; -#endif - - //out of the handleBehaviorZ() function - Coord3D pos = *obj->getPosition(); - if( m_preferredHeight != 0.0f && !getFlag(PRECISE_Z_POS) ) - { - // If we have a preferred flight height, and we haven't been told explicitly to ignore it... - Real surfaceHt = getSurfaceHtAtPt(pos.x, pos.y); - localGoalPos.z = m_preferredHeight + surfaceHt; -// localGoalPos.z = goalPos.z; - Real delta = localGoalPos.z - pos.z; - delta *= getPreferredHeightDamping(); - localGoalPos.z = pos.z + delta; - -#ifdef USE_ZDIR_DAMPING - // closer we get to the preferred height, less we adjust z-thrust, - // so we tend to "level out" at that height. we don't use this till - // below, but go ahead and calc it now... - Real MAX_VERTICAL_DAMP_RANGE = m_preferredHeight * 0.5; - delta = fabs(delta); - if (delta > MAX_VERTICAL_DAMP_RANGE) - delta = MAX_VERTICAL_DAMP_RANGE; - zDirDamping = 1.0f - (delta / MAX_VERTICAL_DAMP_RANGE); -#endif - } - - Vector3 forwardDir = obj->getTransformMatrix()->Get_X_Vector(); - - // Maintain goal speed - Real forwardSpeedDelta = desiredSpeed - actualForwardSpeed; - Real maxAccel = (forwardSpeedDelta > 0.0f || getBraking() == 0) ? getMaxAcceleration(bdt) : -getBraking(); - Real maxTurnRate = getMaxTurnRate(bdt); - - // what direction do we need to thrust in, in order to reach the goalpos? - Vector3 desiredThrustDir; - calcDirectionToApplyThrust(obj, physics, localGoalPos, maxAccel, desiredThrustDir); - - // we might not be able to thrust in that dir, so thrust as closely as we can - Real maxThrustAngle = (maxTurnRate > 0) ? (m_template->m_maxThrustAngle) : 0; - Vector3 thrustDir; - Real thrustAngle = tryToRotateVector3D(maxThrustAngle, forwardDir, desiredThrustDir, thrustDir); - - // note that we are trying to orient in the direction of our vel, not the dir of our thrust. - if (!isNearlyZero(physics->getVelocityMagnitude())) - { - const Coord3D* veltmp = physics->getVelocity(); - Vector3 vel(veltmp->x, veltmp->y, veltmp->z); - Bool adjust = true; - if( obj->getStatusBits().test( OBJECT_STATUS_BRAKING ) ) - { - //Real closeInDist = 150.0f; // TODO: get/set this from missileAI? - //Real af = 1.0f - __min((onPathDistToGoal / closeInDist), 1.0); - - //if (af > 0.0f) { - - // vel.Set( - // vel.X * (1.0f - af) + (goalPos.x - pos.x) * af, - // vel.Y * (1.0f - af) + (goalPos.y - pos.y) * af, - // vel.Z * (1.0f - af) + (goalPos.z - pos.z) * af - // ); - // if (isNearlyZero(sqr(vel.X) + sqr(vel.Y) + sqr(vel.Z))) { - // // we are at target. - // adjust = false; - // } - // maxTurnRate = (1.0f + (af * 2.0f) ) * maxTurnRate; - //} - - // DEBUG_LOG((">>> moveTowardsPositionThrust - Braking - maxTurnRate = %f\n", maxTurnRate)); - - // align to target, cause that's where we're going anyway. - - vel.Set(goalPos.x - pos.x, goalPos.y-pos.y, goalPos.z-pos.z); - if (isNearlyZero(sqr(vel.X)+sqr(vel.Y)+sqr(vel.Z))) { - // we are at target. - adjust = false; - } - maxTurnRate = 3*maxTurnRate; - } -#ifdef USE_ZDIR_DAMPING - if (zDirDamping != 0.0f) - { - Vector3 vel2D(veltmp->x, veltmp->y, 0); - // no need to normalize -- this call does that internally - tryToRotateVector3D(-zDirDamping, vel, vel2D, vel); - } -#endif - if (adjust) { - /*Real orient =*/ tryToOrientInThisDirection3D(obj, maxTurnRate, vel); - } - } - - if (forwardSpeedDelta != 0.0f || thrustAngle != 0.0f) - { - if (maxForwardSpeed <= 0.0f) - { - maxForwardSpeed = 0.01f; // In some cases, this is 0, hack for now. jba. - } - Real damping = clamp(0.0f, maxAccel / maxForwardSpeed, 1.0f); - Vector3 curVel(physics->getVelocity()->x, physics->getVelocity()->y, physics->getVelocity()->z); - - Vector3 accelVec = thrustDir * maxAccel - curVel * damping; - //DEBUG_LOG(("accel %f (max %f) vel %f (max %f) damping %f\n",accelVec.Length(),maxAccel,curVel.Length(),maxForwardSpeed,damping)); - - Real mass = physics->getMass(); - - Coord3D force; - force.x = mass * accelVec.X; - force.y = mass * accelVec.Y; - force.z = mass * accelVec.Z; - - // apply forces to object - physics->applyMotiveForce( &force ); - } -} - -//------------------------------------------------------------------------------------------------- -/*static*/ Real Locomotor::getSurfaceHtAtPt(Real x, Real y) -{ - Real ht = 0; - - Real z,waterZ; - if (TheTerrainLogic->isUnderwater(x, y, &waterZ, &z)) { - ht += waterZ; - } else { - ht += z; - } - - return ht; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::calcLiftToUseAtPt(Object* obj, PhysicsBehavior *physics, Real curZ, Real surfaceAtPt, Real preferredHeight) -{ - /* - take the classic equation: - - x = x0 + v*t + 0.5*a*t^2 - - and solve for acceleration. - */ - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxGrossLift = getMaxLift(bdt); - Real maxNetLift = maxGrossLift + TheGlobalData->m_gravity; // note that gravity is always negative. - if (maxNetLift < 0) - maxNetLift = 0; - Real curVelZ = physics->getVelocity()->z; - // going down, braking is limited by net lift; going up, braking is limited by gravity - Real maxAccel; - if (getFlag(ULTRA_ACCURATE)) - maxAccel = (curVelZ < 0) ? 2*maxNetLift : -2*maxNetLift; - else - maxAccel = (curVelZ < 0) ? maxNetLift : TheGlobalData->m_gravity; - // see how far we need to slow to dead stop, given max braking - Real desiredAccel; - const Real TINY_ACCEL = 0.001f; - if (fabs(maxAccel) > TINY_ACCEL) - { - Real deltaZ = preferredHeight - curZ; - // calc how far it will take for us to go from cur speed to zero speed, at max accel. - // Real brakeDist = calcSlowDownDist(curVelZ, 0, maxAccel); - // in theory, the above is the correct calculation, but in practice, - // doesn't work in some situations (eg, opening of USA01 map). Why, I dunno. - // But for now I have gone back to the old, looks-incorrect-to-me-but-works calc. (srj) - Real brakeDist = (sqr(curVelZ) / fabs(maxAccel)); - if (fabs(brakeDist) > fabs(deltaZ)) - { - // if the dist-to-accel (or dist-to-brake) is further than the dist-to-go, - // use the max accel. - desiredAccel = maxAccel; - } - else if (fabs(curVelZ) > m_template->m_speedLimitZ) - { - // or, if we're going too fast, limit it here. - desiredAccel = m_template->m_speedLimitZ - curVelZ; - } - else - { - // ok, figure out the correct accel to use to get us there at zero. - // - // dz = v t + 0.5 a t^2 - // thus - // a = 2(dz - v t)/t^2 - // and - // t = (-v +- sqrt(v*v + 2*a*dz))/a - // - // but if we assume t=1, then - // a=2(dz-v) - // then, plug it back in and see if t is really 1... - desiredAccel = 2.0f * (deltaZ - curVelZ); - } - } - else - { - desiredAccel = 0.0f; - } - Real liftToUse = desiredAccel - TheGlobalData->m_gravity; - if (getFlag(ULTRA_ACCURATE)) - { - // in ultra-accurate mode, we allow cheating. - const Real UP_FACTOR = 3.0f; - if (liftToUse > UP_FACTOR*maxGrossLift) - liftToUse = UP_FACTOR*maxGrossLift; - // srj sez: we used to clip lift to zero here (not allowing neg lift). - // however, I now think that allowing neg lift in ultra-accurate mode is - // a good and desirable thing; in particular, it enables jets to complete - // "short" landings more accurately (previously they sometimes would "float" - // down, which sucked.) if you need to bump this back to zero, check it carefully... - else if (liftToUse < -maxGrossLift) - liftToUse = -maxGrossLift; - } - else - { - if (liftToUse > maxGrossLift) - liftToUse = maxGrossLift; - else if (liftToUse < 0.0f) - liftToUse = 0.0f; - } - - return liftToUse; -} - -//------------------------------------------------------------------------------------------------- -PhysicsTurningType Locomotor::rotateObjAroundLocoPivot(Object* obj, const Coord3D& goalPos, - Real maxTurnRate, Real *relAngle) -{ - Real angle = obj->getOrientation(); - Real offset = getTurnPivotOffset(); - - PhysicsTurningType turn = TURN_NONE; - - if (getFlag(IS_BRAKING)) offset = 0.0f; // When braking we do exact movement towards goal, instead of physics. - //Rotating about pivot moves the object, and can make us miss our goal, so it is disabled. jba. - if (offset != 0.0f) - { - Real radius = obj->getGeometryInfo().getBoundingCircleRadius(); - Real turnPointOffset = offset * radius; - - Coord3D turnPos = *obj->getPosition(); - const Coord3D* dir = obj->getUnitDirectionVector2D(); - turnPos.x += dir->x * turnPointOffset; - turnPos.y += dir->y * turnPointOffset; - Real dx =goalPos.x - turnPos.x; - Real dy = goalPos.y - turnPos.y; - // If we are very close to the goal, we twitch due to rounding error. So just return. jba. - if (fabs(dx)<0.1f && fabs(dy)<0.1f) return TURN_NONE; - Real desiredAngle = atan2(dy, dx); - Real amount = stdAngleDiff(desiredAngle, angle); - if (relAngle) *relAngle = amount; - if (amount>maxTurnRate) { - amount = maxTurnRate; - turn = TURN_POSITIVE; - } else if (amount < -maxTurnRate) { - amount = -maxTurnRate; - turn = TURN_NEGATIVE; - } else { - turn = TURN_NONE; - } - -#if 0 - Coord3D desiredPos = *obj->getPosition(); // well, desired Dir, anyway - desiredPos.x += Cos(angle + amount) * radius; - desiredPos.y += Sin(angle + amount) * radius; - - - // so, the thing is, we want to rotate ourselves so that our *center* is rotated - // by the given amount, but the rotation must be around turnPos. so do a little - // back-calculation. - Real angleDesiredForTurnPos = atan2(desiredPos.y - turnPos.y, desiredPos.x - turnPos.x); - amount = angleDesiredForTurnPos - angle; -#endif - /// @todo srj -- there's probably a more efficient & more direct way to do this. find it. - Matrix3D mtx; - Matrix3D tmp(1); - tmp.Translate(turnPos.x, turnPos.y, 0); - tmp.In_Place_Pre_Rotate_Z(amount); - tmp.Translate(-turnPos.x, -turnPos.y, 0); - - mtx.mul(tmp, *obj->getTransformMatrix()); - - obj->setTransformMatrix(&mtx); - } - else - { - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); - Real amount = stdAngleDiff(desiredAngle, angle); - if (relAngle) *relAngle = amount; - if (amount>maxTurnRate) { - amount = maxTurnRate; - turn = TURN_POSITIVE; - } else if (amount < -maxTurnRate) { - amount = -maxTurnRate; - turn = TURN_NEGATIVE; - } else { - turn = TURN_NONE; - } - obj->setOrientation( normalizeAngle(angle + amount) ); - } - return turn; -} - -//------------------------------------------------------------------------------------------------- -/* - return true if we can maintain the position without being called every frame (eg, we are - resting on the ground), false if not (eg, we are hovering or circling) -*/ -Bool Locomotor::handleBehaviorZ(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos) -{ - Bool requiresConstantCalling = TRUE; - - // keep the agent aligned on the terrain - switch(m_template->m_behaviorZ) - { - case Z_NO_Z_MOTIVE_FORCE: - // nothing to do. - requiresConstantCalling = FALSE; - break; - - case Z_SEA_LEVEL: - requiresConstantCalling = TRUE; - if( !obj->isDisabledByType( DISABLED_HELD ) ) - { - Coord3D pos = *obj->getPosition(); - Real waterZ; - if (TheTerrainLogic->isUnderwater(pos.x, pos.y, &waterZ)) { - pos.z = waterZ; - } else { - pos.z = TheTerrainLogic->getLayerHeight(pos.x, pos.y, obj->getLayer()); - } - obj->setPosition(&pos); - } - break; - - case Z_FIXED_SURFACE_RELATIVE_HEIGHT: - case Z_FIXED_ABSOLUTE_HEIGHT: - requiresConstantCalling = TRUE; - { - Coord3D pos = *obj->getPosition(); - Bool surfaceRel = (m_template->m_behaviorZ == Z_FIXED_SURFACE_RELATIVE_HEIGHT); - Real surfaceHt = surfaceRel ? getSurfaceHtAtPt(pos.x, pos.y) : 0.0f; - pos.z = m_preferredHeight + (surfaceRel ? surfaceHt : 0); - obj->setPosition(&pos); - } - break; - - case Z_RELATIVE_TO_GROUND_AND_BUILDINGS: - requiresConstantCalling = TRUE; - { - // srj sez: use getGroundOrStructureHeight(), because someday it will cache building heights... - Coord3D pos = *obj->getPosition(); - Real surfaceHt = ThePartitionManager->getGroundOrStructureHeight(pos.x, pos.y); - - pos.z = m_preferredHeight + surfaceHt; - - obj->setPosition(&pos); - - } - break; - case Z_SMOOTH_RELATIVE_TO_HIGHEST_LAYER: - requiresConstantCalling = TRUE; - { - if (m_preferredHeight != 0.0f || getFlag(PRECISE_Z_POS)) - { - Coord3D pos = *obj->getPosition(); - - // srj sez: if we aren't on the ground, never find the ground layer - PathfindLayerEnum layerAtDest = obj->getLayer(); - if (layerAtDest == LAYER_GROUND) - layerAtDest = TheTerrainLogic->getHighestLayerForDestination( &pos ); - - Real surfaceHt; - Coord3D normal; - const Bool clip = false; // return the height, even if off the edge of the bridge proper. - surfaceHt = TheTerrainLogic->getLayerHeight( pos.x, pos.y, layerAtDest, &normal, clip ); - - Real preferredHeight = m_preferredHeight + surfaceHt; - if (getFlag(PRECISE_Z_POS)) - preferredHeight = goalPos.z; - - Real delta = preferredHeight - pos.z; - delta *= getPreferredHeightDamping(); - preferredHeight = pos.z + delta; - - Real liftToUse = calcLiftToUseAtPt(obj, physics, pos.z, surfaceHt, preferredHeight); - - //DEBUG_LOG(("HandleBZ %d LiftToUse %f\n",TheGameLogic->getFrame(),liftToUse)); - if (liftToUse != 0.0f) - { - Coord3D force; - force.x = 0.0f; - force.y = 0.0f; - force.z = liftToUse * physics->getMass(); - physics->applyMotiveForce(&force); - } - } - } - break; - - case Z_SURFACE_RELATIVE_HEIGHT: - case Z_ABSOLUTE_HEIGHT: - requiresConstantCalling = TRUE; - { - if (m_preferredHeight != 0.0f || getFlag(PRECISE_Z_POS)) - { - Coord3D pos = *obj->getPosition(); - - Bool surfaceRel = (m_template->m_behaviorZ == Z_SURFACE_RELATIVE_HEIGHT); - Real surfaceHt = surfaceRel ? getSurfaceHtAtPt(pos.x, pos.y) : 0.0f; - Real preferredHeight = m_preferredHeight + (surfaceRel ? surfaceHt : 0); - if (getFlag(PRECISE_Z_POS)) - preferredHeight = goalPos.z; - - Real delta = preferredHeight - pos.z; - delta *= getPreferredHeightDamping(); - preferredHeight = pos.z + delta; - - Real liftToUse = calcLiftToUseAtPt(obj, physics, pos.z, surfaceHt, preferredHeight); - - //DEBUG_LOG(("HandleBZ %d LiftToUse %f\n",TheGameLogic->getFrame(),liftToUse)); - if (liftToUse != 0.0f) - { - Coord3D force; - force.x = 0.0f; - force.y = 0.0f; - force.z = liftToUse * physics->getMass(); - physics->applyMotiveForce(&force); - } - } - } - break; - } - - return requiresConstantCalling; -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxAcceleration = getMaxAcceleration(bdt); - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - Real maxSpeed = getMaxSpeedForCondition(bdt); - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - Real goalSpeed = desiredSpeed; - Real actualSpeed = physics->getForwardSpeed2D(); - - // Locomotion for other things, ie don't know what it is jba :) - // - // Orient toward goal position - // exception: if very close (ie, we could get there in 2 frames or less),\ - // and ULTRA_ACCURATE, just slide into place - // - const Coord3D* pos = obj->getPosition(); - Coord3D dirToApplyForce = *obj->getUnitDirectionVector2D(); - -//DEBUG_ASSERTLOG(!getFlag(ULTRA_ACCURATE),("thresh %f %f (%f %f)\n", -//fabs(goalPos.y - pos->y),fabs(goalPos.x - pos->x), -//fabs(goalPos.y - pos->y)/goalSpeed,fabs(goalPos.x - pos->x)/goalSpeed)); - if (getFlag(ULTRA_ACCURATE) && - fabs(goalPos.y - pos->y) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor && - fabs(goalPos.x - pos->x) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor) - { - // don't turn, just slide in the right direction - physics->setTurning(TURN_NONE); - dirToApplyForce.x = goalPos.x - pos->x; - dirToApplyForce.y = goalPos.y - pos->y; - dirToApplyForce.z = 0.0f; - dirToApplyForce.normalize(); - } - else - { - PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos); - physics->setTurning(rotating); - } - - if (!getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - { - Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); - if (onPathDistToGoal < slowDownDist) - { - goalSpeed = m_template->m_minSpeed; - } - } - - // - // Maintain goal speed - // - Real speedDelta = goalSpeed - actualSpeed; - if (speedDelta != 0.0f) - { - Real mass = physics->getMass(); - Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - Coord3D force; - force.x = accelForce * dirToApplyForce.x; - force.y = accelForce * dirToApplyForce.y; - force.z = 0.0f; - - // apply forces to object - physics->applyMotiveForce( &force ); - } - -} - - -//------------------------------------------------------------------------------------------------- -/* - return true if we can maintain the position without being called every frame (eg, we are - resting on the ground), false if not (eg, we are hovering or circling) -*/ -Bool Locomotor::locoUpdate_maintainCurrentPosition(Object* obj) -{ - if (!getFlag(MAINTAIN_POS_IS_VALID)) - { - m_maintainPos = *obj->getPosition(); - setFlag(MAINTAIN_POS_IS_VALID, true); - } - - m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; - setFlag(IS_BRAKING, false); - PhysicsBehavior *physics = obj->getPhysics(); - if (physics == NULL) - { - DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); - return TRUE; - } - -#ifdef DEBUG_OBJECT_ID_EXISTS -// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_maintainCurrentPosition %f %f %f, speed %f (%f)\n",m_maintainPos.x,m_maintainPos.y,m_maintainPos.z,physics->getSpeed(),physics->getForwardSpeed2D())); -#endif - - Bool requiresConstantCalling = TRUE; // assume the worst. - switch (m_template->m_appearance) - { - case LOCO_THRUST: - maintainCurrentPositionThrust(obj, physics); - requiresConstantCalling = TRUE; - break; - case LOCO_LEGS_TWO: - maintainCurrentPositionLegs(obj, physics); - requiresConstantCalling = FALSE; - break; - case LOCO_CLIMBER: - maintainCurrentPositionLegs(obj, physics); - requiresConstantCalling = FALSE; - break; - case LOCO_WHEELS_FOUR: - case LOCO_MOTORCYCLE: - maintainCurrentPositionWheels(obj, physics); - requiresConstantCalling = FALSE; - break; - case LOCO_TREADS: - maintainCurrentPositionTreads(obj, physics); - requiresConstantCalling = FALSE; - break; - case LOCO_HOVER: - maintainCurrentPositionHover(obj, physics); - requiresConstantCalling = TRUE; - break; - case LOCO_WINGS: - maintainCurrentPositionWings(obj, physics); - requiresConstantCalling = TRUE; - break; - case LOCO_OTHER: - default: - maintainCurrentPositionOther(obj, physics); - requiresConstantCalling = TRUE; - break; - } - - // but we do need to do this even if not moving, for hovering/Thrusting things. - if (handleBehaviorZ(obj, physics, m_maintainPos)) - requiresConstantCalling = TRUE; - - return requiresConstantCalling; -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::maintainCurrentPositionThrust(Object* obj, PhysicsBehavior *physics) -{ - DEBUG_ASSERTCRASH(getFlag(MAINTAIN_POS_IS_VALID), ("invalid maintain pos")); - /// @todo srj -- should these also use the "circling radius" stuff, like wings? - moveTowardsPositionThrust(obj, physics, m_maintainPos, 0, getMinSpeed()); -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physics) -{ - DEBUG_ASSERTCRASH(getFlag(MAINTAIN_POS_IS_VALID), ("invalid maintain pos")); - physics->setTurning(TURN_NONE); - if (physics->isMotive() && obj->isAboveTerrain()) // no need to stop something that isn't moving (or is just sitting on the ground) - { - - // aim for the spot on the opposite side of the circle. - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real turnRadius = m_template->m_circlingRadius; - if (turnRadius == 0.0f) - turnRadius = calcMinTurnRadius(bdt, NULL); - - // find the direction towards our "maintain pos" - const Coord3D* pos = obj->getPosition(); - Real dx = m_maintainPos.x - pos->x; - Real dy = m_maintainPos.y - pos->y; - Real angleTowardMaintainPos = - (isNearlyZero(dx) && isNearlyZero(dy)) ? - obj->getOrientation() : - atan2(dy, dx); - - Real aimDir = (PI - PI/8); - if (turnRadius < 0) - { - turnRadius = -turnRadius; - aimDir = -aimDir; - } - angleTowardMaintainPos += aimDir; - - // project a spot "radius" dist away from it, in that dir - Coord3D desiredPos = m_maintainPos; - desiredPos.x += Cos(angleTowardMaintainPos) * turnRadius; - desiredPos.y += Sin(angleTowardMaintainPos) * turnRadius; - moveTowardsPositionWings(obj, physics, desiredPos, 0, m_template->m_minSpeed); - } -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physics) -{ - physics->setTurning(TURN_NONE); - if (physics->isMotive()) // no need to stop something that isn't moving. - { - DEBUG_ASSERTCRASH(m_template->m_minSpeed == 0.0f, ("HOVER should always have zero minSpeeds (otherwise, they WING)")); - - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxAcceleration = getMaxAcceleration(bdt); - Real actualSpeed = physics->getForwardSpeed2D(); - // - // Stop - // - Real minSpeed = max( 1.0E-10f, m_template->m_minSpeed ); - Real speedDelta = minSpeed - actualSpeed; - if (fabs(speedDelta) > minSpeed) - { - Real mass = physics->getMass(); - Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - const Coord3D *dir = obj->getUnitDirectionVector2D(); - - Coord3D force; - force.x = accelForce * dir->x; - force.y = accelForce * dir->y; - force.z = 0.0f; - - - // Apply a random kick (if applicable) to dirty-up visually. - // The idea is that chopper pilots have to do course corrections all the time - // Because of changes in wind, pressure, etc. - // Those changes are added here, then the - - - - // apply forces to object - physics->applyMotiveForce( &force ); - } - } - -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::maintainCurrentPositionOther(Object* obj, PhysicsBehavior *physics) -{ - - physics->setTurning(TURN_NONE); - if (physics->isMotive()) // no need to stop something that isn't moving. - { - physics->scrubVelocity2D(0); // stop. - } - -} - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- -LocomotorSet::LocomotorSet() -{ - m_locomotors.clear(); - m_validLocomotorSurfaces = 0; - m_downhillOnly = FALSE; - -} - -//------------------------------------------------------------------------------------------------- -LocomotorSet::LocomotorSet(const LocomotorSet& that) -{ - DEBUG_CRASH(("unimplemented")); -} - -//------------------------------------------------------------------------------------------------- -LocomotorSet& LocomotorSet::operator=(const LocomotorSet& that) -{ - if (this != &that) - { - DEBUG_CRASH(("unimplemented")); - } - return *this; -} - -//------------------------------------------------------------------------------------------------- -LocomotorSet::~LocomotorSet() -{ - clear(); -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void LocomotorSet::crc( Xfer *xfer ) -{ - -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void LocomotorSet::xfer( Xfer *xfer ) -{ - // version - const XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // count of vector - UnsignedShort count = m_locomotors.size(); - xfer->xferUnsignedShort( &count ); - - // data - if (xfer->getXferMode() == XFER_SAVE) - { - for (LocomotorVector::iterator it = m_locomotors.begin(); it != m_locomotors.end(); ++it) - { - Locomotor* loco = *it; - AsciiString name = loco->getTemplateName(); - xfer->xferAsciiString(&name); - xfer->xferSnapshot(loco); - } - } - else if (xfer->getXferMode() == XFER_LOAD) - { - // vector should be empty at this point - if (m_locomotors.empty() == FALSE) - { - DEBUG_CRASH(( "LocomotorSet::xfer - vector is not empty, but should be\n" )); - throw XFER_LIST_NOT_EMPTY; - } - - for (UnsignedShort i = 0; i < count; ++i) - { - AsciiString name; - xfer->xferAsciiString(&name); - - const LocomotorTemplate* lt = TheLocomotorStore->findLocomotorTemplate(NAMEKEY(name)); - if (lt == NULL) - { - DEBUG_CRASH(( "LocomotorSet::xfer - template %s not found\n", name.str() )); - throw XFER_UNKNOWN_STRING; - } - - Locomotor* loco = TheLocomotorStore->newLocomotor(lt); - xfer->xferSnapshot(loco); - m_locomotors.push_back(loco); - } - } - - xfer->xferInt(&m_validLocomotorSurfaces); - xfer->xferBool(&m_downhillOnly); - -} - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void LocomotorSet::loadPostProcess( void ) -{ - -} // end loadPostProcess - -//------------------------------------------------------------------------------------------------- -void LocomotorSet::xferSelfAndCurLocoPtr(Xfer *xfer, Locomotor** loco) -{ - xfer->xferSnapshot(this); - - if (xfer->getXferMode() == XFER_SAVE) - { - AsciiString name; - if (*loco) - name = (*loco)->getTemplateName(); - xfer->xferAsciiString(&name); - } - else if (xfer->getXferMode() == XFER_LOAD) - { - AsciiString name; - xfer->xferAsciiString(&name); - - if (name.isEmpty()) - { - *loco = NULL; - } - else - { - for (int i = 0; i < m_locomotors.size(); ++i) - { - if (m_locomotors[i]->getTemplateName() == name) - { - *loco = m_locomotors[i]; - return; - } - } - - DEBUG_CRASH(( "LocomotorSet::xfer - template %s not found\n", name.str() )); - throw XFER_UNKNOWN_STRING; - } - } -} - -//------------------------------------------------------------------------------------------------- -void LocomotorSet::clear() -{ - for (int i = 0; i < m_locomotors.size(); ++i) - { - if (m_locomotors[i]) - m_locomotors[i]->deleteInstance(); - } - m_locomotors.clear(); - m_validLocomotorSurfaces = 0; - m_downhillOnly = FALSE; -} - -//------------------------------------------------------------------------------------------------- -void LocomotorSet::addLocomotor(const LocomotorTemplate* lt) -{ - Locomotor* loco = TheLocomotorStore->newLocomotor(lt); - if (loco) - { - m_locomotors.push_back(loco); - m_validLocomotorSurfaces |= loco->getLegalSurfaces(); - if (loco->getIsDownhillOnly()) - { - m_downhillOnly = TRUE; - } - else // Previous locos were gravity only, but this one isn't! - { - DEBUG_ASSERTCRASH(!m_downhillOnly,("LocomotorSet, YOU CAN NOT MIX DOWNHILL-ONLY LOCOMOTORS WITH NON-DOWNHILL-ONLY ONES.")); - } - - } -} - -//------------------------------------------------------------------------------------------------- -Locomotor* LocomotorSet::findLocomotor(LocomotorSurfaceTypeMask t) -{ - for (LocomotorVector::iterator it = m_locomotors.begin(); it != m_locomotors.end(); ++it) - { - Locomotor* curLocomotor = *it; - if (curLocomotor && (curLocomotor->getLegalSurfaces() & t)) - return curLocomotor; - } - return NULL; -} - - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: Locomotor.cpp /////////////////////////////////////////////////////////////////////////////// +// Author: Steven Johnson, Feb 2002 +// Desc: Locomotor descriptions +/////////////////////////////////////////////////////////////////////////////////////////////////// + + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#define DEFINE_SURFACECATEGORY_NAMES +#define DEFINE_LOCO_Z_NAMES +#define DEFINE_LOCO_APPEARANCE_NAMES + +#include "Common/INI.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/PartitionManager.h" +#include "GameLogic/Locomotor.h" +#include "GameLogic/Object.h" +#include "GameLogic/AI.h" +#include "GameLogic/AIPathfind.h" +#include "GameLogic/Module/PhysicsUpdate.h" +#include "GameLogic/Module/BodyModule.h" +#include "GameLogic/Module/AIUpdate.h" + +#ifdef RTS_INTERNAL +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +static const Real DONUT_TIME_DELAY_SECONDS=2.5f; +static const Real DONUT_DISTANCE=4.0*PATHFIND_CELL_SIZE_F; + + +#define MAX_BRAKING_FACTOR 5.0f +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +LocomotorStore *TheLocomotorStore = NULL; ///< the Locomotor store definition + +const Real BIGNUM = 99999.0f; + +static const char *TheLocomotorPriorityNames[] = +{ + "MOVES_BACK", + "MOVES_MIDDLE", + "MOVES_FRONT", + + NULL +}; + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +static Real calcSlowDownDist(Real curSpeed, Real desiredSpeed, Real maxBraking) +{ + Real delta = curSpeed - desiredSpeed; + if (delta <= 0) + return 0.0f; + + Real dist = (sqr(delta) / fabs(maxBraking)) * 0.5f; + + // use a little fudge so that things can stop "on a dime" more easily... + const Real FUDGE = 1.05f; + return dist * FUDGE; +} + +//----------------------------------------------------------------------------- +inline Bool isNearlyZero(Real a) +{ + const Real TINY_EPSILON = 0.001f; + return fabs(a) < TINY_EPSILON; +} + +//----------------------------------------------------------------------------- +inline Bool isNearly(Real a, Real val) +{ + const Real TINY_EPSILON = 0.001f; + return fabs(a - val) < TINY_EPSILON; +} + +//----------------------------------------------------------------------------- +// return the angle delta (in 3-space) we turned. +static Real tryToRotateVector3D( + Real maxAngle, // if negative, it's a percent (0...1) of the dist to rotate 'em + const Vector3& inCurDir, + const Vector3& inGoalDir, + Vector3& actualDir +) +{ + if (isNearlyZero(maxAngle)) + { + actualDir = inCurDir; + return 0.0f; + } + + Vector3 curDir = inCurDir; + curDir.Normalize(); + + Vector3 goalDir = inGoalDir; + goalDir.Normalize(); + + // dot of two unit vectors is cos of angle between them. + Real cosine = Vector3::Dot_Product(curDir, goalDir); + // bound it in case of numerical error + Real angleBetween = (Real)ACos(clamp(-1.0f, cosine, 1.0f)); + + if (maxAngle < 0) + { + maxAngle = -maxAngle * angleBetween; + if (isNearlyZero(maxAngle)) + { + actualDir = inCurDir; + return 0.0f; + } + } + + if (fabs(angleBetween) <= maxAngle) + { + // close enough + actualDir = goalDir; + } + else + { + // nah, try as much as we can in the right dir. + // we need to rotate around the axis perpendicular to these two vecs. + // but: cross of two vectors is the perpendicular axis! +#ifdef ALLOW_TEMPORARIES + Vector3 objCrossGoal = Vector3::Cross_Product(curDir, goalDir); + objCrossGoal.Normalize(); +#else + Vector3 objCrossGoal; + Vector3::Normalized_Cross_Product(curDir, goalDir, &objCrossGoal); +#endif + + angleBetween = maxAngle; + Matrix3D rotMtx(objCrossGoal, angleBetween); + actualDir = rotMtx.Rotate_Vector(curDir); + } + + return angleBetween; +} + +//------------------------------------------------------------------------------------------------- +static Real tryToOrientInThisDirection3D(Object* obj, Real maxTurnRate, const Vector3& desiredDir) +{ + Vector3 actualDir; + Real relAngle = tryToRotateVector3D(maxTurnRate, obj->getTransformMatrix()->Get_X_Vector(), desiredDir, actualDir); + if (relAngle != 0.0f) + { + Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z); + + Matrix3D newXform; + newXform.buildTransformMatrix( objPos, actualDir ); + + obj->setTransformMatrix( &newXform ); + } + return relAngle; +} + +//------------------------------------------------------------------------------------------------- +inline Real tryToOrientInThisDirection3D(Object* obj, Real maxTurnRate, const Coord3D* dir) +{ + return tryToOrientInThisDirection3D(obj, maxTurnRate, Vector3(dir->x, dir->y, dir->z)); +} + +//----------------------------------------------------------------------------- +static void calcDirectionToApplyThrust( + const Object* obj, + const PhysicsBehavior* physics, + const Coord3D& ingoalPos, + Real maxAccel, + Vector3& goalDir +) +{ + /* + our meta-goal here is to calculate the direction we should apply our motive force + in order to minimize the angle between (our velocity) and (direction towards goalpos). + + this is complicated by the fact that we generally have an intrinsic velocity already, + that must be accounted for, and by the fact that we can only apply force in our + forward-x-direction (with a thrust-angle-range), and (due to limited range) might not + be able to apply the force in the optimal direction! + */ + + // convert to Vector3, to use all its handy stuff + Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z); + Vector3 goalPos(ingoalPos.x, ingoalPos.y, ingoalPos.z); + + Vector3 vecToGoal = goalPos - objPos; + if (isNearlyZero(vecToGoal.Length2())) + { + // goal pos is essentially same as current pos, so just stay the same & return + goalDir = obj->getTransformMatrix()->Get_X_Vector(); + return; + } + + /* + get our cur vel into a useful Vector3 form + */ + Vector3 curVel(physics->getVelocity()->x, physics->getVelocity()->y, physics->getVelocity()->z); + + // add gravity to our vel so that we account for it in our calcs + curVel.Z += TheGlobalData->m_gravity; + + Bool foundSolution = false; + Real distToGoalSqr = vecToGoal.Length2(); + Real distToGoal = sqrt(distToGoalSqr); + Real curVelMagSqr = curVel.Length2(); + Real curVelMag = sqrt(curVelMagSqr); + Real maxAccelSqr = sqr(maxAccel); + + Real denom = curVelMagSqr - maxAccelSqr; + if (!isNearlyZero(denom)) + { + // solve the (greatly simplified) quadratic... + Real t = (distToGoal * (curVelMag + maxAccel)) / denom; + Real t2 = (distToGoal * (curVelMag - maxAccel)) / denom; + if (t >= 0 || t2 >= 0) + { + // choose the smallest positive t. + if (t < 0 || (t2 >= 0 && t2 < t)) + t = t2; + + // plug it in. + if (!isNearlyZero(t)) + { + goalDir.X = (vecToGoal.X / t) - curVel.X; + goalDir.Y = (vecToGoal.Y / t) - curVel.Y; + goalDir.Z = (vecToGoal.Z / t) - curVel.Z; + goalDir.Normalize(); + foundSolution = true; + } + } + } + if (!foundSolution) + { + // Doh... no (useful) solution. revert to dumb. + goalDir = vecToGoal; + goalDir.Normalize(); + } + +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +LocomotorTemplate::LocomotorTemplate() +{ + // these values mean "make the same as undamaged if not explicitly specified" + m_maxSpeedDamaged = -1.0f; + m_maxTurnRateDamaged = -1.0f; + m_accelerationDamaged = -1.0f; + m_liftDamaged = -1.0f; + + m_surfaces = 0; + m_maxSpeed = 0.0f; + m_maxTurnRate = 0.0f; + m_acceleration = 0.0f; + m_lift = 0.0f; + m_braking = BIGNUM; + m_minSpeed = 0.0f; + m_minTurnSpeed = BIGNUM; + m_behaviorZ = Z_NO_Z_MOTIVE_FORCE; + m_appearance = LOCO_OTHER; + m_movePriority = LOCO_MOVES_MIDDLE; + m_preferredHeight = 0; + m_preferredHeightDamping = 1.0f; + m_circlingRadius = 0; + + m_maxThrustAngle = 0; + m_speedLimitZ = 999999.0f; + m_extra2DFriction = 0.0f; + + m_accelPitchLimit = 0; + m_decelPitchLimit = 0; + m_bounceKick = 0; + +// m_pitchStiffness = 0; +// m_rollStiffness = 0; +// m_pitchDamping = 0; +// m_rollDamping = 0; +// it's highly unlikely you want zero for the defaults for stiffness and damping... (srj) +// for stiffness: stiffness of the "springs" in the suspension 0 = no stiffness, 1 = totally stiff (huh huh, he said "stiff") +// for damping: 0=perfect spring, bounces forever. 1=glued to terrain. + m_pitchStiffness = 0.1f; + m_rollStiffness = 0.1f; + m_pitchDamping = 0.9f; + m_rollDamping = 0.9f; + m_forwardVelCoef = 0; + m_pitchByZVelCoef = 0; + m_thrustRoll = 0.0f; + m_wobbleRate = 0.0f; + m_minWobble = 0.0f; + m_maxWobble = 0.0f; + m_lateralVelCoef = 0; + m_forwardAccelCoef = 0; + m_lateralAccelCoef = 0; + m_uniformAxialDamping = 1.0f; + m_turnPivotOffset = 0; + m_apply2DFrictionWhenAirborne = false; + m_downhillOnly = false; + m_allowMotiveForceWhileAirborne = false; + m_locomotorWorksWhenDead = false; + m_airborneTargetingHeight = INT_MAX; + m_stickToGround = false; + m_canMoveBackward = false; + m_hasSuspension = false; + m_wheelTurnAngle = 0; + m_maximumWheelExtension = 0; + m_maximumWheelCompression = 0; + m_closeEnoughDist = 1.0f; + m_isCloseEnoughDist3D = FALSE; + m_ultraAccurateSlideIntoPlaceFactor = 0.0f; + + m_wanderWidthFactor = 0.0f; + m_wanderLengthFactor = 1.0f; + m_wanderAboutPointRadius = 0.0f; + + m_rudderCorrectionDegree = 0.0f; + m_rudderCorrectionRate = 0.0f; + m_elevatorCorrectionDegree = 0.0f; + m_elevatorCorrectionRate = 0.0f; + +} + +//------------------------------------------------------------------------------------------------- +LocomotorTemplate::~LocomotorTemplate() +{ + +} + +//------------------------------------------------------------------------------------------------- +void LocomotorTemplate::validate() +{ + // this is ok; parachutes need it! + //DEBUG_ASSERTCRASH(m_lift == 0.0f || m_lift > fabs(TheGlobalData->m_gravity), ("Lift is too low to counteract gravity!")); + //DEBUG_ASSERTCRASH(m_liftDamaged == 0.0f || m_liftDamaged > fabs(TheGlobalData->m_gravity), ("LiftDamaged is too low to counteract gravity!")); + //DEBUG_ASSERTCRASH(m_preferredHeight == 0.0f || (m_behaviorZ == Z_SURFACE_RELATIVE_HEIGHT || m_behaviorZ == Z_ABSOLUTE_HEIGHT || m_appearance == LOCO_THRUST), + // ("You must use Z_SURFACE_RELATIVE_HEIGHT or Z_ABSOLUTE_HEIGHT (or THRUST) to use preferredHeight")); + + // for 'damaged' stuff that was omitted, set 'em to be the same as 'undamaged'... + if (m_maxSpeedDamaged < 0.0f) + m_maxSpeedDamaged = m_maxSpeed; + + if (m_maxTurnRateDamaged < 0.0f) + m_maxTurnRateDamaged = m_maxTurnRate; + + if (m_accelerationDamaged < 0.0f) + m_accelerationDamaged = m_acceleration; + + if (m_liftDamaged < 0.0f) + m_liftDamaged = m_lift; + + if (m_appearance == LOCO_WINGS) + { + if (m_minSpeed <= 0.0f) + { + DEBUG_CRASH(("WINGS should always have positive minSpeeds (otherwise, they hover)")); + m_minSpeed = 0.01f; + } + if (m_minTurnSpeed <= 0.0f) + { + DEBUG_CRASH(("WINGS should always have positive minTurnSpeed")); + m_minTurnSpeed = 0.01f; + } + } + + if (m_appearance == LOCO_THRUST) + { + if (m_behaviorZ != Z_NO_Z_MOTIVE_FORCE || + m_lift != 0.0f || + m_liftDamaged != 0.0f) + { + DEBUG_CRASH(("THRUST locos may not use ZAxisBehavior or lift!\n")); + throw INI_INVALID_DATA; + } + if (m_maxSpeed <= 0.0f) + { + // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing + DEBUG_LOG(("THRUST locos may not have zero m_maxSpeed; healing...\n")); + m_maxSpeed = 0.01f; + } + if (m_maxSpeedDamaged <= 0.0f) + { + // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing + DEBUG_LOG(("THRUST locos may not have zero m_maxSpeedDamaged; healing...\n")); + m_maxSpeedDamaged = 0.01f; + } + if (m_minSpeed <= 0.0f) + { + // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing + DEBUG_LOG(("THRUST locos may not have zero m_minSpeed; healing...\n")); + m_minSpeed = 0.01f; + } + } +} + +//------------------------------------------------------------------------------------------------- +static void parseFrictionPerSec( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + Real fricPerSec = INI::scanReal(ini->getNextToken()); + Real fricPerFrame = fricPerSec * SECONDS_PER_LOGICFRAME_REAL; + *(Real *)store = fricPerFrame; +} + +//------------------------------------------------------------------------------------------------- +const FieldParse* LocomotorTemplate::getFieldParse() const +{ + static const FieldParse TheFieldParse[] = + { + { "Surfaces", INI::parseBitString32, TheLocomotorSurfaceTypeNames, offsetof(LocomotorTemplate, m_surfaces) }, + { "Speed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_maxSpeed) }, + { "SpeedDamaged", INI::parseVelocityReal, NULL, offsetof( LocomotorTemplate, m_maxSpeedDamaged ) }, + { "TurnRate", INI::parseAngularVelocityReal, NULL, offsetof(LocomotorTemplate, m_maxTurnRate) }, + { "TurnRateDamaged", INI::parseAngularVelocityReal, NULL, offsetof( LocomotorTemplate, m_maxTurnRateDamaged ) }, + { "Acceleration", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_acceleration) }, + { "AccelerationDamaged", INI::parseAccelerationReal, NULL, offsetof( LocomotorTemplate, m_accelerationDamaged ) }, + { "Lift", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_lift) }, + { "LiftDamaged", INI::parseAccelerationReal, NULL, offsetof( LocomotorTemplate, m_liftDamaged ) }, + { "Braking", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_braking) }, + { "MinSpeed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_minSpeed) }, + { "MinTurnSpeed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_minTurnSpeed) }, + { "PreferredHeight", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_preferredHeight) }, + { "PreferredHeightDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_preferredHeightDamping) }, + { "CirclingRadius", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_circlingRadius) }, + { "Extra2DFriction", parseFrictionPerSec, NULL, offsetof(LocomotorTemplate, m_extra2DFriction) }, + { "SpeedLimitZ", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_speedLimitZ) }, + { "MaxThrustAngle", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_maxThrustAngle) }, // yes, angle, not angular-vel + { "ZAxisBehavior", INI::parseIndexList, TheLocomotorBehaviorZNames, offsetof(LocomotorTemplate, m_behaviorZ) }, + { "Appearance", INI::parseIndexList, TheLocomotorAppearanceNames, offsetof(LocomotorTemplate, m_appearance) }, \ + { "GroupMovementPriority", INI::parseIndexList, TheLocomotorPriorityNames, offsetof(LocomotorTemplate, m_movePriority) }, \ + + { "AccelerationPitchLimit", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_accelPitchLimit) }, + { "DecelerationPitchLimit", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_decelPitchLimit) }, + { "BounceAmount", INI::parseAngularVelocityReal, NULL, offsetof(LocomotorTemplate, m_bounceKick) }, + { "PitchStiffness", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchStiffness) }, + { "RollStiffness", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rollStiffness) }, + { "PitchDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchDamping) }, + { "RollDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rollDamping) }, + { "ThrustRoll", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_thrustRoll) }, + { "ThrustWobbleRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wobbleRate) }, + { "ThrustMinWobble", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_minWobble) }, + { "ThrustMaxWobble", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maxWobble) }, + { "PitchInDirectionOfZVelFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchByZVelCoef) }, + { "ForwardVelocityPitchFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_forwardVelCoef) }, + { "LateralVelocityRollFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_lateralVelCoef) }, + { "ForwardAccelerationPitchFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_forwardAccelCoef) }, + { "LateralAccelerationRollFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_lateralAccelCoef) }, + { "UniformAxialDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_uniformAxialDamping) }, + { "TurnPivotOffset", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_turnPivotOffset) }, + { "Apply2DFrictionWhenAirborne", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_apply2DFrictionWhenAirborne) }, + { "DownhillOnly", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_downhillOnly) }, + { "AllowAirborneMotiveForce", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_allowMotiveForceWhileAirborne) }, + { "LocomotorWorksWhenDead", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_locomotorWorksWhenDead) }, + { "AirborneTargetingHeight", INI::parseInt, NULL, offsetof( LocomotorTemplate, m_airborneTargetingHeight ) }, + { "StickToGround", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_stickToGround) }, + { "CanMoveBackwards", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_canMoveBackward) }, + { "HasSuspension", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_hasSuspension) }, + { "FrontWheelTurnAngle", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_wheelTurnAngle) }, + { "MaximumWheelExtension", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maximumWheelExtension) }, + { "MaximumWheelCompression", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maximumWheelCompression) }, + { "CloseEnoughDist", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_closeEnoughDist) }, + { "CloseEnoughDist3D", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_isCloseEnoughDist3D) }, + { "SlideIntoPlaceTime", INI::parseDurationReal, NULL, offsetof(LocomotorTemplate, m_ultraAccurateSlideIntoPlaceFactor) }, + + { "WanderWidthFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderWidthFactor) }, + { "WanderLengthFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderLengthFactor) }, + { "WanderAboutPointRadius", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderAboutPointRadius) }, + + { "RudderCorrectionDegree", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rudderCorrectionDegree) }, + { "RudderCorrectionRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rudderCorrectionRate) }, + { "ElevatorCorrectionDegree", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_elevatorCorrectionDegree) }, + { "ElevatorCorrectionRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_elevatorCorrectionRate) }, + { NULL, NULL, NULL, 0 } // keep this last + + }; + return TheFieldParse; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +LocomotorStore::LocomotorStore() +{ +} + +//------------------------------------------------------------------------------------------------- +LocomotorStore::~LocomotorStore() +{ + // delete all the templates, then clear out the table. + LocomotorTemplateMap::iterator it; + for (it = m_locomotorTemplates.begin(); it != m_locomotorTemplates.end(); ++it) { + it->second->deleteInstance(); + } + + m_locomotorTemplates.clear(); +} + +//------------------------------------------------------------------------------------------------- +LocomotorTemplate* LocomotorStore::findLocomotorTemplate(NameKeyType namekey) +{ + if (namekey == NAMEKEY_INVALID) + return NULL; + + LocomotorTemplateMap::iterator it = m_locomotorTemplates.find(namekey); + if (it == m_locomotorTemplates.end()) + return NULL; + else + return (*it).second; +} + +//------------------------------------------------------------------------------------------------- +const LocomotorTemplate* LocomotorStore::findLocomotorTemplate(NameKeyType namekey) const +{ + if (namekey == NAMEKEY_INVALID) + return NULL; + + LocomotorTemplateMap::const_iterator it = m_locomotorTemplates.find(namekey); + if (it == m_locomotorTemplates.end()) + { + return NULL; + } + else + { + return (*it).second; + } +} + +//------------------------------------------------------------------------------------------------- +void LocomotorStore::update() +{ +} + +//------------------------------------------------------------------------------------------------- +void LocomotorStore::reset() +{ + // cleanup overrides. + LocomotorTemplateMap::iterator it; + for (it = m_locomotorTemplates.begin(); it != m_locomotorTemplates.end(); ) { + Overridable *locoTemp = it->second->deleteOverrides(); + if (!locoTemp) + { + m_locomotorTemplates.erase(it); + } + else + { + ++it; + } + } +} + +//------------------------------------------------------------------------------------------------- +LocomotorTemplate *LocomotorStore::newOverride( LocomotorTemplate *locoTemplate ) +{ + if (locoTemplate == NULL) + return NULL; + + // allocate new template + LocomotorTemplate *newTemplate = newInstance(LocomotorTemplate); + + // copy data from final override to 'newTemplate' as a set of initial default values + *newTemplate = *locoTemplate; + locoTemplate->setNextOverride(newTemplate); + + newTemplate->markAsOverride(); + + // return the newly created override for us to set values with etc + return newTemplate; + +} // end newOverride + +//------------------------------------------------------------------------------------------------- +/*static*/ void LocomotorStore::parseLocomotorTemplateDefinition(INI* ini) +{ + if (!TheLocomotorStore) + throw INI_INVALID_DATA; + + Bool isOverride = false; + // read the Locomotor name + const char* token = ini->getNextToken(); + NameKeyType namekey = NAMEKEY(token); + + LocomotorTemplate *loco = TheLocomotorStore->findLocomotorTemplate(namekey); + if (loco) { + if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES) { + loco = TheLocomotorStore->newOverride((LocomotorTemplate*) loco->friend_getFinalOverride()); + } + isOverride = true; + } else { + loco = newInstance(LocomotorTemplate); + if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES) { + loco->markAsOverride(); + } + } + + loco->friend_setName(token); + ini->initFromINI(loco, loco->getFieldParse()); + loco->validate(); + + // if this is an override, then we want the pointer on the existing named locomotor to point us + // to the override, so don't add it to the map. + if (!isOverride) + TheLocomotorStore->m_locomotorTemplates[namekey] = loco; +} + +//------------------------------------------------------------------------------------------------- +/*static*/ void INI::parseLocomotorTemplateDefinition( INI* ini ) +{ + LocomotorStore::parseLocomotorTemplateDefinition(ini); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- +Locomotor::Locomotor(const LocomotorTemplate* tmpl) +{ + m_template = tmpl; + m_brakingFactor = 1.0f; + m_maxLift = BIGNUM; + m_maxSpeed = BIGNUM; + m_maxAccel = BIGNUM; + m_maxBraking = BIGNUM; + m_maxTurnRate = BIGNUM; + m_flags = 0; + m_closeEnoughDist = m_template->m_closeEnoughDist; + setFlag(IS_CLOSE_ENOUGH_DIST_3D, m_template->m_isCloseEnoughDist3D); +#ifdef CIRCLE_FOR_LANDING + m_circleThresh = 0.0f; +#endif + m_preferredHeight = m_template->m_preferredHeight; + m_preferredHeightDamping = m_template->m_preferredHeightDamping; + + m_angleOffset = GameLogicRandomValueReal(-PI/6, PI/6); + m_offsetIncrement = (PI/40) * (GameLogicRandomValueReal(0.8f, 1.2f)/m_template->m_wanderLengthFactor); + setFlag(OFFSET_INCREASING, GameLogicRandomValue(0,1)); + m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; + + m_speedMultiplier = 1.0; +} + +//------------------------------------------------------------------------------------------------- +Locomotor::Locomotor(const Locomotor& that) +{ + //Added By Sadullah Nader + //Initializations + m_angleOffset = 0.0f; + m_maintainPos.zero(); + + // + + m_template = that.m_template; + m_brakingFactor = that.m_brakingFactor; + m_maxLift = that.m_maxLift; + m_maxSpeed = that.m_maxSpeed; + m_maxAccel = that.m_maxAccel; + m_maxBraking = that.m_maxBraking; + m_maxTurnRate = that.m_maxTurnRate; + m_flags = that.m_flags; + m_closeEnoughDist = that.m_closeEnoughDist; +#ifdef CIRCLE_FOR_LANDING + m_circleThresh = that.m_circleThresh; +#endif + m_preferredHeight = that.m_preferredHeight; + m_preferredHeightDamping = that.m_preferredHeightDamping; + m_angleOffset = that.m_angleOffset; + m_offsetIncrement = that.m_offsetIncrement; +} + +//------------------------------------------------------------------------------------------------- +Locomotor& Locomotor::operator=(const Locomotor& that) +{ + if (this != &that) + { + m_template = that.m_template; + m_brakingFactor = that.m_brakingFactor; + m_maxLift = that.m_maxLift; + m_maxSpeed = that.m_maxSpeed; + m_maxAccel = that.m_maxAccel; + m_maxBraking = that.m_maxBraking; + m_maxTurnRate = that.m_maxTurnRate; + m_flags = that.m_flags; + m_closeEnoughDist = that.m_closeEnoughDist; +#ifdef CIRCLE_FOR_LANDING + m_circleThresh = that.m_circleThresh; +#endif + m_preferredHeight = that.m_preferredHeight; + m_preferredHeightDamping = that.m_preferredHeightDamping; + } + return *this; +} + +//------------------------------------------------------------------------------------------------- +Locomotor::~Locomotor() +{ +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void Locomotor::crc( Xfer *xfer ) +{ + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void Locomotor::xfer( Xfer *xfer ) +{ + // version + const XferVersion currentVersion = 2; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + if (version>=2) { + xfer->xferUnsignedInt(&m_donutTimer); + } + + xfer->xferCoord3D(&m_maintainPos); + xfer->xferReal(&m_brakingFactor); + xfer->xferReal(&m_maxLift); + xfer->xferReal(&m_maxSpeed); + xfer->xferReal(&m_maxAccel); + xfer->xferReal(&m_maxBraking); + xfer->xferReal(&m_maxTurnRate); + xfer->xferReal(&m_closeEnoughDist); +#ifdef CIRCLE_FOR_LANDING + DEBUG_CRASH(("not supported, must fix me")); +#endif + xfer->xferUnsignedInt(&m_flags); + xfer->xferReal(&m_preferredHeight); + xfer->xferReal(&m_preferredHeightDamping); + xfer->xferReal(&m_angleOffset); + xfer->xferReal(&m_offsetIncrement); + + xfer->xferReal(&m_speedMultiplier); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void Locomotor::loadPostProcess( void ) +{ + +} // end loadPostProcess + +//------------------------------------------------------------------------------------------------- +void Locomotor::startMove(void) +{ + // Reset the donut timer. + m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getMaxSpeedForCondition(BodyDamageType condition) const +{ + Real speed; + + if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) + speed = m_template->m_maxSpeed; + else + speed = m_template->m_maxSpeedDamaged; + + speed *= m_speedMultiplier; + + if (speed > m_maxSpeed) + speed = m_maxSpeed; + + return speed; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getMaxTurnRate(BodyDamageType condition) const +{ + Real turn; + + if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) + turn = m_template->m_maxTurnRate; + else + turn = m_template->m_maxTurnRateDamaged; + + turn *= m_speedMultiplier; + + if (turn > m_maxTurnRate) + turn = m_maxTurnRate; + + const Real TURN_FACTOR = 2; + if (getFlag(ULTRA_ACCURATE)) + turn *= TURN_FACTOR; // monster turning ability + + return turn; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getMaxAcceleration(BodyDamageType condition) const +{ + Real accel; + + if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) + accel = m_template->m_acceleration; + else + accel = m_template->m_accelerationDamaged; + + accel *= m_speedMultiplier; + + if (accel > m_maxAccel) + accel = m_maxAccel; + + return accel; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getBraking() const +{ + Real braking = m_template->m_braking; + + braking *= m_speedMultiplier; + + if (braking > m_maxBraking) + braking = m_maxBraking; + + return braking; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getMaxLift(BodyDamageType condition) const +{ + Real lift; + + if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) + lift = m_template->m_lift; + else + lift = m_template->m_liftDamaged; + + lift *= m_speedMultiplier; + + if (lift > m_maxLift) + lift = m_maxLift; + + return lift; +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::locoUpdate_moveTowardsAngle(Object* obj, Real goalAngle) +{ + setFlag(MAINTAIN_POS_IS_VALID, false); + + if (obj == NULL || m_template == NULL) + return; + + PhysicsBehavior *physics = obj->getPhysics(); + if (physics == NULL) + { + DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); + return; + } + + // Skip moveTowardsAngle if physics say you're stunned + if(physics->getIsStunned()) + { + return; + } + +#ifdef DEBUG_OBJECT_ID_EXISTS +// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_moveTowardsAngle %f (%f deg), spd %f (%f)\n",goalAngle,goalAngle*180/PI,physics->getSpeed(),physics->getForwardSpeed2D())); +#endif + + Real minSpeed = getMinSpeed(); + if (minSpeed > 0) + { + // can't stay in one place; move in the desired direction at min speed. + Coord3D desiredPos = *obj->getPosition(); + desiredPos.x += Cos(goalAngle) * minSpeed * 2; + desiredPos.y += Sin(goalAngle) * minSpeed * 2; + // pass a huge num for "dist to goal", so that we don't think we're nearing + // our destination and thus slow down... + const Real onPathDistToGoal = 99999.0f; + Bool blocked = false; + locoUpdate_moveTowardsPosition(obj, desiredPos, onPathDistToGoal, minSpeed, &blocked); + + // don't need to call handleBehaviorZ() here, since locoUpdate_moveTowardsPosition() will do so + return; + } + else + { + DEBUG_ASSERTCRASH(m_template->m_appearance != LOCO_THRUST, ("THRUST should always have minspeeds!\n")); + Coord3D desiredPos = *obj->getPosition(); + desiredPos.x += Cos(goalAngle) * 1000.0f; + desiredPos.y += Sin(goalAngle) * 1000.0f; + PhysicsTurningType rotating = rotateTowardsPosition(obj, desiredPos); + physics->setTurning(rotating); + handleBehaviorZ(obj, physics, *obj->getPosition()); + } + +} + +//------------------------------------------------------------------------------------------------- +PhysicsTurningType Locomotor::rotateTowardsPosition(Object* obj, const Coord3D& goalPos, Real *relAngle) +{ + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real turnRate = getMaxTurnRate(bdt); + + PhysicsTurningType rotating = rotateObjAroundLocoPivot(obj, goalPos, turnRate, relAngle); + return rotating; +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::setPhysicsOptions(Object* obj) +{ + PhysicsBehavior *physics = obj->getPhysics(); + if (physics == NULL) + { + DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); + return; + } + + // crank up the friction in ultra-accurate mode to increase movement precision. + const Real EXTRA_FRIC = 0.5f; + Real extraExtraFriction = getFlag(ULTRA_ACCURATE) ? EXTRA_FRIC : 0.0f; + physics->setExtraFriction(m_template->m_extra2DFriction + extraExtraFriction); + physics->setAllowAirborneFriction(getApply2DFrictionWhenAirborne()); // you'd think we wouldn't want friction in the air, but it's needed for realistic behavior. + physics->setStickToGround(getStickToGround()); // walking guys aren't allowed to catch huge (or even small) air. +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalPos, + Real onPathDistToGoal, Real desiredSpeed, Bool *blocked) +{ + setFlag(MAINTAIN_POS_IS_VALID, false); + + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxSpeed = getMaxSpeedForCondition(bdt); + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + Real distToStopAtMaxSpeed = (maxSpeed/getBraking()) * (maxSpeed)/2.0f; + if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > distToStopAtMaxSpeed) + { + setFlag(IS_BRAKING, false); + m_brakingFactor = 1.0f; + } + PhysicsBehavior *physics = obj->getPhysics(); + if (physics == NULL) + { + DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); + return; + } + + // Skip moveTowardsPosition if physics say you're stunned + if(physics->getIsStunned()) + { + return; + } + +#ifdef DEBUG_OBJECT_ID_EXISTS +// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_moveTowardsPosition %f %f %f (dtg %f, spd %f), speed %f (%f)\n",goalPos.x,goalPos.y,goalPos.z,onPathDistToGoal,desiredSpeed,physics->getSpeed(),physics->getForwardSpeed2D())); +#endif + + // + // do not allow for invalid positions that the pathfinder cannot handle ... for airborne + // objects we don't need the pathfinder so we'll ignore this + // + if( BitIsSet( m_template->m_surfaces, LOCOMOTORSURFACE_AIR ) == false && + !TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, obj->getPosition()) && + !getFlag(ALLOW_INVALID_POSITION)) + { + // Somehow, we have gotten to an invalid location. + if (fixInvalidPosition(obj, physics)) + { + // the we adjusted us toward a legal position, so just return. + return; + } + } + + // If the actual distance is farther, then use the actual distance so we get there. + Real dx = goalPos.x - obj->getPosition()->x; + Real dy = goalPos.y - obj->getPosition()->y; + Real dz = goalPos.z - obj->getPosition()->z; + Real dist = sqrt(dx*dx+dy*dy); + if (dist>onPathDistToGoal) + { + if (!obj->isKindOf(KINDOF_PROJECTILE) && dist>2*onPathDistToGoal) + { + setFlag(IS_BRAKING, true); + } + onPathDistToGoal = dist; + } + + Coord3D nullAccel; + + Bool treatAsAirborne = false; + Coord3D pos = *obj->getPosition(); + Real heightAboveSurface = pos.z - TheTerrainLogic->getLayerHeight(pos.x, pos.y, obj->getLayer()); + + if( obj->getStatusBits().test( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) ) + { + heightAboveSurface -= obj->getCarrierDeckHeight(); + } + + if (heightAboveSurface > -(3*3)*TheGlobalData->m_gravity) + { + // If we get high enough to stay up for 3 frames, then we left the ground. + treatAsAirborne = true; + } + // We apply a zero acceleration to all units, as the call to + // applyMotiveForce flags an object as being "driven" by a locomotor, rather + // than being pushed around by objects bumping it. + nullAccel.x = nullAccel.y = nullAccel.z = 0; + physics->applyMotiveForce(&nullAccel); + + if (*blocked) + { + if (desiredSpeed > physics->getVelocityMagnitude()) + { + *blocked = false; + } + if (treatAsAirborne && BitIsSet( m_template->m_surfaces, LOCOMOTORSURFACE_AIR ) ) + { + // Airborne flying objects don't collide for now. jba. + *blocked = false; + } + } + + if (*blocked) + { + physics->scrubVelocity2D(desiredSpeed); // stop if we are about to run into the blocking object. + Real turnRate = getMaxTurnRate(obj->getBodyModule()->getDamageState()); + if (m_template->m_wanderWidthFactor == 0.0f) + { + *blocked = (TURN_NONE != rotateObjAroundLocoPivot(obj, goalPos, turnRate)); + } + + // it is very important to be sure to call this in all situations, even if not moving in 2d space. + handleBehaviorZ(obj, physics, goalPos); + return; + } + + if ( +// srj sez: I don't know why we didn't want HOVERs to allow to "brake". +// we actually really want them to, because it allows much more precise destination positioning. +// m_template->m_appearance == LOCO_HOVER || + m_template->m_appearance == LOCO_WINGS) + { + setFlag(IS_BRAKING, false); + } + + Bool wasBraking = obj->getStatusBits().test( OBJECT_STATUS_BRAKING ); + + physics->setTurning(TURN_NONE); + if (getAllowMotiveForceWhileAirborne() || !treatAsAirborne) + { + switch (m_template->m_appearance) + { + case LOCO_LEGS_TWO: + moveTowardsPositionLegs(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_CLIMBER: + moveTowardsPositionClimb(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_WHEELS_FOUR: + case LOCO_MOTORCYCLE: + moveTowardsPositionWheels( obj, physics, goalPos, onPathDistToGoal, desiredSpeed ); + break; + case LOCO_TREADS: + moveTowardsPositionTreads(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_HOVER: + moveTowardsPositionHover(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_WINGS: + moveTowardsPositionWings(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_THRUST: + moveTowardsPositionThrust(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_OTHER: + default: + moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + } + } + + handleBehaviorZ(obj, physics, goalPos); + // Objects that are braking don't follow the normal physics, so they end up at their destination exactly. + obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ), getFlag(IS_BRAKING) ); + + if (wasBraking) + { + #define MIN_VEL (PATHFIND_CELL_SIZE_F/(LOGICFRAMES_PER_SECOND)) + + Coord3D pos = *obj->getPosition(); + if (obj->isKindOf(KINDOF_PROJECTILE)) + { + // Projectiles never stop braking once they start. jba. + obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ) ); + // Projectiles cheat in 3 dimensions. + dist = sqrt(dx*dx+dy*dy+dz*dz); + Real vel = physics->getVelocityMagnitude(); + if (vel < MIN_VEL) + vel = MIN_VEL; + if (vel > dist) + vel = dist; // do not overcompensate! + // Normalize. + if (dist > 0.001f) + { + dist = 1.0f / dist; + dx *= dist; + dy *= dist; + dz *= dist; + + // DEBUG_LOG((">>> Locomotor Braking - d(xyz) = %f / %f / %f\n", dx * vel, dy * vel, dz * vel)); + + pos.x += dx * vel; + pos.y += dy * vel; + pos.z += dz * vel; + } + } + else + { + // not projectiles only cheat in x & y. + // Normalize. + if (dist > 0.001f) + { + Real vel = fabs(physics->getForwardSpeed2D()); + if (vel < MIN_VEL) + vel = MIN_VEL; + if (vel > dist) + vel = dist; // do not overcompensate! + dist = 1.0f / dist; + dx *= dist; + dy *= dist; + pos.x += dx * vel; + pos.y += dy * vel; + } + } + obj->setPosition(&pos); + } + +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxSpeed = getMaxSpeedForCondition(bdt); + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + Real maxAcceleration = getMaxAcceleration(bdt); + + // Locomotion for treaded vehicles, ie tanks. + + // + // Orient toward goal position + // +// Real angle = obj->getOrientation(); +// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); +// Real desiredAngle = angle + relAngle; + Real relAngle ; + PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos, &relAngle); + physics->setTurning(rotating); + + // + // Modulate speed according to turning. The more we have to turn, the slower we go + // + const Real QUAETERPI = PI / 4.0f; + Real angleCoeff = (Real)fabs( relAngle ) / QUAETERPI; + if (angleCoeff > 1.0f) + angleCoeff = 1.0; + + + Real dx = obj->getPosition()->x - goalPos.x; + Real dy = obj->getPosition()->y - goalPos.y; + + + Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; + + +// if (speed < m_minTurnSpeed) +// speed = m_minTurnSpeed; + + Real actualSpeed = physics->getForwardSpeed2D(); + Real slowDownTime = actualSpeed / getBraking(); + Real slowDownDist = (actualSpeed/1.50f) * slowDownTime; + + if (sqr(dx)+sqr(dy) 0.05) { + goalSpeed = actualSpeed*0.6f; + } + + if (onPathDistToGoal < slowDownDist && !getFlag(IS_BRAKING) && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + { + setFlag(IS_BRAKING, true); + m_brakingFactor = 1.1f; + } + + if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > 2.0*slowDownDist) + { + setFlag(IS_BRAKING, false); + } + + if (getFlag(IS_BRAKING)) + { + m_brakingFactor = slowDownDist/onPathDistToGoal; + m_brakingFactor *= m_brakingFactor; + if (m_brakingFactor>MAX_BRAKING_FACTOR) { + m_brakingFactor = MAX_BRAKING_FACTOR; + } + if (slowDownDist>onPathDistToGoal) { + goalSpeed = actualSpeed-getBraking(); + if (goalSpeed<0.0f) goalSpeed= 0.0f; + } else if (slowDownDist>onPathDistToGoal*0.75f) { + goalSpeed = actualSpeed-getBraking()/2.0f; + if (goalSpeed<0.0f) goalSpeed = 0.0f; + } else { + goalSpeed = actualSpeed; + } + } + + + //DEBUG_LOG(("Actual speed %f, Braking factor %f, slowDownDist %f, Pathdist %f, goalSpeed %f\n", + // actualSpeed, m_brakingFactor, slowDownDist, onPathDistToGoal, goalSpeed)); + + // + // Maintain goal speed + // + Real speedDelta = goalSpeed - actualSpeed; + if (speedDelta != 0.0f) + { + Real mass = physics->getMass(); + Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -m_brakingFactor*getBraking(); + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + const Coord3D *dir = obj->getUnitDirectionVector2D(); + + Coord3D force; + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; + force.z = 0.0f; + + // apply forces to object + physics->applyMotiveForce( &force ); + } +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxSpeed = getMaxSpeedForCondition(bdt); + Real maxTurnRate = getMaxTurnRate(bdt); + Real maxAcceleration = getMaxAcceleration(bdt); + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + // Locomotion for wheeled vehicles, ie trucks. + // + // See if we are turning. If so, use the min turn speed. + // + Real turnSpeed = m_template->m_minTurnSpeed; + Real angle = obj->getOrientation(); +// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); +// Real desiredAngle = angle + relAngle; + Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + Real relAngle = stdAngleDiff(desiredAngle, angle); + + Bool moveBackwards = false; + + // Wheeled vehicles can only turn while moving, so make sure the turn speed is reasonable. + if (turnSpeed < maxSpeed/4.0f) + { + turnSpeed = maxSpeed/4.0f; + } + + + Real actualSpeed = physics->getForwardSpeed2D(); + Bool do3pointTurn = false; +#if 1 + if (actualSpeed==0.0f) { + setFlag(MOVING_BACKWARDS, false); + if (m_template->m_canMoveBackward && fabs(relAngle) > PI/2) { + setFlag(MOVING_BACKWARDS, true ); + setFlag(DOING_THREE_POINT_TURN, onPathDistToGoal>5*obj->getGeometryInfo().getMajorRadius()); + } + + } + if (getFlag(MOVING_BACKWARDS)) { + if (fabs(relAngle) < PI/2) { + moveBackwards = false; + setFlag(MOVING_BACKWARDS, false); + } else { + moveBackwards = true; + setFlag(DOING_THREE_POINT_TURN, onPathDistToGoal>5*obj->getGeometryInfo().getMajorRadius()); + do3pointTurn = getFlag(DOING_THREE_POINT_TURN); + if (!do3pointTurn) { + desiredAngle = stdAngleDiff(desiredAngle, PI); + relAngle = stdAngleDiff(desiredAngle, angle); + } + } + } +#endif + + const Real SMALL_TURN = PI / 20.0f; + if ((Real)fabs( relAngle ) > SMALL_TURN) + { + if (desiredSpeed>turnSpeed) + { + desiredSpeed = turnSpeed; + } + } + + Real goalSpeed = desiredSpeed; + if (moveBackwards) { + actualSpeed = -actualSpeed; + } + + + + Real slowDownTime = actualSpeed / getBraking() + 1.0f; + Real slowDownDist = (actualSpeed/1.5f) * slowDownTime + actualSpeed; + Real effectiveSlowDownDist = slowDownDist; + if (effectiveSlowDownDist < 1*PATHFIND_CELL_SIZE) { + effectiveSlowDownDist = 1*PATHFIND_CELL_SIZE; + } + + + const Real FIFTEEN_DEGREES = PI / 12.0f; + const Real PROJECT_FRAMES = LOGICFRAMES_PER_SECOND/2; // Project out 1/2 second. + if (fabs( relAngle ) > FIFTEEN_DEGREES) + { + // If we're turning more than 10 degrees, check & see if we're moving into "impassable territory" + Real distance = PROJECT_FRAMES * (goalSpeed+actualSpeed)/2.0f; + Real targetAngle = obj->getOrientation(); + Real turnFactor = ((goalSpeed+actualSpeed)/2.0f)/turnSpeed; + if (turnFactor > 1.0f) + turnFactor = 1.0f; + Real turnAmount = PROJECT_FRAMES*turnFactor*maxTurnRate/4.0f; + if (relAngle < 0) + { + targetAngle -= turnAmount; + } + else + { + targetAngle += turnAmount; + } + Coord3D offset; + offset.x = Cos(targetAngle)*distance; + offset.y = Sin(targetAngle)*distance; + offset.z = 0; + + const Coord3D* pos = obj->getPosition(); + + Coord3D nextPos; + nextPos.x = pos->x+offset.x; + nextPos.y = pos->y+offset.y; + nextPos.z = pos->z; + + pos = obj->getPosition(); + + Coord3D halfPos; + halfPos.x = pos->x+offset.x/2; + halfPos.y = pos->y+offset.y/2; + halfPos.z = pos->z; + + if (!TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &halfPos) || + !TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &nextPos)) + { + PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos); + physics->setTurning(rotating); + + // apply a zero force to object so that it acts "driven" + Coord3D force; + force.zero(); + physics->applyMotiveForce( &force ); + return; + } + + } + + if (onPathDistToGoal < effectiveSlowDownDist && !getFlag(IS_BRAKING) && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + { + setFlag(IS_BRAKING, true); + m_brakingFactor = 1.1f; + } + + + if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > 2.0*slowDownDist) + { + setFlag(IS_BRAKING, false); + } + + if (onPathDistToGoal > DONUT_DISTANCE) { + m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; + } else { + if (m_donutTimer < TheGameLogic->getFrame()) { + setFlag(IS_BRAKING, true); + } + } + + if (getFlag(IS_BRAKING)) + { + m_brakingFactor = slowDownDist/onPathDistToGoal; + m_brakingFactor *= m_brakingFactor; + if (m_brakingFactor>MAX_BRAKING_FACTOR) { + m_brakingFactor = MAX_BRAKING_FACTOR; + } + m_brakingFactor = 1.0f; + if (slowDownDist>onPathDistToGoal) { + goalSpeed = actualSpeed-getBraking(); + if (goalSpeed<0.0f) goalSpeed= 0.0f; + } else if (slowDownDist>onPathDistToGoal*0.75f) { + goalSpeed = actualSpeed-getBraking()/2.0f; + if (goalSpeed<0.0f) goalSpeed = 0.0f; + } else { + goalSpeed = actualSpeed; + } + } + + + //DEBUG_LOG(("Actual speed %f, Braking factor %f, slowDownDist %f, Pathdist %f, goalSpeed %f\n", + // actualSpeed, m_brakingFactor, slowDownDist, onPathDistToGoal, goalSpeed)); + + + // Wheeled can only turn while moving. + Real turnFactor = actualSpeed/turnSpeed; + if (turnFactor<0) { + turnFactor = -turnFactor; // in case we're sliding backwards in a 3 pt turn. + } + if (turnFactor > 1.0f) + turnFactor = 1.0f; + Real turnAmount = turnFactor*maxTurnRate; + + PhysicsTurningType rotating; + if (moveBackwards && !do3pointTurn) { + Coord3D backwardPos = *obj->getPosition(); + backwardPos.x += -(goalPos.x - obj->getPosition()->x); + backwardPos.y += -(goalPos.y - obj->getPosition()->y); + rotating = rotateObjAroundLocoPivot(obj, backwardPos, turnAmount); + } else { + rotating = rotateObjAroundLocoPivot(obj, goalPos, turnAmount); + } + + physics->setTurning(rotating); + + // + // Maintain goal speed + // + Real speedDelta = goalSpeed - actualSpeed; + if (moveBackwards) { + speedDelta = -goalSpeed+actualSpeed; + } + if (speedDelta != 0.0f) + { + Real mass = physics->getMass(); + Real acceleration; + if (moveBackwards) { + acceleration = (speedDelta < 0.0f) ? -maxAcceleration : m_brakingFactor*getBraking(); + } else { + acceleration = (speedDelta > 0.0f) ? maxAcceleration : -m_brakingFactor*getBraking(); + } + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + //DEBUG_LOG(("Braking %d, actualSpeed %f, goalSpeed %f, delta %f, accel %f\n", getFlag(IS_BRAKING), + //actualSpeed, goalSpeed, speedDelta, accelForce)); + + const Coord3D *dir = obj->getUnitDirectionVector2D(); + + Coord3D force; + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; + force.z = 0.0f; + + // apply forces to object + physics->applyMotiveForce( &force ); + } + +} +//------------------------------------------------------------------------------------------------- +Bool Locomotor::fixInvalidPosition(Object* obj, PhysicsBehavior *physics) +{ + if (obj->isKindOf(KINDOF_DOZER)) { + // don't fix him. + return false; + } +#define no_IGNORE_INVALID +#ifdef IGNORE_INVALID + // Right now we ignore invalid positions, so when units clip the edge of a building or cliff + // they don't get stuck. jba. 12SEPT02 + return false; +#else + Int dx = 0; + Int dy = 0; + Int i, j; + for (j=-1; j<2; j++) { + for (i=-1; i<2; i++) { + Coord3D thePos = *obj->getPosition(); + thePos.x += i*PATHFIND_CELL_SIZE_F; + thePos.y += j*PATHFIND_CELL_SIZE_F; + if (!TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &thePos)) { + if (i<0) dx += 1; + if (i>0) dx -= 1; + if (j<0) dy += 1; + if (j>0) dy -= 1; + } + } + } + if (dx || dy) { + + Coord3D correction; + correction.x = dx*physics->getMass()/5; + correction.y = dy*physics->getMass()/5; + correction.z = 0; + + Coord3D correctionNormalized = correction; + correctionNormalized.normalize(); + + Coord3D velocity; + // Kill current velocity in the direction of the correction. + velocity = *physics->getVelocity(); + Real dot = (velocity.x*correctionNormalized.x) + (velocity.y*correctionNormalized.y); + if (dot>.25f) { + // It was already leaving. + return false; + } + + + // Kill current accel + //physics->clearAcceleration(); + + if (dot<0) { + dot = sqrt(-dot); + correctionNormalized.x *= dot*physics->getMass(); + correctionNormalized.y *= dot*physics->getMass(); + physics->applyMotiveForce(&correctionNormalized); + } + + // apply correction. + physics->applyMotiveForce(&correction); + return true; + } + return false; +#endif +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::calcMinTurnRadius(BodyDamageType condition, Real* timeToTravelThatDist) const +{ + Real minSpeed = getMinSpeed(); // in dist/frame + Real maxTurnRate = getMaxTurnRate(condition); // in rads/frame + + /* + our minimum circumference will be like so: + + Real minTurnCircum = maxSpeed * (2*PI / maxTurnRate); + + so therefore our minimum turn radius is: + + Real minTurnRadius = minTurnCircum / 2*PI; + + so we just eliminate the middleman: + */ + // if we can't turn, return a huge-but-finite radius rather than NAN... + Real minTurnRadius = (maxTurnRate > 0.0f) ? minSpeed / maxTurnRate : BIGNUM; + + if (timeToTravelThatDist) + *timeToTravelThatDist = (minSpeed > 0.0f) ? (minTurnRadius / minSpeed) : 0.0f; + + return minTurnRadius; +} + + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + if (getIsDownhillOnly() && obj->getPosition()->z < goalPos.z) + { + return; + } + + Real maxAcceleration = getMaxAcceleration( obj->getBodyModule()->getDamageState() ); + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState() ); + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + // Locomotion for infantry. + // + // Orient toward goal position + // + Real actualSpeed = physics->getForwardSpeed2D(); + Real angle = obj->getOrientation(); +// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); +// Real desiredAngle = angle + relAngle; + Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + + if (m_template->m_wanderWidthFactor != 0.0f) { + Real angleLimit = PI/8 * m_template->m_wanderWidthFactor; + // This is the wander offline code - it forces the desired angle away from the goal, so we wander back & forth. jba. + if (getFlag(OFFSET_INCREASING)) { + m_angleOffset += m_offsetIncrement*actualSpeed; + if (m_angleOffset > angleLimit) { + setFlag(OFFSET_INCREASING, false); + } + } else { + m_angleOffset -= m_offsetIncrement*actualSpeed; + if (m_angleOffset<-angleLimit) { + setFlag(OFFSET_INCREASING, true); + } + } + desiredAngle = normalizeAngle(desiredAngle+m_angleOffset); + } + + Real relAngle = stdAngleDiff(desiredAngle, angle); + locoUpdate_moveTowardsAngle(obj, desiredAngle); + + // + // Modulate speed according to turning. The more we have to turn, the slower we go + // + const Real QUARTERPI = PI / 4.0f; + Real angleCoeff = (Real)fabs( relAngle ) / (QUARTERPI); + if (angleCoeff > 1.0f) + angleCoeff = 1.0; + + Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; + + //Real slowDownDist = (actualSpeed - m_template->m_minSpeed) / getBraking(); + Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); + if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + { + goalSpeed = m_template->m_minSpeed; + } + + + + // + // Maintain goal speed + // + Real speedDelta = goalSpeed - actualSpeed; + if (speedDelta != 0.0f) + { + Real mass = physics->getMass(); + Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + const Coord3D *dir = obj->getUnitDirectionVector2D(); + + Coord3D force; + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; + force.z = 0.0f; + + + + // apply forces to object + physics->applyMotiveForce( &force ); + } +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + Real maxAcceleration = getMaxAcceleration( obj->getBodyModule()->getDamageState() ); + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState() ); + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + // Locomotion for climbing infantry. + + + Bool moveBackwards = false; + + Real dx, dy, dz; + + Coord3D pos = *obj->getPosition(); + + dx = pos.x - goalPos.x; + dy = pos.y - goalPos.y; + dz = pos.z - goalPos.z; + if (dz*dz > sqr(PATHFIND_CELL_SIZE_F)) { + setFlag(CLIMBING, true); + } + if (fabs(dz)<1) { + setFlag(CLIMBING, false); + } + + + //setFlag(CLIMBING, true); + + if (getFlag(CLIMBING)) { + Coord3D delta = goalPos; + delta.x -= pos.x; + delta.y -= pos.y; + delta.z = 0; + delta.normalize(); + delta.x += pos.x; + delta.y += pos.y; + delta.z = TheTerrainLogic->getGroundHeight(delta.x, delta.y); + if (delta.z < pos.z-0.1) { + moveBackwards = true; + } + + Real groundSlope = fabs(delta.z - pos.z); + if (groundSlope<1.0f) groundSlope = 1.0f; + + if (groundSlope>1.0f) { + desiredSpeed /= groundSlope*4; + } + } + setFlag(MOVING_BACKWARDS, moveBackwards); + + // + // Orient toward goal position + // + Real angle = obj->getOrientation(); +// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); +// Real desiredAngle = angle + relAngle; + Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + Real relAngle = stdAngleDiff(desiredAngle, angle); + + if (moveBackwards) { + desiredAngle = stdAngleDiff(desiredAngle, PI); + relAngle = stdAngleDiff(desiredAngle, angle); + } + + locoUpdate_moveTowardsAngle(obj, desiredAngle); + + // + // Modulate speed according to turning. The more we have to turn, the slower we go + // + const Real QUARTERPI = PI / 4.0f; + Real angleCoeff = (Real)fabs( relAngle ) / (QUARTERPI); + if (angleCoeff > 1.0f) + angleCoeff = 1.0; + + Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; + + Real actualSpeed = physics->getForwardSpeed2D(); + + if (moveBackwards) { + actualSpeed = -actualSpeed; + } + + //Real slowDownDist = (actualSpeed - m_template->m_minSpeed) / getBraking(); + Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); + if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + { + goalSpeed = m_template->m_minSpeed; + } + + // + // Maintain goal speed + // + Real speedDelta = goalSpeed - actualSpeed; + if (moveBackwards) { + speedDelta = -goalSpeed+actualSpeed; + } + if (speedDelta != 0.0f) + { + Real mass = physics->getMass(); + Real acceleration; + if (moveBackwards) { + acceleration = (speedDelta < 0.0f) ? -maxAcceleration : getBraking(); + } else { + acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); + } + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + const Coord3D *dir = obj->getUnitDirectionVector2D(); + + Coord3D force; + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; + force.z = 0.0f; + + // apply forces to object + physics->applyMotiveForce( &force ); + } +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ +#ifdef CIRCLE_FOR_LANDING + if (m_circleThresh > 0.0f) + { + // if we are going a mostly-vertical maneuver, circle in order to + // gain/lose altitude, then resume course... + const Coord3D* pos = obj->getPosition(); + Real dx = goalPos.x - pos->x; + Real dy = goalPos.y - pos->y; + Real dz = goalPos.z - pos->z; + if (fabs(dz) > m_circleThresh) + { + // aim for the spot on the opposite side of the circle. + + // find the direction towards our goal pos + Real angleTowardPos = + (isNearlyZero(dx) && isNearlyZero(dy)) ? + obj->getOrientation() : + atan2(dy, dx); + + Real aimDir = (PI - PI/8); + angleTowardPos += aimDir; + + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real turnRadius = calcMinTurnRadius(bdt, NULL) * 4; + + // project a spot "radius" dist away from it, in that dir + Coord3D desiredPos = goalPos; + desiredPos.x += Cos(angleTowardPos) * turnRadius; + desiredPos.y += Sin(angleTowardPos) * turnRadius; + moveTowardsPositionOther(obj, physics, desiredPos, 0, desiredSpeed); + return; + } + } +#endif + + // handle the 2D component. + moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionHover(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + // handle the 2D component. + moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + + // Only hover locomotors care about their OverWater special effects. (OverWater also affects speed, so this is not a client thing) + Coord3D newPosition = *obj->getPosition(); + if( TheTerrainLogic->isUnderwater( newPosition.x, newPosition.y ) ) + { + if( ! getFlag( OVER_WATER ) ) + { + // Change my model condition because I used to not be over water, but now I am + setFlag( OVER_WATER, TRUE ); + obj->setModelConditionState( MODELCONDITION_OVER_WATER ); + } + } + else + { + if( getFlag( OVER_WATER ) ) + { + // Here, I was, but now I'm not + setFlag( OVER_WATER, FALSE ); + obj->clearModelConditionState( MODELCONDITION_OVER_WATER ); + } + } +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + + Real maxForwardSpeed = getMaxSpeedForCondition(bdt); + desiredSpeed = clamp(m_template->m_minSpeed, desiredSpeed, maxForwardSpeed); + Real actualForwardSpeed = physics->getForwardSpeed3D(); + + if (getBraking() > 0) + { + //Real slowDownDist = (actualForwardSpeed - m_template->m_minSpeed) / getBraking(); + Real slowDownDist = calcSlowDownDist(actualForwardSpeed, m_template->m_minSpeed, getBraking()); + if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + desiredSpeed = m_template->m_minSpeed; + } + + Coord3D localGoalPos = goalPos; +#ifdef USE_ZDIR_DAMPING + Real zDirDamping = 0.0f; +#endif + + //out of the handleBehaviorZ() function + Coord3D pos = *obj->getPosition(); + if( m_preferredHeight != 0.0f && !getFlag(PRECISE_Z_POS) ) + { + // If we have a preferred flight height, and we haven't been told explicitly to ignore it... + Real surfaceHt = getSurfaceHtAtPt(pos.x, pos.y); + localGoalPos.z = m_preferredHeight + surfaceHt; +// localGoalPos.z = goalPos.z; + Real delta = localGoalPos.z - pos.z; + delta *= getPreferredHeightDamping(); + localGoalPos.z = pos.z + delta; + +#ifdef USE_ZDIR_DAMPING + // closer we get to the preferred height, less we adjust z-thrust, + // so we tend to "level out" at that height. we don't use this till + // below, but go ahead and calc it now... + Real MAX_VERTICAL_DAMP_RANGE = m_preferredHeight * 0.5; + delta = fabs(delta); + if (delta > MAX_VERTICAL_DAMP_RANGE) + delta = MAX_VERTICAL_DAMP_RANGE; + zDirDamping = 1.0f - (delta / MAX_VERTICAL_DAMP_RANGE); +#endif + } + + Vector3 forwardDir = obj->getTransformMatrix()->Get_X_Vector(); + + // Maintain goal speed + Real forwardSpeedDelta = desiredSpeed - actualForwardSpeed; + Real maxAccel = (forwardSpeedDelta > 0.0f || getBraking() == 0) ? getMaxAcceleration(bdt) : -getBraking(); + Real maxTurnRate = getMaxTurnRate(bdt); + + // what direction do we need to thrust in, in order to reach the goalpos? + Vector3 desiredThrustDir; + calcDirectionToApplyThrust(obj, physics, localGoalPos, maxAccel, desiredThrustDir); + + // we might not be able to thrust in that dir, so thrust as closely as we can + Real maxThrustAngle = (maxTurnRate > 0) ? (m_template->m_maxThrustAngle) : 0; + Vector3 thrustDir; + Real thrustAngle = tryToRotateVector3D(maxThrustAngle, forwardDir, desiredThrustDir, thrustDir); + + // note that we are trying to orient in the direction of our vel, not the dir of our thrust. + if (!isNearlyZero(physics->getVelocityMagnitude())) + { + const Coord3D* veltmp = physics->getVelocity(); + Vector3 vel(veltmp->x, veltmp->y, veltmp->z); + Bool adjust = true; + if( obj->getStatusBits().test( OBJECT_STATUS_BRAKING ) ) + { + //Real closeInDist = 150.0f; // TODO: get/set this from missileAI? + //Real af = 1.0f - __min((onPathDistToGoal / closeInDist), 1.0); + + //if (af > 0.0f) { + + // vel.Set( + // vel.X * (1.0f - af) + (goalPos.x - pos.x) * af, + // vel.Y * (1.0f - af) + (goalPos.y - pos.y) * af, + // vel.Z * (1.0f - af) + (goalPos.z - pos.z) * af + // ); + // if (isNearlyZero(sqr(vel.X) + sqr(vel.Y) + sqr(vel.Z))) { + // // we are at target. + // adjust = false; + // } + // maxTurnRate = (1.0f + (af * 2.0f) ) * maxTurnRate; + //} + + // DEBUG_LOG((">>> moveTowardsPositionThrust - Braking - maxTurnRate = %f\n", maxTurnRate)); + + // align to target, cause that's where we're going anyway. + + vel.Set(goalPos.x - pos.x, goalPos.y-pos.y, goalPos.z-pos.z); + if (isNearlyZero(sqr(vel.X)+sqr(vel.Y)+sqr(vel.Z))) { + // we are at target. + adjust = false; + } + maxTurnRate = 3*maxTurnRate; + } +#ifdef USE_ZDIR_DAMPING + if (zDirDamping != 0.0f) + { + Vector3 vel2D(veltmp->x, veltmp->y, 0); + // no need to normalize -- this call does that internally + tryToRotateVector3D(-zDirDamping, vel, vel2D, vel); + } +#endif + if (adjust) { + /*Real orient =*/ tryToOrientInThisDirection3D(obj, maxTurnRate, vel); + } + } + + if (forwardSpeedDelta != 0.0f || thrustAngle != 0.0f) + { + if (maxForwardSpeed <= 0.0f) + { + maxForwardSpeed = 0.01f; // In some cases, this is 0, hack for now. jba. + } + Real damping = clamp(0.0f, maxAccel / maxForwardSpeed, 1.0f); + Vector3 curVel(physics->getVelocity()->x, physics->getVelocity()->y, physics->getVelocity()->z); + + Vector3 accelVec = thrustDir * maxAccel - curVel * damping; + //DEBUG_LOG(("accel %f (max %f) vel %f (max %f) damping %f\n",accelVec.Length(),maxAccel,curVel.Length(),maxForwardSpeed,damping)); + + Real mass = physics->getMass(); + + Coord3D force; + force.x = mass * accelVec.X; + force.y = mass * accelVec.Y; + force.z = mass * accelVec.Z; + + // apply forces to object + physics->applyMotiveForce( &force ); + } +} + +//------------------------------------------------------------------------------------------------- +/*static*/ Real Locomotor::getSurfaceHtAtPt(Real x, Real y) +{ + Real ht = 0; + + Real z,waterZ; + if (TheTerrainLogic->isUnderwater(x, y, &waterZ, &z)) { + ht += waterZ; + } else { + ht += z; + } + + return ht; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::calcLiftToUseAtPt(Object* obj, PhysicsBehavior *physics, Real curZ, Real surfaceAtPt, Real preferredHeight) +{ + /* + take the classic equation: + + x = x0 + v*t + 0.5*a*t^2 + + and solve for acceleration. + */ + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxGrossLift = getMaxLift(bdt); + Real maxNetLift = maxGrossLift + TheGlobalData->m_gravity; // note that gravity is always negative. + if (maxNetLift < 0) + maxNetLift = 0; + Real curVelZ = physics->getVelocity()->z; + // going down, braking is limited by net lift; going up, braking is limited by gravity + Real maxAccel; + if (getFlag(ULTRA_ACCURATE)) + maxAccel = (curVelZ < 0) ? 2*maxNetLift : -2*maxNetLift; + else + maxAccel = (curVelZ < 0) ? maxNetLift : TheGlobalData->m_gravity; + // see how far we need to slow to dead stop, given max braking + Real desiredAccel; + const Real TINY_ACCEL = 0.001f; + if (fabs(maxAccel) > TINY_ACCEL) + { + Real deltaZ = preferredHeight - curZ; + // calc how far it will take for us to go from cur speed to zero speed, at max accel. + // Real brakeDist = calcSlowDownDist(curVelZ, 0, maxAccel); + // in theory, the above is the correct calculation, but in practice, + // doesn't work in some situations (eg, opening of USA01 map). Why, I dunno. + // But for now I have gone back to the old, looks-incorrect-to-me-but-works calc. (srj) + Real brakeDist = (sqr(curVelZ) / fabs(maxAccel)); + if (fabs(brakeDist) > fabs(deltaZ)) + { + // if the dist-to-accel (or dist-to-brake) is further than the dist-to-go, + // use the max accel. + desiredAccel = maxAccel; + } + else if (fabs(curVelZ) > m_template->m_speedLimitZ) + { + // or, if we're going too fast, limit it here. + desiredAccel = m_template->m_speedLimitZ - curVelZ; + } + else + { + // ok, figure out the correct accel to use to get us there at zero. + // + // dz = v t + 0.5 a t^2 + // thus + // a = 2(dz - v t)/t^2 + // and + // t = (-v +- sqrt(v*v + 2*a*dz))/a + // + // but if we assume t=1, then + // a=2(dz-v) + // then, plug it back in and see if t is really 1... + desiredAccel = 2.0f * (deltaZ - curVelZ); + } + } + else + { + desiredAccel = 0.0f; + } + Real liftToUse = desiredAccel - TheGlobalData->m_gravity; + if (getFlag(ULTRA_ACCURATE)) + { + // in ultra-accurate mode, we allow cheating. + const Real UP_FACTOR = 3.0f; + if (liftToUse > UP_FACTOR*maxGrossLift) + liftToUse = UP_FACTOR*maxGrossLift; + // srj sez: we used to clip lift to zero here (not allowing neg lift). + // however, I now think that allowing neg lift in ultra-accurate mode is + // a good and desirable thing; in particular, it enables jets to complete + // "short" landings more accurately (previously they sometimes would "float" + // down, which sucked.) if you need to bump this back to zero, check it carefully... + else if (liftToUse < -maxGrossLift) + liftToUse = -maxGrossLift; + } + else + { + if (liftToUse > maxGrossLift) + liftToUse = maxGrossLift; + else if (liftToUse < 0.0f) + liftToUse = 0.0f; + } + + return liftToUse; +} + +//------------------------------------------------------------------------------------------------- +PhysicsTurningType Locomotor::rotateObjAroundLocoPivot(Object* obj, const Coord3D& goalPos, + Real maxTurnRate, Real *relAngle) +{ + Real angle = obj->getOrientation(); + Real offset = getTurnPivotOffset(); + + PhysicsTurningType turn = TURN_NONE; + + if (getFlag(IS_BRAKING)) offset = 0.0f; // When braking we do exact movement towards goal, instead of physics. + //Rotating about pivot moves the object, and can make us miss our goal, so it is disabled. jba. + if (offset != 0.0f) + { + Real radius = obj->getGeometryInfo().getBoundingCircleRadius(); + Real turnPointOffset = offset * radius; + + Coord3D turnPos = *obj->getPosition(); + const Coord3D* dir = obj->getUnitDirectionVector2D(); + turnPos.x += dir->x * turnPointOffset; + turnPos.y += dir->y * turnPointOffset; + Real dx =goalPos.x - turnPos.x; + Real dy = goalPos.y - turnPos.y; + // If we are very close to the goal, we twitch due to rounding error. So just return. jba. + if (fabs(dx)<0.1f && fabs(dy)<0.1f) return TURN_NONE; + Real desiredAngle = atan2(dy, dx); + Real amount = stdAngleDiff(desiredAngle, angle); + if (relAngle) *relAngle = amount; + if (amount>maxTurnRate) { + amount = maxTurnRate; + turn = TURN_POSITIVE; + } else if (amount < -maxTurnRate) { + amount = -maxTurnRate; + turn = TURN_NEGATIVE; + } else { + turn = TURN_NONE; + } + +#if 0 + Coord3D desiredPos = *obj->getPosition(); // well, desired Dir, anyway + desiredPos.x += Cos(angle + amount) * radius; + desiredPos.y += Sin(angle + amount) * radius; + + + // so, the thing is, we want to rotate ourselves so that our *center* is rotated + // by the given amount, but the rotation must be around turnPos. so do a little + // back-calculation. + Real angleDesiredForTurnPos = atan2(desiredPos.y - turnPos.y, desiredPos.x - turnPos.x); + amount = angleDesiredForTurnPos - angle; +#endif + /// @todo srj -- there's probably a more efficient & more direct way to do this. find it. + Matrix3D mtx; + Matrix3D tmp(1); + tmp.Translate(turnPos.x, turnPos.y, 0); + tmp.In_Place_Pre_Rotate_Z(amount); + tmp.Translate(-turnPos.x, -turnPos.y, 0); + + mtx.mul(tmp, *obj->getTransformMatrix()); + + obj->setTransformMatrix(&mtx); + } + else + { + Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + Real amount = stdAngleDiff(desiredAngle, angle); + if (relAngle) *relAngle = amount; + if (amount>maxTurnRate) { + amount = maxTurnRate; + turn = TURN_POSITIVE; + } else if (amount < -maxTurnRate) { + amount = -maxTurnRate; + turn = TURN_NEGATIVE; + } else { + turn = TURN_NONE; + } + obj->setOrientation( normalizeAngle(angle + amount) ); + } + return turn; +} + +//------------------------------------------------------------------------------------------------- +/* + return true if we can maintain the position without being called every frame (eg, we are + resting on the ground), false if not (eg, we are hovering or circling) +*/ +Bool Locomotor::handleBehaviorZ(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos) +{ + Bool requiresConstantCalling = TRUE; + + // keep the agent aligned on the terrain + switch(m_template->m_behaviorZ) + { + case Z_NO_Z_MOTIVE_FORCE: + // nothing to do. + requiresConstantCalling = FALSE; + break; + + case Z_SEA_LEVEL: + requiresConstantCalling = TRUE; + if( !obj->isDisabledByType( DISABLED_HELD ) ) + { + Coord3D pos = *obj->getPosition(); + Real waterZ; + if (TheTerrainLogic->isUnderwater(pos.x, pos.y, &waterZ)) { + pos.z = waterZ; + } else { + pos.z = TheTerrainLogic->getLayerHeight(pos.x, pos.y, obj->getLayer()); + } + obj->setPosition(&pos); + } + break; + + case Z_FIXED_SURFACE_RELATIVE_HEIGHT: + case Z_FIXED_ABSOLUTE_HEIGHT: + requiresConstantCalling = TRUE; + { + Coord3D pos = *obj->getPosition(); + Bool surfaceRel = (m_template->m_behaviorZ == Z_FIXED_SURFACE_RELATIVE_HEIGHT); + Real surfaceHt = surfaceRel ? getSurfaceHtAtPt(pos.x, pos.y) : 0.0f; + pos.z = m_preferredHeight + (surfaceRel ? surfaceHt : 0); + obj->setPosition(&pos); + } + break; + + case Z_RELATIVE_TO_GROUND_AND_BUILDINGS: + requiresConstantCalling = TRUE; + { + // srj sez: use getGroundOrStructureHeight(), because someday it will cache building heights... + Coord3D pos = *obj->getPosition(); + Real surfaceHt = ThePartitionManager->getGroundOrStructureHeight(pos.x, pos.y); + + pos.z = m_preferredHeight + surfaceHt; + + obj->setPosition(&pos); + + } + break; + case Z_SMOOTH_RELATIVE_TO_HIGHEST_LAYER: + requiresConstantCalling = TRUE; + { + if (m_preferredHeight != 0.0f || getFlag(PRECISE_Z_POS)) + { + Coord3D pos = *obj->getPosition(); + + // srj sez: if we aren't on the ground, never find the ground layer + PathfindLayerEnum layerAtDest = obj->getLayer(); + if (layerAtDest == LAYER_GROUND) + layerAtDest = TheTerrainLogic->getHighestLayerForDestination( &pos ); + + Real surfaceHt; + Coord3D normal; + const Bool clip = false; // return the height, even if off the edge of the bridge proper. + surfaceHt = TheTerrainLogic->getLayerHeight( pos.x, pos.y, layerAtDest, &normal, clip ); + + Real preferredHeight = m_preferredHeight + surfaceHt; + if (getFlag(PRECISE_Z_POS)) + preferredHeight = goalPos.z; + + Real delta = preferredHeight - pos.z; + delta *= getPreferredHeightDamping(); + preferredHeight = pos.z + delta; + + Real liftToUse = calcLiftToUseAtPt(obj, physics, pos.z, surfaceHt, preferredHeight); + + //DEBUG_LOG(("HandleBZ %d LiftToUse %f\n",TheGameLogic->getFrame(),liftToUse)); + if (liftToUse != 0.0f) + { + Coord3D force; + force.x = 0.0f; + force.y = 0.0f; + force.z = liftToUse * physics->getMass(); + physics->applyMotiveForce(&force); + } + } + } + break; + + case Z_SURFACE_RELATIVE_HEIGHT: + case Z_ABSOLUTE_HEIGHT: + requiresConstantCalling = TRUE; + { + if (m_preferredHeight != 0.0f || getFlag(PRECISE_Z_POS)) + { + Coord3D pos = *obj->getPosition(); + + Bool surfaceRel = (m_template->m_behaviorZ == Z_SURFACE_RELATIVE_HEIGHT); + Real surfaceHt = surfaceRel ? getSurfaceHtAtPt(pos.x, pos.y) : 0.0f; + Real preferredHeight = m_preferredHeight + (surfaceRel ? surfaceHt : 0); + if (getFlag(PRECISE_Z_POS)) + preferredHeight = goalPos.z; + + Real delta = preferredHeight - pos.z; + delta *= getPreferredHeightDamping(); + preferredHeight = pos.z + delta; + + Real liftToUse = calcLiftToUseAtPt(obj, physics, pos.z, surfaceHt, preferredHeight); + + //DEBUG_LOG(("HandleBZ %d LiftToUse %f\n",TheGameLogic->getFrame(),liftToUse)); + if (liftToUse != 0.0f) + { + Coord3D force; + force.x = 0.0f; + force.y = 0.0f; + force.z = liftToUse * physics->getMass(); + physics->applyMotiveForce(&force); + } + } + } + break; + } + + return requiresConstantCalling; +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxAcceleration = getMaxAcceleration(bdt); + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + Real maxSpeed = getMaxSpeedForCondition(bdt); + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + Real goalSpeed = desiredSpeed; + Real actualSpeed = physics->getForwardSpeed2D(); + + // Locomotion for other things, ie don't know what it is jba :) + // + // Orient toward goal position + // exception: if very close (ie, we could get there in 2 frames or less),\ + // and ULTRA_ACCURATE, just slide into place + // + const Coord3D* pos = obj->getPosition(); + Coord3D dirToApplyForce = *obj->getUnitDirectionVector2D(); + +//DEBUG_ASSERTLOG(!getFlag(ULTRA_ACCURATE),("thresh %f %f (%f %f)\n", +//fabs(goalPos.y - pos->y),fabs(goalPos.x - pos->x), +//fabs(goalPos.y - pos->y)/goalSpeed,fabs(goalPos.x - pos->x)/goalSpeed)); + if (getFlag(ULTRA_ACCURATE) && + fabs(goalPos.y - pos->y) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor && + fabs(goalPos.x - pos->x) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor) + { + // don't turn, just slide in the right direction + physics->setTurning(TURN_NONE); + dirToApplyForce.x = goalPos.x - pos->x; + dirToApplyForce.y = goalPos.y - pos->y; + dirToApplyForce.z = 0.0f; + dirToApplyForce.normalize(); + } + else + { + PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos); + physics->setTurning(rotating); + } + + if (!getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + { + Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); + if (onPathDistToGoal < slowDownDist) + { + goalSpeed = m_template->m_minSpeed; + } + } + + // + // Maintain goal speed + // + Real speedDelta = goalSpeed - actualSpeed; + if (speedDelta != 0.0f) + { + Real mass = physics->getMass(); + Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + Coord3D force; + force.x = accelForce * dirToApplyForce.x; + force.y = accelForce * dirToApplyForce.y; + force.z = 0.0f; + + // apply forces to object + physics->applyMotiveForce( &force ); + } + +} + + +//------------------------------------------------------------------------------------------------- +/* + return true if we can maintain the position without being called every frame (eg, we are + resting on the ground), false if not (eg, we are hovering or circling) +*/ +Bool Locomotor::locoUpdate_maintainCurrentPosition(Object* obj) +{ + if (!getFlag(MAINTAIN_POS_IS_VALID)) + { + m_maintainPos = *obj->getPosition(); + setFlag(MAINTAIN_POS_IS_VALID, true); + } + + m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; + setFlag(IS_BRAKING, false); + PhysicsBehavior *physics = obj->getPhysics(); + if (physics == NULL) + { + DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); + return TRUE; + } + +#ifdef DEBUG_OBJECT_ID_EXISTS +// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_maintainCurrentPosition %f %f %f, speed %f (%f)\n",m_maintainPos.x,m_maintainPos.y,m_maintainPos.z,physics->getSpeed(),physics->getForwardSpeed2D())); +#endif + + Bool requiresConstantCalling = TRUE; // assume the worst. + switch (m_template->m_appearance) + { + case LOCO_THRUST: + maintainCurrentPositionThrust(obj, physics); + requiresConstantCalling = TRUE; + break; + case LOCO_LEGS_TWO: + maintainCurrentPositionLegs(obj, physics); + requiresConstantCalling = FALSE; + break; + case LOCO_CLIMBER: + maintainCurrentPositionLegs(obj, physics); + requiresConstantCalling = FALSE; + break; + case LOCO_WHEELS_FOUR: + case LOCO_MOTORCYCLE: + maintainCurrentPositionWheels(obj, physics); + requiresConstantCalling = FALSE; + break; + case LOCO_TREADS: + maintainCurrentPositionTreads(obj, physics); + requiresConstantCalling = FALSE; + break; + case LOCO_HOVER: + maintainCurrentPositionHover(obj, physics); + requiresConstantCalling = TRUE; + break; + case LOCO_WINGS: + maintainCurrentPositionWings(obj, physics); + requiresConstantCalling = TRUE; + break; + case LOCO_OTHER: + default: + maintainCurrentPositionOther(obj, physics); + requiresConstantCalling = TRUE; + break; + } + + // but we do need to do this even if not moving, for hovering/Thrusting things. + if (handleBehaviorZ(obj, physics, m_maintainPos)) + requiresConstantCalling = TRUE; + + return requiresConstantCalling; +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::maintainCurrentPositionThrust(Object* obj, PhysicsBehavior *physics) +{ + DEBUG_ASSERTCRASH(getFlag(MAINTAIN_POS_IS_VALID), ("invalid maintain pos")); + /// @todo srj -- should these also use the "circling radius" stuff, like wings? + moveTowardsPositionThrust(obj, physics, m_maintainPos, 0, getMinSpeed()); +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physics) +{ + DEBUG_ASSERTCRASH(getFlag(MAINTAIN_POS_IS_VALID), ("invalid maintain pos")); + physics->setTurning(TURN_NONE); + if (physics->isMotive() && obj->isAboveTerrain()) // no need to stop something that isn't moving (or is just sitting on the ground) + { + + // aim for the spot on the opposite side of the circle. + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real turnRadius = m_template->m_circlingRadius; + if (turnRadius == 0.0f) + turnRadius = calcMinTurnRadius(bdt, NULL); + + // find the direction towards our "maintain pos" + const Coord3D* pos = obj->getPosition(); + Real dx = m_maintainPos.x - pos->x; + Real dy = m_maintainPos.y - pos->y; + Real angleTowardMaintainPos = + (isNearlyZero(dx) && isNearlyZero(dy)) ? + obj->getOrientation() : + atan2(dy, dx); + + Real aimDir = (PI - PI/8); + if (turnRadius < 0) + { + turnRadius = -turnRadius; + aimDir = -aimDir; + } + angleTowardMaintainPos += aimDir; + + // project a spot "radius" dist away from it, in that dir + Coord3D desiredPos = m_maintainPos; + desiredPos.x += Cos(angleTowardMaintainPos) * turnRadius; + desiredPos.y += Sin(angleTowardMaintainPos) * turnRadius; + moveTowardsPositionWings(obj, physics, desiredPos, 0, m_template->m_minSpeed); + } +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physics) +{ + physics->setTurning(TURN_NONE); + if (physics->isMotive()) // no need to stop something that isn't moving. + { + DEBUG_ASSERTCRASH(m_template->m_minSpeed == 0.0f, ("HOVER should always have zero minSpeeds (otherwise, they WING)")); + + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxAcceleration = getMaxAcceleration(bdt); + Real actualSpeed = physics->getForwardSpeed2D(); + // + // Stop + // + Real minSpeed = max( 1.0E-10f, m_template->m_minSpeed ); + Real speedDelta = minSpeed - actualSpeed; + if (fabs(speedDelta) > minSpeed) + { + Real mass = physics->getMass(); + Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + const Coord3D *dir = obj->getUnitDirectionVector2D(); + + Coord3D force; + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; + force.z = 0.0f; + + + // Apply a random kick (if applicable) to dirty-up visually. + // The idea is that chopper pilots have to do course corrections all the time + // Because of changes in wind, pressure, etc. + // Those changes are added here, then the + + + + // apply forces to object + physics->applyMotiveForce( &force ); + } + } + +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::maintainCurrentPositionOther(Object* obj, PhysicsBehavior *physics) +{ + + physics->setTurning(TURN_NONE); + if (physics->isMotive()) // no need to stop something that isn't moving. + { + physics->scrubVelocity2D(0); // stop. + } + +} + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- +LocomotorSet::LocomotorSet() +{ + m_locomotors.clear(); + m_validLocomotorSurfaces = 0; + m_downhillOnly = FALSE; + +} + +//------------------------------------------------------------------------------------------------- +LocomotorSet::LocomotorSet(const LocomotorSet& that) +{ + DEBUG_CRASH(("unimplemented")); +} + +//------------------------------------------------------------------------------------------------- +LocomotorSet& LocomotorSet::operator=(const LocomotorSet& that) +{ + if (this != &that) + { + DEBUG_CRASH(("unimplemented")); + } + return *this; +} + +//------------------------------------------------------------------------------------------------- +LocomotorSet::~LocomotorSet() +{ + clear(); +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void LocomotorSet::crc( Xfer *xfer ) +{ + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void LocomotorSet::xfer( Xfer *xfer ) +{ + // version + const XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // count of vector + UnsignedShort count = m_locomotors.size(); + xfer->xferUnsignedShort( &count ); + + // data + if (xfer->getXferMode() == XFER_SAVE) + { + for (LocomotorVector::iterator it = m_locomotors.begin(); it != m_locomotors.end(); ++it) + { + Locomotor* loco = *it; + AsciiString name = loco->getTemplateName(); + xfer->xferAsciiString(&name); + xfer->xferSnapshot(loco); + } + } + else if (xfer->getXferMode() == XFER_LOAD) + { + // vector should be empty at this point + if (m_locomotors.empty() == FALSE) + { + DEBUG_CRASH(( "LocomotorSet::xfer - vector is not empty, but should be\n" )); + throw XFER_LIST_NOT_EMPTY; + } + + for (UnsignedShort i = 0; i < count; ++i) + { + AsciiString name; + xfer->xferAsciiString(&name); + + const LocomotorTemplate* lt = TheLocomotorStore->findLocomotorTemplate(NAMEKEY(name)); + if (lt == NULL) + { + DEBUG_CRASH(( "LocomotorSet::xfer - template %s not found\n", name.str() )); + throw XFER_UNKNOWN_STRING; + } + + Locomotor* loco = TheLocomotorStore->newLocomotor(lt); + xfer->xferSnapshot(loco); + m_locomotors.push_back(loco); + } + } + + xfer->xferInt(&m_validLocomotorSurfaces); + xfer->xferBool(&m_downhillOnly); + +} + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void LocomotorSet::loadPostProcess( void ) +{ + +} // end loadPostProcess + +//------------------------------------------------------------------------------------------------- +void LocomotorSet::xferSelfAndCurLocoPtr(Xfer *xfer, Locomotor** loco) +{ + xfer->xferSnapshot(this); + + if (xfer->getXferMode() == XFER_SAVE) + { + AsciiString name; + if (*loco) + name = (*loco)->getTemplateName(); + xfer->xferAsciiString(&name); + } + else if (xfer->getXferMode() == XFER_LOAD) + { + AsciiString name; + xfer->xferAsciiString(&name); + + if (name.isEmpty()) + { + *loco = NULL; + } + else + { + for (int i = 0; i < m_locomotors.size(); ++i) + { + if (m_locomotors[i]->getTemplateName() == name) + { + *loco = m_locomotors[i]; + return; + } + } + + DEBUG_CRASH(( "LocomotorSet::xfer - template %s not found\n", name.str() )); + throw XFER_UNKNOWN_STRING; + } + } +} + +//------------------------------------------------------------------------------------------------- +void LocomotorSet::clear() +{ + for (int i = 0; i < m_locomotors.size(); ++i) + { + if (m_locomotors[i]) + m_locomotors[i]->deleteInstance(); + } + m_locomotors.clear(); + m_validLocomotorSurfaces = 0; + m_downhillOnly = FALSE; +} + +//------------------------------------------------------------------------------------------------- +void LocomotorSet::addLocomotor(const LocomotorTemplate* lt) +{ + Locomotor* loco = TheLocomotorStore->newLocomotor(lt); + if (loco) + { + m_locomotors.push_back(loco); + m_validLocomotorSurfaces |= loco->getLegalSurfaces(); + if (loco->getIsDownhillOnly()) + { + m_downhillOnly = TRUE; + } + else // Previous locos were gravity only, but this one isn't! + { + DEBUG_ASSERTCRASH(!m_downhillOnly,("LocomotorSet, YOU CAN NOT MIX DOWNHILL-ONLY LOCOMOTORS WITH NON-DOWNHILL-ONLY ONES.")); + } + + } +} + +//------------------------------------------------------------------------------------------------- +Locomotor* LocomotorSet::findLocomotor(LocomotorSurfaceTypeMask t) +{ + for (LocomotorVector::iterator it = m_locomotors.begin(); it != m_locomotors.end(); ++it) + { + Locomotor* curLocomotor = *it; + if (curLocomotor && (curLocomotor->getLegalSurfaces() & t)) + return curLocomotor; + } + return NULL; +} + + diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index ce7f2843c6b..e83712e972d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -1,6479 +1,6505 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE Object.cpp //////////////////////////////////////////////////////////////////////////////// -// Simple base object -// Author: Michael S. Booth, October 2000 -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine -#define DEFINE_WEAPONCONDITIONMAP -#include "Common/BitFlagsIO.h" -#include "Common/BuildAssistant.h" -#include "Common/Dict.h" -#include "Common/GameCommon.h" -#include "Common/GameEngine.h" -#include "Common/GameState.h" -#include "Common/ModuleFactory.h" -#include "Common/Player.h" -#include "Common/PlayerList.h" -#include "Common/Radar.h" -#include "Common/SpecialPower.h" -#include "Common/Team.h" -#include "Common/ThingFactory.h" -#include "Common/ThingTemplate.h" -#include "Common/Upgrade.h" -#include "Common/WellKnownKeys.h" -#include "Common/Xfer.h" -#include "Common/XferCRC.h" -#include "Common/PerfTimer.h" - -#include "GameClient/Anim2D.h" -#include "GameClient/ControlBar.h" -#include "GameClient/Drawable.h" -#include "GameClient/Eva.h" -#include "GameClient/GameClient.h" -#include "GameClient/InGameUI.h" - -#include "GameLogic/AI.h" -#include "GameLogic/AIPathfind.h" -#include "GameLogic/ExperienceTracker.h" -#include "GameLogic/FiringTracker.h" -#include "GameLogic/GameLogic.h" -#include "GameLogic/Locomotor.h" - -#include "GameLogic/Module/AIUpdate.h" -#include "GameLogic/Module/AutoHealBehavior.h" -#include "GameLogic/Module/BehaviorModule.h" -#include "GameLogic/Module/BodyModule.h" -#include "GameLogic/Module/CollideModule.h" -#include "GameLogic/Module/ContainModule.h" -#include "GameLogic/Module/CountermeasuresBehavior.h" -#include "GameLogic/Module/CreateModule.h" -#include "GameLogic/Module/DamageModule.h" -#include "GameLogic/Module/DeletionUpdate.h" -#include "GameLogic/Module/DestroyModule.h" -#include "GameLogic/Module/DieModule.h" -#include "GameLogic/Module/DozerAIUpdate.h" -#include "GameLogic/Module/ObjectDefectionHelper.h" -#include "GameLogic/Module/ObjectRepulsorHelper.h" -#include "GameLogic/Module/ObjectSMCHelper.h" -#include "GameLogic/Module/ObjectWeaponStatusHelper.h" -#include "GameLogic/Module/OverchargeBehavior.h" -#include "GameLogic/Module/PhysicsUpdate.h" -#include "GameLogic/Module/PowerPlantUpgrade.h" -#include "GameLogic/Module/ProductionUpdate.h" -#include "GameLogic/Module/RadarUpgrade.h" -#include "GameLogic/Module/RebuildHoleBehavior.h" -#include "GameLogic/Module/SpawnBehavior.h" -#include "GameLogic/Module/SpecialPowerModule.h" -#include "GameLogic/Module/SpecialAbilityUpdate.h" -#include "GameLogic/Module/StatusDamageHelper.h" -#include "GameLogic/Module/StickyBombUpdate.h" -#include "GameLogic/Module/SubdualDamageHelper.h" -#include "GameLogic/Module/TempWeaponBonusHelper.h" -#include "GameLogic/Module/ToppleUpdate.h" -#include "GameLogic/Module/UpdateModule.h" -#include "GameLogic/Module/UpgradeModule.h" - -#include "GameLogic/Object.h" -#include "GameLogic/PartitionManager.h" -#include "GameLogic/PolygonTrigger.h" -#include "GameLogic/ScriptEngine.h" -#include "GameLogic/Weapon.h" -#include "GameLogic/WeaponSet.h" -#include "GameLogic/Module/RadarUpdate.h" -#include "GameLogic/Module/PowerPlantUpdate.h" - -#include "Common/CRCDebug.h" -#include "Common/MiscAudio.h" -#include "Common/AudioEventInfo.h" -#include "Common/DynamicAudioEventInfo.h" - -#ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -#ifdef DEBUG_OBJECT_ID_EXISTS -ObjectID TheObjectIDToDebug = INVALID_ID; -#endif - -// ------------------------------------------------------------------------------------------------ -static const ModelConditionFlags s_allWeaponFireFlags[WEAPONSLOT_COUNT] = -{ - MAKE_MODELCONDITION_MASK5( - MODELCONDITION_FIRING_A, - MODELCONDITION_BETWEEN_FIRING_SHOTS_A, - MODELCONDITION_RELOADING_A, - MODELCONDITION_PREATTACK_A, - MODELCONDITION_USING_WEAPON_A - ), - MAKE_MODELCONDITION_MASK5( - MODELCONDITION_FIRING_B, - MODELCONDITION_BETWEEN_FIRING_SHOTS_B, - MODELCONDITION_RELOADING_B, - MODELCONDITION_PREATTACK_B, - MODELCONDITION_USING_WEAPON_B - ), - MAKE_MODELCONDITION_MASK5( - MODELCONDITION_FIRING_C, - MODELCONDITION_BETWEEN_FIRING_SHOTS_C, - MODELCONDITION_RELOADING_C, - MODELCONDITION_PREATTACK_C, - MODELCONDITION_USING_WEAPON_C - ), - MAKE_MODELCONDITION_MASK5( - MODELCONDITION_FIRING_D, - MODELCONDITION_BETWEEN_FIRING_SHOTS_D, - MODELCONDITION_RELOADING_D, - MODELCONDITION_PREATTACK_D, - MODELCONDITION_USING_WEAPON_D - ), - MAKE_MODELCONDITION_MASK5( - MODELCONDITION_FIRING_E, - MODELCONDITION_BETWEEN_FIRING_SHOTS_E, - MODELCONDITION_RELOADING_E, - MODELCONDITION_PREATTACK_E, - MODELCONDITION_USING_WEAPON_E - ), - MAKE_MODELCONDITION_MASK5( - MODELCONDITION_FIRING_F, - MODELCONDITION_BETWEEN_FIRING_SHOTS_F, - MODELCONDITION_RELOADING_F, - MODELCONDITION_PREATTACK_F, - MODELCONDITION_USING_WEAPON_F - ), - MAKE_MODELCONDITION_MASK5( - MODELCONDITION_FIRING_G, - MODELCONDITION_BETWEEN_FIRING_SHOTS_G, - MODELCONDITION_RELOADING_G, - MODELCONDITION_PREATTACK_G, - MODELCONDITION_USING_WEAPON_G - ), - MAKE_MODELCONDITION_MASK5( - MODELCONDITION_FIRING_H, - MODELCONDITION_BETWEEN_FIRING_SHOTS_H, - MODELCONDITION_RELOADING_H, - MODELCONDITION_PREATTACK_H, - MODELCONDITION_USING_WEAPON_H - ) -}; - -//------------------------------------------------------------------------------------------------- -extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color); - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -AsciiString DebugDescribeObject(const Object *obj) -{ - if (!obj) - return ""; - - AsciiString ret; - - if (obj->getName().isNotEmpty()) - { - ret.format("Object %d (%s) [%s, owned by player %d (%ls)]", - obj->getID(), obj->getName().str(), obj->getTemplate()->getName().str(), - obj->getControllingPlayer()->getPlayerIndex(), - obj->getControllingPlayer()->getPlayerDisplayName().str()); - } - else - { - ret.format("Object %d [%s, owned by player %d (%ls)]", - obj->getID(), obj->getTemplate()->getName().str(), - obj->getControllingPlayer()->getPlayerIndex(), - obj->getControllingPlayer()->getPlayerDisplayName().str()); - } - - return ret; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Object::Object( const ThingTemplate *tt, const ObjectStatusMaskType &objectStatusMask, Team *team ) : - Thing(tt), - m_indicatorColor(0), - m_ai(NULL), - m_physics(NULL), - m_geometryInfo(tt->getTemplateGeometryInfo()), - m_containedBy(NULL), - m_xferContainedByID(INVALID_ID), - m_containedByFrame(0), - m_behaviors(NULL), - m_body(NULL), - m_contain(NULL), - m_stealth(NULL), - m_partitionData(NULL), - m_radarData(NULL), - m_drawable(NULL), - m_next(NULL), - m_prev(NULL), - m_team(NULL), - m_experienceTracker(NULL), - m_firingTracker(NULL), - m_repulsorHelper(NULL), - m_statusDamageHelper(NULL), - m_tempWeaponBonusHelper(NULL), - m_subdualDamageHelper(NULL), - m_smcHelper(NULL), - m_wsHelper(NULL), - m_defectionHelper(NULL), - m_partitionLastLook(NULL), - m_partitionRevealAllLastLook(NULL), - m_partitionLastShroud(NULL), - m_partitionLastThreat(NULL), - m_partitionLastValue(NULL), - m_smcUntil(NEVER), - m_privateStatus(0), - m_formationID(NO_FORMATION_ID), - m_isReceivingDifficultyBonus(FALSE), - m_singleUseCommandUsed(FALSE), - m_scriptStatus(0), - m_enteredOrExitedFrame(0), - m_visionSpiedMask (PLAYERMASK_NONE), - m_numTriggerAreasActive(0) -{ -#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) - m_hasDiedAlready = false; -#endif - //Modules have not been created yet! - m_modulesReady = false; - - // Force the thing template to use the most overridden version of itself - jkmcd - // Note that after this, the object will be using m_template, which forces the usage of the - // most overridden version of tt, so this is okay. - tt = (const ThingTemplate *) tt->getFinalOverride(); - - Int i, modIdx; - AsciiString modName; - - //Added By Sadullah Nader - //Initializations inserted - m_formationOffset.x = m_formationOffset.y = 0.0f; - m_iPos.zero(); - // - for (i = 0; i < MAX_PLAYER_COUNT; ++i) - { - m_visionSpiedBy[i] = 0; - } - - for( i = 0; i < DISABLED_COUNT; i++ ) - { - m_disabledTillFrame[ i ] = NEVER; - } - - m_weaponBonusCondition = 0; - m_curWeaponSetFlags.clear(); - - // sanity - if( TheGameLogic == NULL || tt == NULL ) - { - - assert( 0 ); - return; - - } // end if - - // Object's set of these persist for the life of the object. - m_partitionLastLook = newInstance(SightingInfo); - m_partitionLastLook->reset(); - m_partitionRevealAllLastLook = newInstance(SightingInfo); - m_partitionRevealAllLastLook->reset(); - m_partitionLastShroud = newInstance(SightingInfo); - m_partitionLastShroud->reset(); - m_partitionLastThreat = newInstance(SightingInfo); - m_partitionLastThreat->reset(); - m_partitionLastValue = newInstance(SightingInfo); - m_partitionLastValue->reset(); - - // must set ID to zero, since some of these set methods - // will cause network messages to be sent - // which use this ID. - m_id = INVALID_ID; - m_producerID = INVALID_ID; - m_builderID = INVALID_ID; - - m_status = objectStatusMask; - m_layer = LAYER_GROUND; - - m_group = NULL; - - m_constructionPercent = CONSTRUCTION_COMPLETE; // complete by default - - m_visionRange = tt->friend_calcVisionRange(); - m_shroudClearingRange = tt->friend_calcShroudClearingRange(); - if( m_shroudClearingRange == -1.0f ) - m_shroudClearingRange = m_visionRange;// Backwards compatible, and perfectly logical default to assign - m_shroudRange = 0.0f; - - m_singleUseCommandUsed = false; - - // assign unique object id - setID( TheGameLogic->allocateObjectID() ); - - // - // allocate any modules we need to, we should keep - // this at or near the end of the drawable construction so that we have - // all the valid data about the thing when we create the module - // - Int totalModules = tt->getBehaviorModuleInfo().getCount() + NUM_SLEEP_HELPERS; // need to take into account all the helper modules - - // allocate the publicModule arrays -// pool[]ify - m_behaviors = MSGNEW("ModulePtrs") BehaviorModule*[totalModules + 1]; - BehaviorModule** curB = m_behaviors; - const ModuleInfo& mi = tt->getBehaviorModuleInfo(); - - // set m_team to null before the first call, to avoid naughtiness... - // If no team is specified in the constructor, then assign the object - // to the neutral team. - setTeam(team ? team : ThePlayerList->getNeutralPlayer()->getDefaultTeam()); - - // the helpers are done first -- even before Behaviors! -- in case a module needs - // to call something that uses them. - static const NameKeyType smcHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_SMCHelper" ); - static ObjectSMCHelperModuleData smcModuleData; - smcModuleData.setModuleTagNameKey( smcHelperModuleDataTagNameKey ); - m_smcHelper = newInstance(ObjectSMCHelper)(this, &smcModuleData); - *curB++ = m_smcHelper; - - //Inactive bodies can't take special damage since they can't take damage - Bool isInactiveBody = FALSE; - for( Int infoIndex = 0; infoIndex < mi.getCount(); ++infoIndex ) - { - modName = mi.getNthName(infoIndex); - if (modName.isEmpty()) - continue; - - if( modName.compare("InactiveBody") == 0 ) - { - isInactiveBody = TRUE; - break; - } - } - - if( !isInactiveBody ) - { - static const NameKeyType statusHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_StatusDamageHelper" ); - static StatusDamageHelperModuleData statusModuleData; - statusModuleData.setModuleTagNameKey( statusHelperModuleDataTagNameKey ); - m_statusDamageHelper = newInstance(StatusDamageHelper)(this, &statusModuleData); - *curB++ = m_statusDamageHelper; - - static const NameKeyType subdualHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_SubdualDamageHelper" ); - static SubdualDamageHelperModuleData subdualModuleData; - subdualModuleData.setModuleTagNameKey( subdualHelperModuleDataTagNameKey ); - m_subdualDamageHelper = newInstance(SubdualDamageHelper)(this, &subdualModuleData); - *curB++ = m_subdualDamageHelper; - } - - if (TheAI != NULL - && TheAI->getAiData()->m_enableRepulsors - && isKindOf(KINDOF_CAN_BE_REPULSED)) - { - // if we can ever be a temporary-repulsor, make a repulsor helper. (srj) - static const NameKeyType repulsorHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_RepulsorHelper" ); - static ObjectRepulsorHelperModuleData repulsorModuleData; - repulsorModuleData.setModuleTagNameKey( repulsorHelperModuleDataTagNameKey ); - m_repulsorHelper = newInstance(ObjectRepulsorHelper)(this, &repulsorModuleData); - *curB++ = m_repulsorHelper; - } - - /** @todo srj -- figure out how to create this only on demand. - currently we don't have a good way to add/remove update modules from - an object on-the-fly, so we fake it here, and just skip the creation - if it is impossible for this object to ever defect... */ - - // shrubbery cannot defect. no, really. - if (!tt->isKindOf(KINDOF_SHRUBBERY)) - { - static const NameKeyType defectionModuleDataTagNameKey = NAMEKEY( "ModuleTag_DefectionHelper" ); - static ObjectDefectionHelperModuleData defectionModuleData; - defectionModuleData.setModuleTagNameKey( defectionModuleDataTagNameKey ); - m_defectionHelper = newInstance(ObjectDefectionHelper)(this, &defectionModuleData); - *curB++ = m_defectionHelper; - } - - if (tt->canPossiblyHaveAnyWeapon()) - { - // we only need a firingtracker and wshelper if we can possibly have a weapon. - static const NameKeyType weaponStatusModuleDataTagNameKey = NAMEKEY( "ModuleTag_WeaponStatusHelper" ); - static ObjectWeaponStatusHelperModuleData weaponStatusModuleData; - weaponStatusModuleData.setModuleTagNameKey( weaponStatusModuleDataTagNameKey ); - m_wsHelper = newInstance(ObjectWeaponStatusHelper)(this, &weaponStatusModuleData); - *curB++ = m_wsHelper; - - static const NameKeyType firingTrackerModuleDataTagNameKey = NAMEKEY( "ModuleTag_FiringTrackerHelper" ); - static FiringTrackerModuleData firingTrackerModuleData; - firingTrackerModuleData.setModuleTagNameKey( firingTrackerModuleDataTagNameKey ); - m_firingTracker = newInstance(FiringTracker)(this, &firingTrackerModuleData); - *curB++ = m_firingTracker; - - static const NameKeyType tempWeaponBonusHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_TempWeaponBonusHelper" ); - static TempWeaponBonusHelperModuleData tempWeaponBonusModuleData; - tempWeaponBonusModuleData.setModuleTagNameKey( tempWeaponBonusHelperModuleDataTagNameKey ); - m_tempWeaponBonusHelper = newInstance(TempWeaponBonusHelper)(this, &tempWeaponBonusModuleData); - *curB++ = m_tempWeaponBonusHelper; - } - - // behaviors are always done first, so they get into the publicModule arrays - // before anything else. - for (modIdx = 0; modIdx < mi.getCount(); ++modIdx) - { - modName = mi.getNthName(modIdx); - if (modName.isEmpty()) - continue; - - BehaviorModule* newMod = (BehaviorModule*)TheModuleFactory->newModule(this, modName, mi.getNthData(modIdx), MODULETYPE_BEHAVIOR); - *curB++ = newMod; - - BodyModuleInterface* body = newMod->getBody(); - if (body) - { - DEBUG_ASSERTCRASH(m_body == NULL, ("Duplicate bodies")); - m_body = body; - } - - ContainModuleInterface* contain = newMod->getContain(); - if (contain) - { - DEBUG_ASSERTCRASH(m_contain == NULL, ("Duplicate containers")); - m_contain = contain; - } - - StealthUpdate* stealth = (StealthUpdate*)newMod->getStealth(); - if ( stealth ) - { - DEBUG_ASSERTCRASH( m_stealth == NULL, ("DuplicateStealthUpdates!") ); - m_stealth = stealth; - } - - - AIUpdateInterface* ai = newMod->getAIUpdateInterface(); - if (ai) - { - if( m_ai ) - { - DEBUG_ASSERTCRASH( m_ai == NULL, ("%s has more than one AI module. This is illegal!\n", getTemplate()->getName().str()) ); - } - m_ai = ai; - } - - static NameKeyType key_PhysicsUpdate = NAMEKEY("PhysicsBehavior"); - if (newMod->getModuleNameKey() == key_PhysicsUpdate) - { - DEBUG_ASSERTCRASH(m_physics == NULL, ("You should never have more than one Physics module (%s)\n",getTemplate()->getName().str())); - m_physics = (PhysicsBehavior*)newMod; - } - } - - *curB = NULL; - - AIUpdateInterface *ai = getAIUpdateInterface(); - if (ai) { - ai->setAttitude(getTeam()->getPrototype()->getTemplateInfo()->m_initialTeamAttitude); - if (m_team && m_team->getPrototype() && m_team->getPrototype()->getAttackPriorityName().isNotEmpty()) { - AsciiString name = m_team->getPrototype()->getAttackPriorityName(); - const AttackPriorityInfo *info = TheScriptEngine->getAttackInfo(name); - if (info && info->getName().isNotEmpty()) { - ai->setAttackInfo(info); - } - } - } - - // allocate experience tracker - m_experienceTracker = newInstance(ExperienceTracker)(this); - - // If a valid team has been assigned me, then I have a Player I can ask about my starting level - const Player* controller = getControllingPlayer(); - m_experienceTracker->setVeterancyLevel( controller->getProductionVeterancyLevel( getTemplate()->getName() ) ); - - /// allow for inter-Module resolution - for (BehaviorModule** b = m_behaviors; *b; ++b) - { - (*b)->onObjectCreated(); - } - - m_numTriggerAreasActive = 0; - m_enteredOrExitedFrame = 0; - m_isSelectable = tt->isKindOf(KINDOF_SELECTABLE); - - m_healthBoxOffset.zero();// this is used for units that are amorphous, like angry mob - - //Modules have now been completely created! - m_modulesReady = true; - - TheRadar->addObject( this ); - - // register the object with the GameLogic - TheGameLogic->registerObject( this ); - - //disable occlusion for some time after object is created to allow them to exit the factory/building. - m_safeOcclusionFrame = TheGameLogic->getFrame()+tt->getOcclusionDelay(); - - - m_soleHealingBenefactorID = INVALID_ID; ///< who is the only other object that can give me this non-stacking heal benefit? - m_soleHealingBenefactorExpirationFrame = 0; ///< on what frame can I accept healing (thus to switch) from a new benefactor - - - -} // end Object - -//------------------------------------------------------------------------------------------------- -/** Emit message announcing object's creation - * Note: Have to do this in virtual init() method because virtual methods - * don't become virtual until AFTER the constructor has completed, and we - * need to send our type in this message via virtual getType(). */ -//------------------------------------------------------------------------------------------------- -void Object::initObject() -{ - // Weapons & Damage ------------------------------------------------------------------------------------------------- - // Force the initial weapon set to be instantiated & reloaded. - - //GS No Bad Wrong - // The flags are constructed to empty, and between then and now they may be set in valid ways by onCreate modules. - // We don't want to blow that away. updateWeaponSet is safe to call on its own, so I will move that to the end. -// m_curWeaponSetFlags.clear(); -// m_weaponSet.updateWeaponSet(this); -// m_weaponBonusCondition = 0; - - for (int i = 0; i < WEAPONSLOT_COUNT; ++i) - m_lastWeaponCondition[i] = WSF_INVALID; - - // emit message announcing object's creation - TheGameLogic->sendObjectCreated( this ); - - // If I have a valid team assigned, I can run through my Upgrade modules with his flags - updateUpgradeModules(); - - //If the player has battle plans (America Strategy Center), then apply those bonuses - //to this object if applicable. Internally it validates certain kinds of objects. - const Player* controller = getControllingPlayer(); - if (controller) - { - if (!getReceivingDifficultyBonus() && TheScriptEngine->getObjectsShouldReceiveDifficultyBonus()) - { - setReceivingDifficultyBonus(TRUE); - } - - if (controller->getNumBattlePlansActive() > 0) - { - controller->applyBattlePlanBonusesForObject( this ); - } - } - - - //For each special power module that we have, add it's type to the specialpower bits. This is - //for optimal access later. - for (BehaviorModule** m = m_behaviors; *m; ++m) - { - SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); - if (!sp) - continue; - - const SpecialPowerTemplate *spTemplate = sp->getSpecialPowerTemplate(); - if( spTemplate ) - { - SET_SPECIALPOWERMASK( m_specialPowerBits, spTemplate->getSpecialPowerType() ); - } - } - - // Kris -- All missiles must be projectiles! This is the perfect place to assert them! - // srj: yes, but only in debug... -#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) - if( !isKindOf( KINDOF_PROJECTILE ) ) - { - if( isKindOf( KINDOF_SMALL_MISSILE ) || isKindOf( KINDOF_BALLISTIC_MISSILE ) ) - { - //Warning only... - DEBUG_CRASH( ("Missile %s must also be a KindOf = PROJECTILE in addition to being either a SMALL_MISSILE or PROJECTILE_MISSILE -- call Kris (36844) for questions!", getTemplate()->getName().str() ) ); - } - } -#endif - if (!isKindOf(KINDOF_PROJECTILE) && !isKindOf(KINDOF_INERT)) { - // Notify script conditions to update conditions that consider unit counts. - // We ignore projectiles cause they are frequently created & destroyed, and are not - // of general interest. Normal unit count tests consider tanks or infantry or planes, etc. jba. - TheScriptEngine->notifyOfObjectCreationOrDestruction(); - TheGameLogic->updateObjectsChangedTriggerAreas(); - } - - // Everything (like weaponSet flags) is inited, so check if the WeaponSet needs to change. - m_weaponSet.updateWeaponSet(this); - - if( isKindOf( KINDOF_MINE ) || isKindOf( KINDOF_BOOBY_TRAP ) || isKindOf( KINDOF_DEMOTRAP ) ) - { - ThePlayerList->getNeutralPlayer()->getAcademyStats()->recordMine(); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Object::~Object() -{ - - // tell the AI the building is gone - /// @todo Generalize the notion of objects entering and leaving the world, so we don't have to special case this - TheAI->pathfinder()->removeObjectFromPathfindMap( this ); - - if (!isKindOf(KINDOF_PROJECTILE) && !isKindOf(KINDOF_INERT)) { - // Notify script conditions to update conditions that consider unit counts. - // We ignore projectiles cause they are frequently created & destroyed, and are not - // of general interest. Normal unit count tests consider tanks or infantry or planes, etc. jba. - TheGameLogic->updateObjectsChangedTriggerAreas(); - TheScriptEngine->notifyOfObjectCreationOrDestruction(); - } - - // - // remove from radar before we NULL out the team ... the order of ops are critical here - // because the radar code will sometimes look at the team info and it is assumed through - // the team and player code that the team is valid - // - if( m_radarData ) - TheRadar->removeObject( this ); - - // emit message announcing object's destruction. Again, order is important; we must do this - // before wiping out the team. - TheGameLogic->sendObjectDestroyed( this ); - - // empty the team - setTeam( NULL ); - - // Object's set of these persist for the life of the object. - m_partitionLastLook->deleteInstance(); - m_partitionLastLook = NULL; - m_partitionRevealAllLastLook->deleteInstance(); - m_partitionRevealAllLastLook = NULL; - m_partitionLastShroud->deleteInstance(); - m_partitionLastShroud = NULL; - m_partitionLastThreat->deleteInstance(); - m_partitionLastThreat = NULL; - m_partitionLastValue->deleteInstance(); - m_partitionLastValue = NULL; - - // remove the object from the partition system if present - if( m_partitionData ) - ThePartitionManager->unRegisterObject( this ); - - // if we are in a group, remove us - if (m_group) - m_group->remove( this ); - - // note, do NOT free these, there are just a shadow copy! - m_ai = NULL; - m_physics = NULL; - - // delete any modules present - for (BehaviorModule** b = m_behaviors; *b; ++b) - { - (*b)->deleteInstance(); - *b = NULL; // in case other modules call findModule from their dtor! - } - - delete [] m_behaviors; - m_behaviors = NULL; - - if( m_experienceTracker ) - m_experienceTracker->deleteInstance(); - - m_experienceTracker = NULL; - - // we don't need to delete these, there were deleted on the m_behaviors list - m_firingTracker = NULL; - m_repulsorHelper = NULL; - - m_statusDamageHelper = NULL; - m_tempWeaponBonusHelper = NULL; - m_subdualDamageHelper = NULL; - m_smcHelper = NULL; - m_wsHelper = NULL; - m_defectionHelper = NULL; - - // reset id to zero so we never mistaken grab "dead" objects - m_id = INVALID_ID; - - // Instead of removing it from the named cache, notify the script engine that it has died. - // The script engine will remove it from the cache if necessary. The script engine needs to take - // a crack at this in case it is the current "This Object" pointer. - TheScriptEngine->notifyOfObjectDestruction(this); -} - -//------------------------------------------------------------------------------------------------- -/// this object now contained in "containedBy" -//------------------------------------------------------------------------------------------------- -void Object::onContainedBy( Object *containedBy ) -{ - setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_UNSELECTABLE ) ); - if (containedBy && containedBy->getContain()->isEnclosingContainerFor(this)) - setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) ); - else - clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) ); - m_containedBy = containedBy; - m_containedByFrame = TheGameLogic->getFrame(); - - handlePartitionCellMaintenance(); // which should unlook me now that I am contained - -} - -//------------------------------------------------------------------------------------------------- -/// this object no longer contained in "containedBy" -//------------------------------------------------------------------------------------------------- -void Object::onRemovedFrom( Object *removedFrom ) -{ - clearStatus( MAKE_OBJECT_STATUS_MASK2( OBJECT_STATUS_MASKED, OBJECT_STATUS_UNSELECTABLE ) ); - m_containedBy = NULL; - m_containedByFrame = 0; - - handlePartitionCellMaintenance(); // get a clean look, now that I am outdoors, again - -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Int Object::getTransportSlotCount() const -{ - Int count = getTemplate()->getRawTransportSlotCount(); - ContainModuleInterface* contain = getContain(); - if ( contain && contain->isSpecialZeroSlotContainer() ) - { - count = 0; - const ContainedItemsList* items = contain->getContainedItemsList(); - if (items) - { - for (ContainedItemsList::const_iterator it = items->begin(); it != items->end(); ++it) - { - count += (*it)->getTransportSlotCount(); - } - } - } - return count; -} - -//------------------------------------------------------------------------------------------------- -/** Run from GameLogic::destroyObject */ -//------------------------------------------------------------------------------------------------- -void Object::onDestroy() -{ - - // This is the old cleanUpContain safeguard. Say goodbye so they don't try to look us up. - if( m_containedBy && m_containedBy->getContain() ) - { - m_containedBy->getContain()->removeFromContain( this ); - } - - // - // run the onDelete on all modules present so they each have an opportunity to cleanup - // anything they need to ... including talking to any other modules - // - for (BehaviorModule** b = m_behaviors; *b; ++b) - { - (*b)->onDelete(); - } - - //Have to remove ourself from looking as well. RebuildHoleWorkers definately hit here. - handlePartitionCellMaintenance(); -} // end onDestroy - -//============================================================================= -//============================================================================= -void Object::setGeometryInfo(const GeometryInfo& geom) -{ - m_geometryInfo = geom; - if( m_partitionData ) - { - // if our geometry changes, we unregister and re-register with the partitionmgr - // so that our size gets updated appropriately. this shouldn't be a problem - // unless setGeometryInfo gets called frequently. (srj) - ThePartitionManager->unRegisterObject( this ); - ThePartitionManager->registerObject( this ); - } - - if (m_drawable) - m_drawable->reactToGeometryChange(); -} - -//============================================================================= -//============================================================================= -void Object::setGeometryInfoZ( Real newZ ) -{ - // A Z change only does not need to un/register with the PartitionManager - m_geometryInfo.setMaxHeightAbovePosition( newZ ); - - if (m_drawable) - m_drawable->reactToGeometryChange(); -} - -//============================================================================= -void Object::friend_setUndetectedDefector( Bool status ) -{ - if (status) - m_privateStatus |= UNDETECTED_DEFECTOR; - else - m_privateStatus &= ~UNDETECTED_DEFECTOR; -} - -//============================================================================= -void Object::restoreOriginalTeam() -{ - if( m_team == NULL || m_originalTeamName.isEmpty() ) - return; - - Team* origTeam = TheTeamFactory->findTeam(m_originalTeamName); - if (origTeam == NULL) - { - DEBUG_CRASH(("Object original team (%s) could not be found or created! (srj)\n",m_originalTeamName.str())); - return; - } - - if (m_team == origTeam) - { - DEBUG_CRASH(("Object appears to still be on its original team, so why are we attempting to restore it? (srj)\n")); - return; - } - - setTeam(origTeam); -} - -//============================================================================= -//============================================================================= -void Object::setTeam( Team *team ) -{ - // In order to prevent spawning useful units for a player after he dies, we - // just assign objects to the neutral player if we try to misbehave. - if (team && !team->getControllingPlayer()->isPlayerActive()) - team = ThePlayerList->getNeutralPlayer()->getDefaultTeam(); - - setTemporaryTeam(team); - m_originalTeamName = m_team ? m_team->getName() : AsciiString::TheEmptyString; -} - -//============================================================================= -//============================================================================= -void Object::setTemporaryTeam( Team *team ) -{ - const Bool restoring = false; - setOrRestoreTeam(team, restoring); -} - -//============================================================================= -//============================================================================= -void Object::setOrRestoreTeam( Team* team, Bool restoring ) -{ - // don't do anything if the team hasn't changed - if( m_team == team ) - return; - - Team* oldTeam = m_team; - - // Before Switch ////////////////////////// - if (m_team) - { - if (m_team->isInList_TeamMemberList(this)) - { - m_team->removeFrom_TeamMemberList(this); - m_team->getControllingPlayer()->becomingTeamMember(this, false); - } - } - - // Switch ////////////////////////// - m_team = team; - - // After Switch ////////////////////////// - if (m_team) - { - if (!m_team->isInList_TeamMemberList(this)) - { - m_team->prependTo_TeamMemberList(this); - m_team->getControllingPlayer()->becomingTeamMember(this, true); - } - - // now, adjust the attitude of the unit to its new team. - const TeamPrototype* proto = m_team->getPrototype(); - if (proto && proto->getTemplateInfo()) - { - AIUpdateInterface *ai = getAIUpdateInterface(); - if (ai) - { - ai->setAttitude(proto->getTemplateInfo()->m_initialTeamAttitude); - if (proto->getAttackPriorityName().isNotEmpty()) { - AsciiString name = proto->getAttackPriorityName(); - const AttackPriorityInfo *info = TheScriptEngine->getAttackInfo(name); - if (info && info->getName().isNotEmpty()) { - ai->setAttackInfo(info); - } - } - } - } - // emit message announcing object's new alliance - Drawable *draw = getDrawable(); - if (draw) - draw->changedTeam(); - } - - // This can't just go in ::defect, because some things just do setTeam. The act of - // setting a new team needs to tell the modules and do other important stuff. - // And it needs to happen after the switch. - if( oldTeam && team && !restoring ) - onCapture( oldTeam->getControllingPlayer(), team->getControllingPlayer() ); - - // - // the team changed we have a change in priorities on the radar if we are - // a candidate for the radar as it is - // - if( m_radarData ) - { - - // removing it and adding it will cause a resort to happen - TheRadar->removeObject( this ); - TheRadar->addObject( this ); - } - - // Tell TheInGameUI that the object has changed hands - Int oldPlayerIndex = (oldTeam)?(oldTeam->getControllingPlayer()->getPlayerIndex()):-1; - Int newPlayerIndex = (m_team)?(m_team->getControllingPlayer()->getPlayerIndex()):-1; - if (oldPlayerIndex != newPlayerIndex) - TheInGameUI->objectChangedTeam(this, oldPlayerIndex, newPlayerIndex); -} - -//============================================================================= -enum -{ - BOOBY_TRAP_SCAN_RANGE = 25 -}; -Bool Object::checkAndDetonateBoobyTrap(const Object *victim) -{ - if( !testStatus(OBJECT_STATUS_BOOBY_TRAPPED) ) - return FALSE; - - PartitionFilterAcceptByKindOf kindFilter(MAKE_KINDOF_MASK(KINDOF_BOOBY_TRAP), KINDOFMASK_NONE); - PartitionFilterSameMapStatus filterMapStatus(this); - PartitionFilter *filters[3]; - filters[0] = &kindFilter; - filters[1] = &filterMapStatus; - filters[2] = NULL; - - ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( getPosition(), BOOBY_TRAP_SCAN_RANGE + getGeometryInfo().getBoundingCircleRadius(), - FROM_CENTER_2D, filters, ITER_SORTED_NEAR_TO_FAR ); - MemoryPoolObjectHolder hold(iter);// This is the magic thing that frees the dynamically made iter in its destructor - - Object *ourBoobyTrap = NULL; - for( Object *other = iter->first(); other; other = iter->next() ) - { - if( other->getProducerID() == getID() )// Sticky bombs call the thing they are on their producer for just such an occasion - { - ourBoobyTrap = other; - break; - } - } - - if( ourBoobyTrap ) - { - static NameKeyType key_StickyBombUpdate = NAMEKEY( "StickyBombUpdate" ); - StickyBombUpdate *update = (StickyBombUpdate*)ourBoobyTrap->findUpdateModule( key_StickyBombUpdate ); - if( update ) - { - if( victim && ourBoobyTrap->getControllingPlayer()->getRelationship(victim->getTeam()) == ALLIES ) - return FALSE;// Friends don't touch friends boobies. - - update->detonate(); - return TRUE;// Booby Trapped status will be cleared by stickybomb, as they set it - } - } - - return FALSE; -} - -//============================================================================= -void Object::setStatus( ObjectStatusMaskType objectStatus, Bool set ) -{ - ObjectStatusMaskType oldStatus = m_status; - - if (set) - m_status.set( objectStatus ); - else - m_status.clear( objectStatus ); - - if (m_status != oldStatus) - { - if( set && objectStatus.test( OBJECT_STATUS_REPULSOR ) && m_repulsorHelper != NULL ) - { - // Damaged repulsable civilians scare (repulse) other civs, but only - // for a short amount of time... use the repulsor helper to turn off repulsion shortly. - m_repulsorHelper->sleepUntil(TheGameLogic->getFrame() + 2*LOGICFRAMES_PER_SECOND); - } - - if( objectStatus.test( OBJECT_STATUS_STEALTHED ) || objectStatus.test( OBJECT_STATUS_DETECTED ) || objectStatus.test( OBJECT_STATUS_DISGUISED ) ) - { - //Kris: Aug 20, 2003 - //When any of the three key status bits for stealth go on or off, then handle partition updates for vision. - if( getTemplate()->getShroudRevealToAllRange() > 0.0f ) - { - handlePartitionCellMaintenance(); - } - } - - - // when an object's construction status changes, it needs to have its partition data updated, - // in order to maintain the shroud correctly. - if( m_status.test( OBJECT_STATUS_UNDER_CONSTRUCTION ) != oldStatus.test( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) - { - - // CHECK FOR MINES, AND DETONATE THEM NOW - ObjectIterator *iter = - ThePartitionManager->iteratePotentialCollisions( getPosition(), getGeometryInfo(), getOrientation() ); - MemoryPoolObjectHolder hold( iter ); - Object *them; - for( them = iter->first(); them; them = iter->next() ) - { - if (them->isKindOf( KINDOF_MINE )) - { - //DETONATE ANY ENEMY MINES, OR DELETE FRIENDLY ONES - Relationship r = getRelationship(them); - if (r == ENEMIES) - { - them->kill(); // detonate mine - } - else - { - TheGameLogic->destroyObject(them); - } - } - }// next object - - if (m_partitionData) - m_partitionData->makeDirty(true); - } - - } - -} - -//============================================================================= -void Object::setScriptStatus( ObjectScriptStatusBit bit, Bool set ) -{ - UnsignedInt oldScriptStatus = m_scriptStatus; - - if( set ) - { - m_scriptStatus |= bit; - } - else - { - m_scriptStatus &= ~bit; - } - - if( m_scriptStatus != oldScriptStatus ) - { - if( (m_scriptStatus & OBJECT_STATUS_SCRIPT_DISABLED) != (oldScriptStatus & OBJECT_STATUS_SCRIPT_DISABLED) ) - { - if( m_partitionData ) - { - // if an object becomes disabled or unpowered, then you have to update its partition data because it will - // change how far it can see. - m_partitionData->makeDirty(true); - } - if( m_scriptStatus & OBJECT_STATUS_SCRIPT_DISABLED ) - { - //I am now disabled, so tell the main game engine! - setDisabled( DISABLED_SCRIPT_DISABLED ); - } - else - { - //I am no longer disabled, so tell the main game engine! - clearDisabled( DISABLED_SCRIPT_DISABLED ); - } - } - if( (m_scriptStatus & OBJECT_STATUS_SCRIPT_UNPOWERED) != (oldScriptStatus & OBJECT_STATUS_SCRIPT_UNPOWERED) ) - { - if( m_partitionData ) - { - // if an object becomes disabled or unpowered, then you have to update its partition data because it will - // change how far it can see. - m_partitionData->makeDirty(true); - } - if( m_scriptStatus & OBJECT_STATUS_SCRIPT_UNPOWERED ) - { - //I am now underpowered, so tell the main game engine! - setDisabled( DISABLED_SCRIPT_UNDERPOWERED ); - } - else - { - //I am no longer undperpowered, so tell the main game engine! - clearDisabled( DISABLED_SCRIPT_UNDERPOWERED ); - } - } - } -} - -//============================================================================= -Bool Object::canCrushOrSquish(Object *otherObj, CrushSquishTestType testType ) const -{ - DEBUG_ASSERTCRASH(this, ("null this in canCrushOrSquish")); - - if( !otherObj ) - { - //Can't crush anything. - return false; - } - - if( isDisabledByType( DISABLED_UNMANNED ) ) - { - //Unmanned vehicles cannot crush troops. This was happening when Jarmen Kell sniped - //the vehicle and booted the guys out while still moving, as the vehicle is now - //on a different team. - return false; - } - - UnsignedByte crusherLevel = getCrusherLevel(); - - // order matters: we want to know if I consider it to be an ally, not vice versa - if( getRelationship( otherObj ) == ALLIES ) - { - //Friends don't let friends crush friends. - return false; - } - - if( !crusherLevel ) - { - //Can't crush anything! - return false; - } - - //Test this case for generic infantry getting squished by vehicles! - if( testType == TEST_SQUISH_ONLY || testType == TEST_CRUSH_OR_SQUISH ) - { - - //**************************************************************************************** - //NOTE: This section of code is used by the pathfinder to determine if the object should - // move to the target. I don't think it's the right place to check for this because - // the semantics check to see if we can squish something -- not approach it. However - // I'm not moving it for fear of some major breakage! -- KM - //Bool squisher = crusherLevel > 0; - //if( !squisher ) - //{ - // Weapon *weapon = getCurrentWeapon(); - // if( weapon && weapon->isContactWeapon() ) - // { - // squisher = true; - // } - //} - //if( squisher ) - //NOTE2: *** IF YOU REENABLE THIS CODE -- Move the "if( !crusherLevel ) return false" below - // this squish section. - //**************************************************************************************** - { - // See if other is squishable - static NameKeyType key_squish = NAMEKEY( "SquishCollide" ); - if( otherObj->findModule( key_squish ) ) - { - return true; // squishable. - } - } - } - - - UnsignedByte crushableLevel = otherObj->getCrushableLevel(); - - if( testType == TEST_CRUSH_ONLY || testType == TEST_CRUSH_OR_SQUISH ) - { - if( crusherLevel > crushableLevel ) - { - return true; - } - } - - return false; -} - -//------------------------------------------------------------------------------------------------- -UnsignedByte Object::getCrusherLevel() const -{ - return getTemplate()->getCrusherLevel(); -} - -//------------------------------------------------------------------------------------------------- -UnsignedByte Object::getCrushableLevel() const -{ - return getTemplate()->getCrushableLevel(); -} - - -// ------------------------------------------------------------------------------------------------ -/** Topple an object, if possible */ -// ------------------------------------------------------------------------------------------------ -void Object::topple( const Coord3D *toppleDirection, Real toppleSpeed, UnsignedInt options ) -{ - static NameKeyType key_ToppleUpdate = NAMEKEY("ToppleUpdate"); - - ToppleUpdate* toppleUpdate = (ToppleUpdate*)findModule(key_ToppleUpdate); - if( toppleUpdate && toppleUpdate->isAbleToBeToppled() ) - { - - // apply the topple force - toppleUpdate->applyTopplingForce( toppleDirection, toppleSpeed, options ); - - } // end if - -} // end topple - -//============================================================================= -void Object::setArmorSetFlag(ArmorSetType ast) -{ - m_body->setArmorSetFlag(ast); -} - -//============================================================================= -void Object::clearArmorSetFlag(ArmorSetType ast) -{ - m_body->clearArmorSetFlag(ast); -} - -//============================================================================= -Bool Object::testArmorSetFlag(ArmorSetType ast) const -{ - return m_body->testArmorSetFlag(ast); -} - -//============================================================================= -void Object::reloadAllAmmo(Bool now) -{ - m_weaponSet.reloadAllAmmo(this, now); -} - -//============================================================================= -Bool Object::isOutOfAmmo() const -{ - return m_weaponSet.isOutOfAmmo(); -} - -//============================================================================= -Bool Object::hasAnyWeapon() const -{ - return m_weaponSet.hasAnyWeapon(); -} - -//============================================================================= -Bool Object::hasAnyDamageWeapon() const -{ - //First check to see if we have any weapons -- if not return false. - if( !m_weaponSet.hasAnyDamageWeapon() ) - { - return FALSE; - } - return TRUE; -} - -//============================================================================= -UnsignedInt Object::getMostPercentReadyToFireAnyWeapon() const -{ - return m_weaponSet.getMostPercentReadyToFireAnyWeapon(); -} - -//============================================================================= -Bool Object::getWeaponInWeaponSlotSyncedToSlot(WeaponSlotType thisSlot, WeaponSlotType otherSlot) const -{ - CommandSourceMask mask = getWeaponInWeaponSlotCommandSourceMask(thisSlot); - - //Bool value0a = mask & (1 << CMD_SYNC_TO_PRIMARY); - //Bool value0b = (otherSlot == PRIMARY_WEAPON); - //Bool value1a = mask & (1 << CMD_SYNC_TO_SECONDARY); - //Bool value1b = (otherSlot == SECONDARY_WEAPON); - //Bool value2a = mask & (1 << CMD_SYNC_TO_TERTIARY); - //Bool value2b = (otherSlot == TERTIARY_WEAPON); - - //DEBUG_LOG(("- getWeaponInWeaponSlotSyncedToSlot (thisSlot=%d, otherSlot=%d): mask = %d --> value0 = %d/%d, value1 = %d/%d, value2 = %d/%d.\n", - // thisSlot, otherSlot, static_cast(mask), value0a, value0b, value1a, value1b, value2a, value2b)); - - return ((Int)mask >= 0) && - ((mask & (1 << CMD_SYNC_TO_PRIMARY) && otherSlot == PRIMARY_WEAPON) || - (mask & (1 << CMD_SYNC_TO_SECONDARY) && otherSlot == SECONDARY_WEAPON) || - (mask & (1 << CMD_SYNC_TO_TERTIARY) && otherSlot == TERTIARY_WEAPON) || - (mask & (1 << CMD_SYNC_TO_FOUR) && otherSlot == WEAPON_FOUR) || - (mask & (1 << CMD_SYNC_TO_FIVE) && otherSlot == WEAPON_FIVE) || - (mask & (1 << CMD_SYNC_TO_SIX) && otherSlot == WEAPON_SIX) || - (mask & (1 << CMD_SYNC_TO_SEVEN) && otherSlot == WEAPON_SEVEN) || - (mask & (1 << CMD_SYNC_TO_EIGHT) && otherSlot == WEAPON_EIGHT)); - -} - -//============================================================================= -Bool Object::hasWeaponToDealDamageType(DamageType typeToDeal) const -{ - return m_weaponSet.hasWeaponToDealDamageType(typeToDeal); -} - -//============================================================================= -Real Object::getLargestWeaponRange() const -{ - Real retVal = -1; - for (Int i = PRIMARY_WEAPON; i < WEAPONSLOT_COUNT; ++i) { - Weapon* weapon = m_weaponSet.getWeaponInWeaponSlot((WeaponSlotType)i); - if (!weapon) { - continue; - } - - Real tmpVal = weapon->getAttackRange(this); - if (tmpVal > retVal) { - retVal = tmpVal; - } - } - return retVal; -} - -//============================================================================= -void Object::setFiringConditionForCurrentWeapon() const -{ - if (m_drawable) - { - WeaponSlotType wslot = m_weaponSet.getCurWeaponSlot(); - ModelConditionFlags c = m_weaponSet.getModelConditionForWeaponSlot(wslot, WSF_FIRING); - m_drawable->clearAndSetModelConditionFlags(s_allWeaponFireFlags[wslot], c); - } -} - -//============================================================================= -void Object::setModelConditionState( ModelConditionFlagType a ) -{ - if (m_drawable) - { - m_drawable->setModelConditionState(a); - } -} - -//============================================================================= -void Object::clearModelConditionState( ModelConditionFlagType a ) -{ - if (m_drawable) - { - m_drawable->clearModelConditionState(a); - } -} - -//============================================================================= -void Object::clearAndSetModelConditionState( ModelConditionFlagType clr, ModelConditionFlagType set ) -{ - if (m_drawable) - { - m_drawable->clearAndSetModelConditionState(clr, set); - } -} - -//============================================================================= -void Object::clearModelConditionFlags( const ModelConditionFlags& clr ) -{ - if (m_drawable) - { - m_drawable->clearModelConditionFlags(clr); - } -} - -//============================================================================= -void Object::setModelConditionFlags( const ModelConditionFlags& set ) -{ - if (m_drawable) - { - m_drawable->setModelConditionFlags(set); - } -} - -//============================================================================= -void Object::clearAndSetModelConditionFlags( const ModelConditionFlags& clr, const ModelConditionFlags& set ) -{ - if (m_drawable) - { - m_drawable->clearAndSetModelConditionFlags(clr, set); - } -} - -//============================================================================= -// Special model states are states that are turned on for a period of time, and -// turned off automatically -- used for cheer, and scripted special moment -// animations. Setting a special state will automatically clear any other -// special states that may be turned on so you can only have one at a time. -//============================================================================= -void Object::setSpecialModelConditionState( ModelConditionFlagType set, UnsignedInt frames ) -{ - clearSpecialModelConditionStates(); - - setModelConditionState( set ); - - if( frames == 0 ) - { - frames = 1; - } - - m_smcUntil = TheGameLogic->getFrame() + frames; - m_smcHelper->sleepUntil(m_smcUntil); -} - -//============================================================================= -void Object::clearSpecialModelConditionStates() -{ - clearModelConditionFlags( MAKE_MODELCONDITION_MASK( MODELCONDITION_SPECIAL_CHEERING ) ); - m_smcUntil = NEVER; -} - -// Lorenzen has some interest in this, ask before deleting -//============================================================================= -//const ModelConditionFlags& Object::getModelConditionFlags() const -//{ -// if (m_drawable) -// { -// return m_drawable->getModelConditionFlags(); -// } -// else -// { -// DEBUG_CRASH(("NULL Drawable at this point, you can't get modelconditionflags now.")); -// static ModelConditionFlags noFlags; -// return noFlags; -// } -//} - -//============================================================================= -Weapon* Object::getCurrentWeapon(WeaponSlotType* wslot) -{ - if (!m_weaponSet.hasAnyWeapon()) - return NULL; - - if (wslot) - *wslot = m_weaponSet.getCurWeaponSlot(); - return m_weaponSet.getCurWeapon(); -} - -//============================================================================= -const Weapon* Object::getCurrentWeapon(WeaponSlotType* wslot) const -{ - if (!m_weaponSet.hasAnyWeapon()) - return NULL; - - if (wslot) - *wslot = m_weaponSet.getCurWeaponSlot(); - return m_weaponSet.getCurWeapon(); -} - -//============================================================================= -Weapon* Object::findWaypointFollowingCapableWeapon() -{ - return m_weaponSet.findWaypointFollowingCapableWeapon(); -} - -//============================================================================= -Bool Object::getAmmoPipShowingInfo(Int& numTotal, Int& numFull) const -{ -/// @todo srj -- may need to cache this inside weaponset. - const Weapon* w = m_weaponSet.findAmmoPipShowingWeapon(); - if (w) - { - numTotal = w->getClipSize(); - numFull = w->getRemainingAmmo(); - return true; - } - else - { - return false; - } -} - -//============================================================================= -/* - NOTE: getAbleToAttackSpecificObject NO LONGER internally calls isAbleToAttack(), - since that isn't an incredibly fast call, and this is called repeatedly in some inner loops - where we already know that isAbleToAttack() == true. so you should always - call isAbleToAttack prior to calling this! (srj) -*/ -CanAttackResult Object::getAbleToAttackSpecificObject( AbleToAttackType t, const Object* target, CommandSourceType commandSource, WeaponSlotType specificSlot ) const -{ - // NO! BAD! WRONG! - // If we can't attack at all, then we cannot attack this - //if (!isAbleToAttack()) - // return FALSE; - - // Otherwise leave it up to our weapons. - return m_weaponSet.getAbleToAttackSpecificObject( t, this, target, commandSource, specificSlot ); -} - -//============================================================================= -//Used for base defenses and otherwise stationary units to see if you can attack a position potentially out of range. -CanAttackResult Object::getAbleToUseWeaponAgainstTarget( AbleToAttackType attackType, const Object *victim, const Coord3D *pos, CommandSourceType commandSource, WeaponSlotType specificSlot ) const -{ - return m_weaponSet.getAbleToUseWeaponAgainstTarget( attackType, this, victim, pos, commandSource, specificSlot ); -} - - -//============================================================================= -Bool Object::chooseBestWeaponForTarget(const Object* target, WeaponChoiceCriteria criteria, CommandSourceType cmdSource ) -{ - return m_weaponSet.chooseBestWeaponForTarget(this, target, criteria, cmdSource ); -} - -//DECLARE_PERF_TIMER(fireCurrentWeapon) -//============================================================================= -void Object::fireCurrentWeapon(Object *target) -{ - //USE_PERF_TIMER(fireCurrentWeapon) - - // victim may have already been destroyed - if (target == NULL) - return; - - Weapon* weapon = m_weaponSet.getCurWeapon(); - if (weapon && (weapon->getStatus() == READY_TO_FIRE)) - { - Bool reloaded = weapon->fireWeapon(this, target); - DEBUG_ASSERTCRASH(m_firingTracker, ("hey, we are firing but have no firing tracker. this is wrong.")); - if (m_firingTracker) - m_firingTracker->shotFired(weapon, target->getID()); - if (reloaded) - releaseWeaponLock(LOCKED_TEMPORARILY); // release any temporary locks. - - friend_setUndetectedDefector( FALSE );// My secret is out - } -} - -//============================================================================= -void Object::fireCurrentWeapon(const Coord3D* pos) -{ - //USE_PERF_TIMER(fireCurrentWeapon) - - if (pos == NULL) - return; - - Weapon* weapon = m_weaponSet.getCurWeapon(); - if (weapon && (weapon->getStatus() == READY_TO_FIRE)) - { - Bool reloaded = weapon->fireWeapon(this, pos); - DEBUG_ASSERTCRASH(m_firingTracker, ("hey, we are firing but have no firing tracker. this is wrong.")); - if (m_firingTracker) - m_firingTracker->shotFired(weapon, INVALID_ID); - if (reloaded) - releaseWeaponLock(LOCKED_TEMPORARILY); // release any temporary locks. - - friend_setUndetectedDefector( FALSE );// My secret is out - } -} - -//============================================================================== -void Object::notifyFiringTrackerShotFired( const Weapon* weaponFired, ObjectID victimID ) -{ - if ( m_firingTracker ) - m_firingTracker->shotFired( weaponFired, victimID ); -} - - -//============================================================================= -void Object::preFireCurrentWeapon( const Object *victim ) -{ - Weapon* weapon = m_weaponSet.getCurWeapon(); - - //If we are going to be capable of firing our weapon NEXT frame, set the pre-attack - //up now. This gets called by AIAttackFireWeaponState::onEnter().. but the update happens - //next frame. - if (weapon && TheGameLogic->getFrame() + 1 >= weapon->getPossibleNextShotFrame() ) - { - weapon->preFireWeapon( this, victim ); - friend_setUndetectedDefector( FALSE );// My secret is out - } -} - -//============================================================================= -void Object::preFireCurrentWeapon(const Coord3D* pos) -{ - Weapon* weapon = m_weaponSet.getCurWeapon(); - - //If we are going to be capable of firing our weapon NEXT frame, set the pre-attack - //up now. This gets called by AIAttackFireWeaponState::onEnter().. but the update happens - //next frame. - if (weapon && TheGameLogic->getFrame() + 1 >= weapon->getPossibleNextShotFrame()) - { - weapon->preFireWeapon(this, pos); - friend_setUndetectedDefector(FALSE);// My secret is out - } -} - -// ============================================================================ -/** Using the firing tracker, return the frame a shot was last fired on */ -// ============================================================================ -UnsignedInt Object::getLastShotFiredFrame() const -{ - UnsignedInt recent = 0; - for (int i = 0; i < WEAPONSLOT_COUNT; ++i) - { - const Weapon* w = m_weaponSet.getWeaponInWeaponSlot((WeaponSlotType)i); - if (w) - { - UnsignedInt when = w->getLastShotFrame(); - if (when > recent) - recent = when; - } - } - return recent; -} - -// ============================================================================ -/** Get the victim ID we last shot at */ -// ============================================================================ -ObjectID Object::getLastVictimID() const -{ - return m_firingTracker ? m_firingTracker->getLastShotVictim() : INVALID_ID; -} - -//============================================================================= -// Object::getRelationship -//============================================================================= -Relationship Object::getRelationship(const Object *that) const -{ - const Team *myTeam = getTeam(); - - if (myTeam && that) - { - if (getIsUndetectedDefector()) - { - return NEUTRAL; // so my AI does not give away my position by auto acquire - } - else if (that->getIsUndetectedDefector()) - { - return ALLIES; // so I treat undetecteddefectors like they were my very own - } - else - { - return myTeam->getRelationship( that->getTeam() ); - } - } - - return NEUTRAL; - -} - -//============================================================================= -// Object::getControllingPlayer -//============================================================================= -Player * Object::getControllingPlayer() const -{ - const Team* myTeam = this->getTeam(); - if (myTeam) - return myTeam->getControllingPlayer(); - - return NULL; -} - -//============================================================================= -void Object::setProducer(const Object* obj) -{ - m_producerID = obj ? obj->getID() : INVALID_ID; -// seems like a good idea, but is not. (srj) -// if (obj) -// m_indicatorColor = obj->m_indicatorColor; -} - -//============================================================================= -void Object::setBuilder( const Object *obj ) -{ - - m_builderID = obj ? obj->getID() : INVALID_ID; - -} - -//============================================================================= -void Object::setCustomIndicatorColor(Color c) -{ - if (m_indicatorColor != c) - { - m_indicatorColor = c; - if (m_drawable) - m_drawable->changedTeam(); - } -} - -//============================================================================= -void Object::removeCustomIndicatorColor() -{ - setCustomIndicatorColor(0); -} - -//============================================================================= -// Object::getIndicatorColor -//============================================================================= -Color Object::getIndicatorColor() const -{ - if (m_indicatorColor == 0) - { - const Team *myTeam = getTeam(); - if (myTeam) - { - const Player* p = myTeam->getControllingPlayer(); - if (p) - { - return p->getPlayerColor(); - } - } - return GameMakeColor(0, 0, 0, 255); - } - else - { - return m_indicatorColor; - } -} - -//============================================================================= -// Object::getNightIndicatorColor - used to make blue/purple easier to see on night models. -//============================================================================= -Color Object::getNightIndicatorColor() const -{ - if (m_indicatorColor == 0) - { - const Team *myTeam = getTeam(); - if (myTeam) - { - const Player* p = myTeam->getControllingPlayer(); - if (p) - { - return p->getPlayerNightColor(); - } - } - return GameMakeColor(0, 0, 0, 255); - } - else - { - return m_indicatorColor; - } -} - -//============================================================================= -// Object::isLocallyControlled -//============================================================================= -Bool Object::isLocallyControlled() const -{ - return getControllingPlayer() == ThePlayerList->getLocalPlayer(); -} - -//============================================================================= -// Object::isLocallyControlled -//============================================================================= -Bool Object::isNeutralControlled() const -{ - return getControllingPlayer() == ThePlayerList->getNeutralPlayer(); -} - -//------------------------------------------------------------------------------------------------- -inline Bool isPosDifferent(const Coord3D* a, const Coord3D* b) -{ - // this is necessary because PhysicsBehavior may generate tiny changes even when - // "standing still", due to roundoff errors. It's important that we only invalidate - // the PartitionManager stuff when the pos/orientation really changes (for efficiency purposes) - // so we must put in some cleverness... - const Real THRESH = 0.01f; - - if (fabs(a->x - b->x) > THRESH) - return true; - - if (fabs(a->y - b->y) > THRESH) - return true; - - if (fabs(a->z - b->z) > THRESH) - return true; - - return false; -} - -//------------------------------------------------------------------------------------------------- -inline Bool isAngleDifferent(Real a, Real b) -{ - // this is necessary because PhysicsBehavior may generate tiny changes even when - // "standing still", due to roundoff errors. It's important that we only invalidate - // the PartitionManager stuff when the pos/orientation really changes (for efficiency purposes) - // so we must put in some cleverness... - - const Real THRESH = 0.01f; // in radians, this is approx 1/2 degree. - - if (fabs(a - b) > THRESH) - return true; - - return false; -} - -//------------------------------------------------------------------------------------------------- -void Object::reactToTurretChange( WhichTurretType turret, Real oldRotation, Real oldPitch ) -{ - Real currentRotation = 0.0f; - Real currentPitch = 0.0f; - if( getAI() ) - { - getAI()->getTurretRotAndPitch( turret, ¤tRotation, ¤tPitch ); - } - Bool rotationChange = (currentRotation != oldRotation); -// Bool pitchChange = (currentPitch != oldPitch); - - if( rotationChange ) - { - if (getContain()) - getContain()->containReactToTransformChange(); - } -} - -//------------------------------------------------------------------------------------------------- -//DECLARE_PERF_TIMER(Object_reactToTransformChange) -void Object::reactToTransformChange(const Matrix3D* oldMtx, const Coord3D* oldPos, Real oldAngle) -{ - //USE_PERF_TIMER(Object_reactToTransformChange) - if(_isnan(getPosition()->x) || _isnan(getPosition()->y) || _isnan(getPosition()->z)) { - DEBUG_CRASH(("Object pos is nan.")); - TheGameLogic->destroyObject(this); - } - if (m_drawable) - { - m_drawable->setTransformMatrix( this->getTransformMatrix() ); - } - - Bool posDiff = isPosDifferent(oldPos, getPosition()); - Bool angDiff = isAngleDifferent(oldAngle, getOrientation()); - - if (posDiff || angDiff) - { - if (m_partitionData) - m_partitionData->makeDirty(true); - - if (getContain()) - getContain()->containReactToTransformChange(); - } - - if (posDiff) - { - setTriggerAreaFlagsForChangeInPosition(); // Update for entered/exited - - Region3D mapExtent; - TheTerrainLogic->getExtent(&mapExtent); - if (mapExtent.isInRegionNoZ(getPosition())) - m_privateStatus &= ~OFF_MAP; - else - m_privateStatus |= OFF_MAP; - } -} - -//------------------------------------------------------------------------------------------------- -ObjectShroudStatus Object::getShroudedStatus(Int playerIndex) const -{ - if (getTemplate()->isKindOf( KINDOF_ALWAYS_VISIBLE )) - return OBJECTSHROUD_CLEAR; - - if (m_partitionData) - return m_partitionData->getShroudedStatus(playerIndex); - - // This can happen for objects removed from the partition system (e.g., - // for soldiers that are garrisoned inside a building). - return OBJECTSHROUD_CLEAR; -} - -//------------------------------------------------------------------------------------------------- -/** Something is attempting to damage this object */ -//------------------------------------------------------------------------------------------------- -void Object::attemptDamage( DamageInfo *damageInfo ) -{ - BodyModuleInterface* body = getBodyModule(); - if (body) - body->attemptDamage( damageInfo ); - - // Process any shockwave forces that might affect this object due to the incurred damage - if (damageInfo->in.m_shockWaveAmount > 0.0f && damageInfo->in.m_shockWaveRadius > 0.0f) - { - //KindOfMaskType immuneToShockwaveKindofs; //NEW RESTRICTIONS ADDED - //immuneToShockwaveKindofs.set(KINDOF_PROJECTILE);// projectiles go idle in midair when they get sw'd //NEW RESTRICTIONS ADDED - //immuneToShockwaveKindofs.set(KINDOF_PRODUCED_AT_HELIPAD);//helicopters go all wonky when they get shockwaved //NEW RESTRICTIONS ADDED - - PhysicsBehavior *behavior = getPhysics(); - if ( behavior && (isAirborneTarget() == FALSE) && (! isKindOf(KINDOF_PROJECTILE) ) ) -// if (behavior && isAnyKindOf( immuneToShockwaveKindofs ) == FALSE )//NEW RESTRICTIONS ADDED - { - // Calculate the shockwave taperoff amount due to distance from ground zero - Real shockWaveScalar = damageInfo->in.m_shockWaveVector.length(); - Real distanceFromCenter = min(1.0f, shockWaveScalar / damageInfo->in.m_shockWaveRadius); - Real distanceTaper = (distanceFromCenter) * (1.0f - damageInfo->in.m_shockWaveTaperOff); - Real shockTaperMult = 1.0f - distanceTaper; - - // Set up the shockwave force to use apply on object - Coord3D shockWaveForce; - shockWaveForce.set( &damageInfo->in.m_shockWaveVector ); - shockWaveForce.normalize(); - shockWaveForce.scale( damageInfo->in.m_shockWaveAmount * shockTaperMult ); - shockWaveForce.z = shockWaveForce.length(); // Apply up force equal to the lateral force for dramatic effect - - // Apply the shock to the object - behavior->applyShock(&shockWaveForce); - - // Add random rotation to the object for drama - - behavior->applyRandomRotation(); - - // Set stunned state due to the shock for the object - behavior->setStunned(true); - - setModelConditionState(MODELCONDITION_STUNNED_FLAILING); - } - } - - - /// @todo track damage dealt/attempted - - // - // if actual damage occurred, and this is an object owned by the local player we - // might do a radar event for under attack. Note that we do not even try - // to do radar events for DAMAGE_PENALTY as that damage type is a type of damage - // that occurs with explicit player knowledge - // - if( damageInfo->out.m_actualDamageDealt > 0.0f && - damageInfo->in.m_damageType != DAMAGE_PENALTY && - damageInfo->in.m_damageType != DAMAGE_HEALING && - getControllingPlayer() && - !BitIsSet(damageInfo->in.m_sourcePlayerMask, getControllingPlayer()->getPlayerMask()) && - m_radarData != NULL && - getControllingPlayer() == ThePlayerList->getLocalPlayer() ) - TheRadar->tryUnderAttackEvent( this ); - -} - -//------------------------------------------------------------------------------------------------- -void Object::attemptHealing(Real amount, const Object* source) -{ - BodyModuleInterface* body = getBodyModule(); - if (body) - { - DamageInfo damageInfo; - damageInfo.in.m_damageType = DAMAGE_HEALING; - damageInfo.in.m_deathType = DEATH_NONE; - damageInfo.in.m_sourceID = source ? source->getID() : INVALID_ID; - damageInfo.in.m_amount = amount; - body->attemptHealing( &damageInfo ); - } -} - -ObjectID Object::getSoleHealingBenefactor( void ) const -{ - UnsignedInt now = TheGameLogic->getFrame(); - if( now > m_soleHealingBenefactorExpirationFrame ) - return INVALID_ID; - - return m_soleHealingBenefactorID; - -} - -Bool Object::attemptHealingFromSoleBenefactor ( Real amount, const Object* source, UnsignedInt duration ) -{///< for the non-stacking healers like ambulance and propaganda - - if( ! source ) // sanity - return FALSE; - - UnsignedInt now = TheGameLogic->getFrame(); - ObjectID id = source->getID(); - -// Either it is ok to accept healing from any who offer or this is my guy, calling again - if( now > m_soleHealingBenefactorExpirationFrame || m_soleHealingBenefactorID == id ) - { - m_soleHealingBenefactorID = id; - m_soleHealingBenefactorExpirationFrame = now + duration; - - BodyModuleInterface* body = getBodyModule(); - if (body) - { - DamageInfo damageInfo; - damageInfo.in.m_damageType = DAMAGE_HEALING; - damageInfo.in.m_deathType = DEATH_NONE; - damageInfo.in.m_sourceID = source ? source->getID() : INVALID_ID; - damageInfo.in.m_amount = amount; - body->attemptHealing( &damageInfo ); - } - - return TRUE; - } - - return FALSE; - -} - - -//------------------------------------------------------------------------------------------------- -Real Object::estimateDamage( DamageInfoInput& damageInfo ) const -{ - BodyModuleInterface* body = getBodyModule(); - if (body) - return body->estimateDamage( damageInfo ); - - return 0.0f; -} - -//------------------------------------------------------------------------------------------------- -/** Do so much damage to an object that it will certainly die */ -//------------------------------------------------------------------------------------------------- -void Object::kill( DamageType damageType, DeathType deathType ) -{ - DamageInfo damageInfo; - - // Do unmodifiable damage equal to their max health to kill. - damageInfo.in.m_damageType = damageType; - damageInfo.in.m_deathType = deathType; - damageInfo.in.m_sourceID = INVALID_ID; - damageInfo.in.m_amount = getBodyModule()->getMaxHealth(); - damageInfo.in.m_kill = TRUE; // Triggers object to die no matter what. - attemptDamage( &damageInfo ); - - DEBUG_ASSERTCRASH(!damageInfo.out.m_noEffect, ("Attempting to kill an unKillable object (InactiveBody?)\n")); - -} // end kill - -//------------------------------------------------------------------------------------------------- -/** Restore max health to this Object */ -//------------------------------------------------------------------------------------------------- -void Object::healCompletely() -{ - attemptHealing(HUGE_DAMAGE_AMOUNT, NULL); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void Object::setEffectivelyDead(Bool dead) -{ - if (dead) - BitSet(m_privateStatus, EFFECTIVELY_DEAD); - else - BitClear(m_privateStatus, EFFECTIVELY_DEAD); - - if (dead) - { - if( m_radarData ) - TheRadar->removeObject( this ); - } -} - -//------------------------------------------------------------------------------------------------- -void Object::setCaptured(Bool isCaptured) -{ - if (isCaptured) - BitSet(m_privateStatus, CAPTURED); - else - { - DEBUG_LOG(("Clearing Captured Status. This should never happen. jkmcd")); - BitClear(m_privateStatus, CAPTURED); - } - - // No need to see if we should skip updates, this flag has no effect on skipping updates. -} - - - -//------------------------------------------------------------------------------------------------- -Bool Object::isStructure(void) const -{ - return isKindOf(KINDOF_STRUCTURE); -} - -//------------------------------------------------------------------------------------------------- -Bool Object::isFactionStructure(void) const -{ - return isAnyKindOf( KINDOFMASK_FS ); -} - -//------------------------------------------------------------------------------------------------- -Bool Object::isNonFactionStructure(void) const -{ - return isStructure() && !isFactionStructure(); -} - -void localIsHero( Object *obj, void* userData ) -{ - Bool *hero = (Bool*)userData; - - if( obj && obj->isKindOf( KINDOF_HERO ) ) - { - *hero = TRUE; - } -} - -//------------------------------------------------------------------------------------------------- -Bool Object::isHero(void) const -{ - ContainModuleInterface *contain = getContain(); - if( contain ) - { - Bool heroInside = FALSE; - contain->iterateContained( localIsHero, (void*)(&heroInside), FALSE ); - if( heroInside ) - { - return TRUE; - } - } - return isKindOf( KINDOF_HERO ); -} - -//------------------------------------------------------------------------------------------------- -void Object::setReceivingDifficultyBonus(Bool receive) -{ - if (receive == m_isReceivingDifficultyBonus) { - return; - } - - m_isReceivingDifficultyBonus = receive; - getControllingPlayer()->friend_applyDifficultyBonusesForObject(this, m_isReceivingDifficultyBonus); -} - -//------------------------------------------------------------------------------------------------- -//- DISABLEDNESS STUFF ---------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void Object::setDisabled( DisabledType type ) -{ - setDisabledUntil(type, FOREVER); -} - -//------------------------------------------------------------------------------------------------- -void Object::setDisabledUntil( DisabledType type, UnsignedInt frame ) -{ - Bool edgeCase = !isDisabled(); - - if( type < 0 || type >= DISABLED_COUNT ) - { - DEBUG_CRASH( ("Invalid disabled type value %d specified -- doesn't not exist!", type ) ); - return; - } - - //Handle audio events! - AudioEventRTS sound; - if( type == DISABLED_UNMANNED && !isKindOf( KINDOF_DRONE ) ) - { - //We've been sniped! Play a splatter sound for the pilot losing his face. - sound = TheAudio->getMiscAudio()->m_splatterVehiclePilotsBrain; - sound.setPosition( getPosition() ); - TheAudio->addAudioEvent( &sound ); - } - else if( type == DISABLED_UNDERPOWERED || type == DISABLED_EMP || type == DISABLED_SUBDUED || type == DISABLED_HACKED ) - { - //We've lost power -- make sure we aren't already out of power as the sounds shouldn't happen - //if you were already disabled. - if( !isDisabledByType( DISABLED_UNDERPOWERED ) && - !isDisabledByType( DISABLED_EMP ) && - !isDisabledByType( DISABLED_SUBDUED ) && - !isDisabledByType( DISABLED_HACKED ) ) - { - if( isKindOf( KINDOF_STRUCTURE ) ) - { - sound = TheAudio->getMiscAudio()->m_buildingDisabled; - sound.setPosition( getPosition() ); - TheAudio->addAudioEvent( &sound ); - } - else if( isKindOf( KINDOF_VEHICLE ) ) - { - sound = TheAudio->getMiscAudio()->m_vehicleDisabled; - sound.setPosition( getPosition() ); - TheAudio->addAudioEvent( &sound ); - } - } - } - - if( m_disabledTillFrame[ type ] != frame ) - { - // an edge-test for disabledness, for type. This INCREMENTS m_pauseCount - // srj sez: HELD nevers disables special powers. - if ( type != DISABLED_HELD && !isDisabledByType( type ) ) - pauseAllSpecialPowers( TRUE ); - - m_disabledTillFrame[ type ] = frame; - m_disabledMask.set( type, frame > TheGameLogic->getFrame() ); - - if( m_drawable ) - { - if( isDisabled() ) - { - // Held does not tint anybody. If we are multiply disabled, the other setting will hit the tint, - // and in clear, only-held and not-disabled are both causes to untint. - // Doh. Also shouldn't be tinting when disabled by scripting. - // Doh^2. Also shouldn't be CLEARING tinting if we're disabling by held or script disabledness - // Doh^3. Unmanned is no tint too - if( type != DISABLED_HELD && type != DISABLED_SCRIPT_DISABLED && type != DISABLED_UNMANNED && type != DISABLED_TELEPORT) - { - m_drawable->setTintStatus( TINT_STATUS_DISABLED ); - } - } - } - - ContainModuleInterface *contain = getContain(); - if ( contain ) - { - Object *rider = (Object*)contain->friend_getRider(); - if ( rider ) - { - rider->setDisabledUntil(type, frame); - } - } - - if ( isKindOf( KINDOF_SPAWNS_ARE_THE_WEAPONS ) ) - { - SpawnBehaviorInterface *sbi = this->getSpawnBehaviorInterface(); - if ( sbi ) - { - //Kris: Patch 1.01 - November 12, 2003 - //Actually, we want to disable the slaves, not order them to go idle! This fix was made to - //stinger sites getting hit by an EMP to prevent the soldiers from attacking. - //sbi->orderSlavesToGoIdle( CMD_FROM_AI ); // the canattack() will take care of any future attempts to fire - sbi->orderSlavesDisabledUntil( type, frame ); - } - - } - - } - - if( type == DISABLED_UNMANNED && !isKindOf( KINDOF_DRONE ) ) - { - //strange but true: If I am a carbomb, - //my driver actually has a dead-man's - //trigger for my dynamite... - //If he gets sniped, I blow up! Wheeee! - - WeaponSetFlags flags; - flags.set( WEAPONSET_CARBOMB ); - const WeaponTemplateSet* set = getTemplate()->findWeaponTemplateSet( flags ); - if( set && set->testWeaponSetFlag( WEAPONSET_CARBOMB ) ) - { - Object* sniper = TheGameLogic->findObjectByID( getBodyModule()->getLastDamageInfo()->in.m_sourceID ); - if ( sniper ) - sniper->scoreTheKill( this ); - - kill(); - } - else - { - //This vehicle's pilot has been sniped, so we want to clear the veterancy rating (if any) - ExperienceTracker *xpTracker = getExperienceTracker(); - if( xpTracker ) - { - xpTracker->setExperienceAndLevel( 0, FALSE ); - } - //Not only that, but it also loses any healing bonuses it may have earned in its prior life - { - static const NameKeyType key_AutoHealBehavior = NAMEKEY("AutoHealBehavior"); - AutoHealBehavior* autoHeal = (AutoHealBehavior*)(findUpdateModule( key_AutoHealBehavior )); - if (autoHeal) - autoHeal->undoUpgrade(); - - - } - } - - } - - // This will only be called if we were NOT disabled before coming into this function. - if (edgeCase) { - onDisabledEdge(true); - } -} - -//------------------------------------------------------------------------------------------------- -UnsignedInt Object::getDisabledUntil( DisabledType type ) const -{ - if( type == DISABLED_ANY ) - { - UnsignedInt highestFrame = 0; - //Iterate through each disabled type and return the one with the highest frame. - for( Int i = 0; i < DISABLED_COUNT; i++ ) - { - if( m_disabledMask.test( i ) && m_disabledTillFrame[ i ] > highestFrame ) - { - highestFrame = m_disabledTillFrame[ i ]; - } - } - return highestFrame; - } - else if( m_disabledMask.test( type ) ) - { - //Specific query. - return m_disabledTillFrame[ type ]; - } - //Not disabled. - return 0; -} - -//------------------------------------------------------------------------------------------------- -Bool Object::clearDisabled( DisabledType type ) -{ - if( type < 0 || type >= DISABLED_COUNT ) - { - DEBUG_CRASH( ("Invalid disabled type value %d specified -- doesn't not exist!", type ) ); - return FALSE; - } - - if (!isDisabledByType(type)) { - return FALSE; - } - - if( type == DISABLED_UNDERPOWERED || type == DISABLED_EMP || type == DISABLED_SUBDUED || type == DISABLED_HACKED ) - { - //We've regained power-- make sure we aren't still disabled by another type. - AudioEventRTS sound; - if( (!isDisabledByType( DISABLED_UNDERPOWERED ) || type == DISABLED_UNDERPOWERED ) && - (!isDisabledByType( DISABLED_EMP ) || type == DISABLED_EMP ) && - (!isDisabledByType( DISABLED_SUBDUED ) || type == DISABLED_SUBDUED ) && - (!isDisabledByType( DISABLED_HACKED ) || type == DISABLED_HACKED ) ) - { - if( isKindOf( KINDOF_STRUCTURE ) ) - { - sound = TheAudio->getMiscAudio()->m_buildingReenabled; - sound.setPosition( getPosition() ); - TheAudio->addAudioEvent( &sound ); - } - else if( isKindOf( KINDOF_VEHICLE ) ) - { - sound = TheAudio->getMiscAudio()->m_vehicleReenabled; - sound.setPosition( getPosition() ); - TheAudio->addAudioEvent( &sound ); - } - } - } - - - // an edge-test for disabledness, for type. This DECREMENTS m_pauseCount - // srj sez: HELD nevers disables special powers. - if ( type != DISABLED_HELD && isDisabledByType( type ) ) - pauseAllSpecialPowers( FALSE ); - - ContainModuleInterface *contain = getContain(); - if ( contain ) - { - // We explicitly pass stuff in up in the set, so we need to turn it off if it is a forever type - Object *rider = (Object*)contain->friend_getRider(); - if( rider && (m_disabledTillFrame[ type ] == FOREVER) ) - { - rider->clearDisabled(type); - } - } - - if ( isKindOf( KINDOF_SPAWNS_ARE_THE_WEAPONS ) ) - { - SpawnBehaviorInterface *sbi = this->getSpawnBehaviorInterface(); - if ( sbi ) - { - //Kris: Patch 1.02 - December 17, 2003 - //Make sure slaves can recover from being disabled by subdual (stinger site soldier case) - sbi->orderSlavesToClearDisabled( type ); - } - - } - - m_disabledTillFrame[ type ] = NEVER; - m_disabledMask.set( type, 0 ); - - DisabledMaskType exceptions; - exceptions.set(DISABLED_HELD); - exceptions.set(DISABLED_SCRIPT_DISABLED); - exceptions.set(DISABLED_UNMANNED); - exceptions.set(DISABLED_TELEPORT); - - DisabledMaskType myFlagsMinusExceptions = getDisabledFlags(); - myFlagsMinusExceptions.clearAndSet(exceptions, DISABLEDMASK_NONE); - - // to clarify, if I am NOT disabled by anything other than DISABLED_HELD, or DISABLED_SCRIPT_DISABLED - - // to clarify, count inverse intersection gives you the number of exceptions you don't have, - // and has nothing to do with checking other disabled types -// if( !isDisabled() || getDisabledFlags().countInverseIntersection( exceptions ) == 0 ) - if( myFlagsMinusExceptions.count() == 0 ) - { - // I have no disabled flag that is not one of the exceptions above. - if (m_drawable) - m_drawable->clearTintStatus( TINT_STATUS_DISABLED ); - } - - checkDisabledStatus();// in case we just edged - - // if we're no longer disabled by anything, then call the edge function. - if (!isDisabled()) { - onDisabledEdge(false); - } - return TRUE; -} - - -//------------------------------------------------------------------------------------------------- -//Checks any timers and clears disabled statii that have expired. -//------------------------------------------------------------------------------------------------- -void Object::checkDisabledStatus() -{ - UnsignedInt now = TheGameLogic->getFrame(); - for( int i = 0; i < DISABLED_COUNT; i++ ) - { - DisabledType type = (DisabledType)i; - if( isDisabledByType( type ) ) - { - if ( now >= m_disabledTillFrame[ i ] ) - { - clearDisabled( type ); // This will also DECREMENT m_pauseCount in all specialpowers - m_disabledMask.set( type, 0 ); - } - } - } -} - -//------------------------------------------------------------------------------------------------- -void Object::pauseAllSpecialPowers( const Bool disabling ) const -{ - for (BehaviorModule** m = m_behaviors; *m; ++m) - { - SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); - if (!sp) - continue; - - sp->pauseCountdown( disabling );// So it will pause if we are disabling. - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -/** Clear the previous entered/exited flags. */ -//------------------------------------------------------------------------------------------------- -void Object::updateTriggerAreaFlags() -{ - Int j = 0; - // Update the flags, and remove any trigger areas that this object isn't inside. - for (Int i=0; igetCollide(); - if (!collide) - continue; - - // check each time thru the loop, in case a collide module sets it - if( getStatusBits().test( OBJECT_STATUS_NO_COLLISIONS ) ) - { -#ifdef DEBUG_CRC - //DEBUG_LOG(("Object::onCollide() - OBJECT_STATUS_NO_COLLISIONS set\n")); -#endif - break; - } -#ifdef DEBUG_CRC - //DEBUG_LOG(("Object::onCollide() - calling collide module\n")); -#endif - collide->onCollide(other, loc, normal); - } -} - -//------------------------------------------------------------------------------------------------- -Bool Object::isSalvageCrate() const -{ - for( BehaviorModule** m = m_behaviors; *m; ++m ) - { - CollideModuleInterface* collide = (*m)->getCollide(); - if( collide && collide->isSalvageCrateCollide() ) - { - return true; - } - } - return false; -} - -//------------------------------------------------------------------------------------------------- -/** - Our owning player is telling us to recheck our UpgradeModules, as an upgrade has completed - */ -void Object::updateUpgradeModules() -{ - if( testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) ) - return; // No upgrade can run if we are under construction. The three places that clear UnderConstruction will re-update us. - - if( testStatus( OBJECT_STATUS_DESTROYED ) ) - return; // Patch 1.03 -- Fixes crash when you upgrade a fake GLA command center to a real one if (toxic or demo). - - if( getControllingPlayer() == NULL ) - return; // This can only happen in game teardown. No upgrades for you without a player. Weird crashes are bad. - - UpgradeMaskType playerMask = getControllingPlayer()->getCompletedUpgradeMask(); - UpgradeMaskType objectMask = getObjectCompletedUpgradeMask(); - UpgradeMaskType maskToCheck = playerMask; - maskToCheck.set( objectMask ); - // We need to add in all of the already owned upgrades to handle "AND" requiring upgrades. - // We combine all the masks in case someone has a Object AND Player combination - - for (BehaviorModule** module = m_behaviors; *module; ++module) - { - UpgradeModuleInterface* upgrade = (*module)->getUpgrade(); - if (!upgrade) - continue; - - if( !upgrade->isAlreadyUpgraded() ) - { - upgrade->attemptUpgrade( maskToCheck ); - } - } -} - -//------------------------------------------------------------------------------------------------- -//This function sucks. -//It was added for objects that can disguise as other objects and contain upgraded subobject overrides. -//A concrete example is the bomb truck. Different payloads are displayed based on which upgrades have been -//made. When the bomb truck disguises as something else, these subobjects are lost because the vector is -//stored in W3DDrawModule. When we revert back to the original bomb truck, we call this function to -//recalculate those upgraded subobjects. -//------------------------------------------------------------------------------------------------- -void Object::forceRefreshSubObjectUpgradeStatus() -{ - for (BehaviorModule** module = m_behaviors; *module; ++module) - { - UpgradeModuleInterface* upgrade = (*module)->getUpgrade(); - if (!upgrade) - continue; - - if( upgrade->isSubObjectsUpgrade() ) - { - upgrade->forceRefreshUpgrade(); - } - } -} - -//------------------------------------------------------------------------------------------------- -/** Returns whether an object entered or exited an area. */ -//------------------------------------------------------------------------------------------------- -Bool Object::didEnterOrExit() const -{ - if (isKindOf(KINDOF_INERT)) { - return FALSE; - } - // note that this needs to return true if we - // entered or exited on the current frame OR - // the previous frame... since the current execution - // order is ScriptEngine, then ObjectUpdates, - // enter/exits detected in ObjectUpdate on frame N - // won't be noticed by the ScriptEngine till frame N+1. - UnsignedInt now = TheGameLogic->getFrame(); - return m_enteredOrExitedFrame == now || m_enteredOrExitedFrame == now - 1; -} - -//------------------------------------------------------------------------------------------------- -/** Returns whether an object entered an area. */ -//------------------------------------------------------------------------------------------------- -Bool Object::didEnter(const PolygonTrigger *pTrigger) const -{ - if (!didEnterOrExit()) - return false; - - DEBUG_ASSERTCRASH(!isKindOf(KINDOF_INERT), ("Asking whether an inert object entered or exited. This is invalid.\n")); - - for (Int i=0; igetUpdateExitInterface()) != NULL ) - break; - } - - // If you don't have a fancy one, you may have one from your contain module, - // since if you can contain something, they will need to get out. - if( exitInterface == NULL ) - { - ContainModuleInterface *cmod = getContain(); - if( cmod ) - { - exitInterface = cmod->getContainExitInterface(); - } - } - - return exitInterface; - -} // end getObjectExitInterface - -//------------------------------------------------------------------------------------------------- -/** Checks the object against trigger areas when the position changes. */ -//------------------------------------------------------------------------------------------------- -void Object::setTriggerAreaFlagsForChangeInPosition() -{ - // projectiles cannot trigger areas. (jkmcd) - // neither can inert objects, like the radar ping, etc. (jkmcd) - if (isKindOf(KINDOF_PROJECTILE) || isKindOf(KINDOF_INERT)) - return; - - ICoord3D iPos; - Coord3D pos = *getPosition(); - iPos.x = REAL_TO_INT(pos.x); - iPos.y = REAL_TO_INT(pos.y); - iPos.z = 0; // Trigger areas compare on xy only. - if (m_iPos.x == iPos.x && m_iPos.y == iPos.y) - { - return; // didn't move enough to change integer position. - } - - if (!isKindOf(KINDOF_IMMOBILE)) { - if (isKindOf(KINDOF_INFANTRY) || isKindOf(KINDOF_VEHICLE) ) { - TheGameClient->notifyTerrainObjectMoved(this); - } - } - - if (getAIUpdateInterface()) - { - TheAI->pathfinder()->updatePos(this, getPosition()); - } - - UnsignedInt now = TheGameLogic->getFrame(); - if (m_enteredOrExitedFrame != 0 && m_enteredOrExitedFrame != now) - updateTriggerAreaFlags(); - - // Check for exited. - Int i; - for (i=0; ipointInTrigger(m_iPos)) - { - m_triggerInfo[i].isInside = false; - m_triggerInfo[i].exited = true; - m_enteredOrExitedFrame = now; - if (m_team) - m_team->setEnteredExited(); - TheGameLogic->updateObjectsChangedTriggerAreas(); -#ifdef RTS_DEBUG - //TheScriptEngine->AppendDebugMessage("Object exited.", false); -#endif - } - } - - m_iPos = iPos; - - for (const PolygonTrigger *pTrig = PolygonTrigger::getFirstPolygonTrigger(); pTrig; pTrig = pTrig->getNext()) - { - Bool skip = false; - for (i = 0; i < m_numTriggerAreasActive; i++) - { - if (m_triggerInfo[i].pTrigger == pTrig) - { - // Already handled this one in the check for exited above. - skip = true; - break; - } - } - if (skip) - continue; - if (pTrig->pointInTrigger(m_iPos)) - { - if (m_numTriggerAreasActive < MAX_TRIGGER_AREA_INFOS) - { - m_triggerInfo[m_numTriggerAreasActive].isInside = true; - m_triggerInfo[m_numTriggerAreasActive].entered = true; - m_triggerInfo[m_numTriggerAreasActive].exited = false; - m_triggerInfo[m_numTriggerAreasActive].pTrigger = pTrig; - m_enteredOrExitedFrame = now; - if (m_team) - m_team->setEnteredExited(); - TheGameLogic->updateObjectsChangedTriggerAreas(); - ++m_numTriggerAreasActive; -#ifdef RTS_DEBUG - //TheScriptEngine->AppendDebugMessage("Object entered.", false); -#endif - } - else - { - // Shouldn't happen. - static Bool didWarn = false; - if (!didWarn) - { - didWarn = true; - TheScriptEngine->AppendDebugMessage("***WARNING - Too many nested trigger areas. ***", true); - } - } - } - - } - -} - - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Bool Object::isInList(Object **pListHead) const -{ - Bool result = m_prev || m_next || *pListHead == this; -#ifdef INTENSE_DEBUG - Bool found = false; - for (Object* o = *pListHead; o; o = o->m_next) - { - if (o == this) - { - found = true; - break; - } - } - DEBUG_ASSERTCRASH(found==result,("inconsistent links in Object::isInList")); -#endif - return result; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void Object::prependToList(Object **pListHead) -{ - DEBUG_ASSERTCRASH(!isInList(pListHead), ("obj is already in a list")); - - m_prev = NULL; - m_next = *pListHead; - if (*pListHead) - (*pListHead)->m_prev = this; - *pListHead = this; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void Object::setLayer(PathfindLayerEnum layer) -{ - if (layer!=m_layer) { -#define no_SET_LAYER_INTENSE_DEBUG -#ifdef SET_LAYER_INTENSE_DEBUG - DEBUG_LOG(("Changing layer from %d to %d\n", m_layer, layer)); - if (m_layer != LAYER_GROUND) { - if (TheTerrainLogic->objectInteractsWithBridgeLayer(this, m_layer)) { - DEBUG_CRASH(("Probably shouldn't be chaging layer. jba.")); - } - } -#endif - TheAI->pathfinder()->removePos(this); - m_layer = layer; - TheAI->pathfinder()->updatePos(this, getPosition()); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void Object::setDestinationLayer(PathfindLayerEnum layer) -{ - if (layer!=m_destinationLayer) { - m_destinationLayer = layer; - } -} - -// ------------------------------------------------------------------------------------------------ -/** Set unique ID */ -// ------------------------------------------------------------------------------------------------ -void Object::setID( ObjectID id ) -{ - - // sanity - DEBUG_ASSERTCRASH( id != INVALID_ID, ("Object::setID - Invalid id\n") ); - - // if id hasn't changed do nothing - if( m_id == id ) - return; - - // remove this objects previous id from the lookup table - TheGameLogic->removeObjectFromLookupTable( this ); - - // assign new id - m_id = id; - - // add new id to lookup table - TheGameLogic->addObjectToLookupTable( this ); - -} // end setID - -// ------------------------------------------------------------------------------------------------ -Real Object::calculateHeightAboveTerrain(void) const -{ - const Coord3D* pos = getPosition(); - Real terrainZ = TheTerrainLogic->getLayerHeight( pos->x, pos->y, m_layer ); - Real myZ = pos->z; - return myZ - terrainZ; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void Object::removeFromList(Object **pListHead) -{ - if (m_next) - m_next->m_prev = m_prev; - - if (m_prev) - m_prev->m_next = m_next; - else - *pListHead = m_next; - - m_prev = NULL; - m_next = NULL; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void Object::friend_prepareForMapBoundaryAdjust(void) -{ - // NOTE - DO NOT remove from pathfind map. jba. - // NO NO. jba. TheAI->pathfinder()->removeObjectFromPathfindMap( this ); - - // remove from the radar, remove from the partition manager - TheRadar->removeObject(this); - ThePartitionManager->unRegisterObject(this); - - // The whole PartitionManager and all of the Looker data is about to be blown away, - // so forget what I think I have done - m_partitionLastLook->reset(); - m_partitionRevealAllLastLook->reset(); - m_partitionLastShroud->reset(); - - m_partitionLastThreat->reset(); - m_partitionLastValue->reset(); - -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void Object::friend_notifyOfNewMapBoundary(void) -{ - ThePartitionManager->registerObject(this); - TheRadar->addObject(this); - TheAI->pathfinder()->addObjectToPathfindMap( this ); - - // Now that the PartitionManager has finished its reset, we need to relook - handlePartitionCellMaintenance(); - - Region3D mapExtent; - TheTerrainLogic->getExtent(&mapExtent); - if (mapExtent.isInRegionNoZ(getPosition())) - m_privateStatus &= ~OFF_MAP; - else - m_privateStatus |= OFF_MAP; -} - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void Object::calcNaturalRallyPoint(Coord2D *pt) -{ - const Matrix3D *transform = getTransformMatrix(); - Vector3 v; - - // - // get the natural rally point from the template, this coord is in model space relative - // to the model (0,0,0) - // -/* - const Coord3D *naturalRallyPoint; - naturalRallyPoint = m_template->getNaturalRallyPoint(); - v.X = naturalRallyPoint->x; - v.Y = naturalRallyPoint->y; - v.Z = naturalRallyPoint->z; -*/ - v.Set( 0, 0, 0 ); - - // transform the point into world space - transform->Transform_Vector( *transform, v, &v ); - - // we're only concerned with the 2D elements for now - pt->x = v.X; - pt->y = v.Y; - -} - -//------------------------------------------------------------------------------------------------- -Module* Object::findModule(NameKeyType key) const -{ - Module* m = NULL; - - for (BehaviorModule** b = m_behaviors; *b; ++b) - { - if ((*b)->getModuleNameKey() == key) - { -#ifdef INTENSE_DEBUG - if (m == NULL) - { - m = *b; - } - else - { - DEBUG_CRASH(("Duplicate modules found for name %s!\n",TheNameKeyGenerator->keyToName(key).str())); - } -#else - m = *b; - break; -#endif - } - } - - return m; -} - -//------------------------------------------------------------------------------------------------- -/** - * Returns true if object is currently able to move. - */ -Bool Object::isMobile() const -{ - if (isKindOf(KINDOF_IMMOBILE)) - return false; - - // AW: This excemption is needed, because teleporters still need to listen to AI commands when disabled - if( isDisabled() && !isDisabledByType(DISABLED_TELEPORT) ) - return false; - - return true; -} - -//------------------------------------------------------------------------------------------------- -void Object::scoreTheKill( const Object *victim ) -{ - // Do stuff that has nothing to do with experience points here, like tell our Player we killed something - /// @todo Multiplayer score hook location? - - Player* victimController = victim->getControllingPlayer(); - // if the other player is not a playable side (i.e. they are civilian, observer, whatever) - // we shouldn't count the kill. - if (victimController->isPlayableSide() == FALSE) - { - return; - } - - - if ( victim->isKindOf( KINDOF_IGNORED_IN_GUI ) ) - return; - - - Player* controller = getControllingPlayer(); - - if (victimController) - { - victimController->getScoreKeeper()->addObjectLost(victim); - } - - Relationship r = getRelationship(victim); - if (r != ENEMIES) - return; - - // Don't count kills that I do on my own buildings or units, cause thats just silly. - if (controller == victimController) - { - return; - } - - if (controller) - { - controller->getScoreKeeper()->addObjectDestroyed(victim); - controller->addSkillPointsForKill(this, victim); - controller->doBountyForKill(this, victim); - } - - // Now handle experience, if we can gain any - if (m_experienceTracker && m_experienceTracker->isAcceptingExperiencePoints()) - { - // srj sez: per dustin, no experience (et al) for killing things under construction. - if (!victim->testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION)) - { - Int experienceValue = victim->getExperienceTracker()->getExperienceValue( this ); - getExperienceTracker()->addExperiencePoints( experienceValue ); - } - } -} - -//------------------------------------------------------------------------------------------------- -VeterancyLevel Object::getVeterancyLevel() const -{ - return m_experienceTracker ? m_experienceTracker->getVeterancyLevel() : LEVEL_REGULAR; -} - -//------------------------------------------------------------------------------------------------- -void Object::friend_bindToDrawable( Drawable *draw ) -{ - m_drawable = draw; - if (m_drawable) - { - ModelConditionFlags set; - ModelConditionFlags clr; - for (int i = 0; i < WEAPONSET_COUNT; ++i) - { - ModelConditionFlagType mcs = TheWeaponSetTypeToModelConditionTypeMap[i]; - if( mcs != MODELCONDITION_INVALID ) - { - if (m_curWeaponSetFlags.test(i)) - set.set(mcs); - else - clr.set(mcs); - } - } - if (TheGlobalData) - { - if (TheGlobalData->m_forceModelsToFollowTimeOfDay) - { - set.set(MODELCONDITION_NIGHT, (TheGlobalData->m_timeOfDay == TIME_OF_DAY_NIGHT) ? 1 : 0); - } - - if (TheGlobalData->m_forceModelsToFollowWeather) - { - set.set(MODELCONDITION_SNOW, (TheGlobalData->m_weather == WEATHER_SNOWY) ? 1 : 0); - } - } - m_drawable->clearAndSetModelConditionFlags(clr, set); - } - - for (BehaviorModule** b = m_behaviors; *b; ++b) - { - (*b)->onDrawableBoundToObject(); - } -} - -//------------------------------------------------------------------------------------------------- -void Object::setSelectable(Bool selectable) -{ - m_isSelectable = selectable; - if (m_drawable) - { - m_drawable->setSelectable(selectable); - } -} - -//------------------------------------------------------------------------------------------------- -Bool Object::isSelectable() const -{ -// return getTemplate()->isKindOf(KINDOF_ALWAYS_SELECTABLE) -// || (m_isSelectable -// && !testStatus(OBJECT_STATUS_UNSELECTABLE) -// && !isEffectivelyDead() -// && !getTemplate()->isKindOf(KINDOF_DRONE)//Most drones are unselectable from being slaved, but the SpyDrone needs help -// ); - - - if (getTemplate()->isKindOf(KINDOF_ALWAYS_SELECTABLE)) - return TRUE; - - if ( m_isSelectable ) - if ( !testStatus(OBJECT_STATUS_UNSELECTABLE) ) - if ( !isEffectivelyDead() ) - //if ( !getTemplate()->isKindOf(KINDOF_DRONE) )//Most drones are unselectable from being slaved, but the SpyDrone needs help - return TRUE; - - return FALSE; -} - -//------------------------------------------------------------------------------------------------- -Bool Object::isMassSelectable() const -{ - return isSelectable() && !isKindOf(KINDOF_STRUCTURE); -} - -//------------------------------------------------------------------------------------------------- -void Object::setWeaponSetFlag(WeaponSetType wst) -{ - m_curWeaponSetFlags.set(wst); - m_weaponSet.updateWeaponSet(this); - if (m_drawable) - { - m_drawable->setModelConditionState(TheWeaponSetTypeToModelConditionTypeMap[wst]); - } -} - -//------------------------------------------------------------------------------------------------- -void Object::clearWeaponSetFlag(WeaponSetType wst) -{ - m_curWeaponSetFlags.set(wst, 0); - m_weaponSet.updateWeaponSet(this); - if (m_drawable) - { - m_drawable->clearModelConditionState(TheWeaponSetTypeToModelConditionTypeMap[wst]); - } -} - -//------------------------------------------------------------------------------------------------- -Bool Object::hasSpecialPower( SpecialPowerType type ) const -{ - return TEST_SPECIALPOWERMASK( m_specialPowerBits, type ); -} - -//------------------------------------------------------------------------------------------------- -Bool Object::hasAnySpecialPower() const -{ - return SPECIALPOWERMASK_ANY_SET( m_specialPowerBits ); -} - -//------------------------------------------------------------------------------------------------- -void Object::onVeterancyLevelChanged( VeterancyLevel oldLevel, VeterancyLevel newLevel, Bool provideFeedback ) -{ - updateUpgradeModules(); - - const UpgradeTemplate* up = TheUpgradeCenter->findVeterancyUpgrade(newLevel); - if (up) - giveUpgrade(up); - - BodyModuleInterface* body = getBodyModule(); - if (body) - body->onVeterancyLevelChanged( oldLevel, newLevel, provideFeedback ); - - Bool hideAnimationForStealth = FALSE; - if( !isLocallyControlled() && - testStatus( OBJECT_STATUS_STEALTHED ) && - !testStatus( OBJECT_STATUS_DETECTED ) && - !testStatus( OBJECT_STATUS_DISGUISED ) ) - { - hideAnimationForStealth = TRUE; - } - - Bool doAnimation = ( ! hideAnimationForStealth - && (newLevel > oldLevel) - && ( ! isKindOf(KINDOF_IGNORED_IN_GUI))); //First, we plan to do the animation if the level went up - - switch (newLevel) - { - case LEVEL_REGULAR: - clearWeaponSetFlag(WEAPONSET_VETERAN); - clearWeaponSetFlag(WEAPONSET_ELITE); - clearWeaponSetFlag(WEAPONSET_HERO); - clearWeaponBonusCondition(WEAPONBONUSCONDITION_VETERAN); - clearWeaponBonusCondition(WEAPONBONUSCONDITION_ELITE); - clearWeaponBonusCondition(WEAPONBONUSCONDITION_HERO); - doAnimation = FALSE;//... but not if somehow up to Regular - break; - case LEVEL_VETERAN: - setWeaponSetFlag(WEAPONSET_VETERAN); - clearWeaponSetFlag(WEAPONSET_ELITE); - clearWeaponSetFlag(WEAPONSET_HERO); - setWeaponBonusCondition(WEAPONBONUSCONDITION_VETERAN); - clearWeaponBonusCondition(WEAPONBONUSCONDITION_ELITE); - clearWeaponBonusCondition(WEAPONBONUSCONDITION_HERO); - break; - case LEVEL_ELITE: - clearWeaponSetFlag(WEAPONSET_VETERAN); - setWeaponSetFlag(WEAPONSET_ELITE); - clearWeaponSetFlag(WEAPONSET_HERO); - clearWeaponBonusCondition(WEAPONBONUSCONDITION_VETERAN); - setWeaponBonusCondition(WEAPONBONUSCONDITION_ELITE); - clearWeaponBonusCondition(WEAPONBONUSCONDITION_HERO); - break; - case LEVEL_HEROIC: - clearWeaponSetFlag(WEAPONSET_VETERAN); - clearWeaponSetFlag(WEAPONSET_ELITE); - setWeaponSetFlag(WEAPONSET_HERO); - clearWeaponBonusCondition(WEAPONBONUSCONDITION_VETERAN); - clearWeaponBonusCondition(WEAPONBONUSCONDITION_ELITE); - setWeaponBonusCondition(WEAPONBONUSCONDITION_HERO); - break; - } - - if( doAnimation && TheGameLogic->getDrawIconUI() && provideFeedback ) - { - if( TheAnim2DCollection && TheGlobalData->m_levelGainAnimationName.isEmpty() == FALSE ) - { - Anim2DTemplate *animTemplate = TheAnim2DCollection->findTemplate( TheGlobalData->m_levelGainAnimationName ); - - Coord3D pos = *getPosition(); - pos.add(&m_healthBoxOffset); - - TheInGameUI->addWorldAnimation( animTemplate, - &pos, - WORLD_ANIM_FADE_ON_EXPIRE, - TheGlobalData->m_levelGainAnimationDisplayTimeInSeconds, - TheGlobalData->m_levelGainAnimationZRisePerSecond); - } - - AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_unitPromoted; - soundToPlay.setObjectID( getID() ); - TheAudio->addAudioEvent( &soundToPlay ); - } - -} - -//------------------------------------------------------------------------------------------------- -/** - * Returns true if object currently has some kind of attack capability - */ -Bool Object::isAbleToAttack() const -{ - - //****************************************************** - //********* AUTOMATICALLY FALSE CONDITIONS ************* - //****************************************************** - - // For things that may or may not be able to normally attack, but are under a status condition - if( getStatusBits().test( OBJECT_STATUS_NO_ATTACK ) ) - return false; - - // if we're contained within a transport we cannot attack unless it specifically allows us - const Object *containedBy = getContainedBy(); - DEBUG_ASSERTCRASH( (containedBy == NULL) || (containedBy->getContain() != NULL), ("A %s thinks they are contained by something with no contain module!", getTemplate()->getName().str() ) ); - if( containedBy && containedBy->getContain() && !containedBy->getContain()->isPassengerAllowedToFire( getID() ) ) - return false; - - - // We can't fire if under construction - if( testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) ) - return false; - - // or being sold - if( testStatus(OBJECT_STATUS_SOLD) ) - return false; - - if ( isDisabledByType( DISABLED_SUBDUED ) ) - return FALSE; // A Microwave Tank is cooking me - - //We can't fire if we, as a portable structure, are aptly disabled - if ( isKindOf( KINDOF_PORTABLE_STRUCTURE ) || isKindOf( KINDOF_SPAWNS_ARE_THE_WEAPONS )) - { - if( isDisabledByType( DISABLED_HACKED ) || isDisabledByType( DISABLED_EMP ) ) - return false; - - if ( isKindOf( KINDOF_INFANTRY ) ) // I must be a stinger soldier or similar - { - for (BehaviorModule** update = getBehaviorModules(); *update; ++update)//expensive search, limited only to stinger soldiers - { - SlavedUpdateInterface* sdu = (*update)->getSlavedUpdateInterface(); - if ( sdu ) - { - ObjectID slaverID = sdu->getSlaverID(); - if ( slaverID != INVALID_ID ) - { - Object *slaver = TheGameLogic->findObjectByID( slaverID ); - if ( slaver && slaver->isDisabledByType( DISABLED_SUBDUED )) - return FALSE;// if my stinger site is subdued, so am I - } - - break;//only expect one slavedupdate, so stop searching - } - } - } - - - } - - - - //We can't fire if all our weapons are disabled! - //Currently, only turreted weapons can be disabled. - //ONLY DO THIS CHECK IF OUR UNIT DOESN'T HAVE THE - //KINDOF_CAN_ATTACK flag... nuke cannons have disabled - //turrets when not deployed, and need to be able to attack to deploy! - //Strategy centers can't attack when bombardment isn't active! - Bool anyEnabled = FALSE; - Bool anyWeapon = FALSE; - const AIUpdateInterface *ai = getAI(); - if( ai && !isKindOf( KINDOF_CAN_ATTACK ) ) - { - for( Int i = 0; i < WEAPONSLOT_COUNT; i++ ) - { - //Find the weapon in this slot. - Weapon* weapon = getWeaponInWeaponSlot( (WeaponSlotType)i ); - if( !weapon ) - continue; - - anyWeapon = TRUE; - - //We found a weapon, is it a turret? - Real dummy; - WhichTurretType tur = ai->getWhichTurretForWeaponSlot( (WeaponSlotType)i, &dummy ); - if( tur == TURRET_INVALID ) - { - //Currently impossible to disable a non-turreted weapon, so we - //have a non turreted weapon that is enabled. Quit. - anyEnabled = TRUE; - break; - } - - if( ai->isTurretEnabled( tur ) ) - { - //The turret is enable, meaning we have an enabled weapon. Quit. - anyEnabled = TRUE; - break;; - } - } - if( anyWeapon && !anyEnabled ) - { - //We failed to find any active weapons. - return FALSE; - } - } - - - //*************************************** - //********* TRUE CONDITIONS ************* - //*************************************** - - // for certain buildings - if (isKindOf(KINDOF_CAN_ATTACK)) - return true; - - // for garrisonned buildings that can attack sometimes - if( getStatusBits().test( OBJECT_STATUS_CAN_ATTACK ) ) - return true; - - // for weaponless transports. This will make me think I can, but I will check if I literally can by looking - // at passenger weapons in CanAttack. - const ContainModuleInterface* contain = getContain(); - if( contain && contain->isPassengerAllowedToFire( getID() ) && contain->getContainCount() > 0 ) - return true; - - // if we have AI and a weapon, assume we know how to use it - if (getAIUpdateInterface() != NULL && m_weaponSet.hasAnyWeapon()) - { - -// actually, we don't want to do this; we want the troop crawler to be considered "able to attack" -// even if empty, so sayeth Dustin. (srj) -// // special case: if the only damage we do is DEPLOY, we must have some guys contained. -// if (m_weaponSet.hasSingleDamageType(DAMAGE_DEPLOY)) -// { -// return contain->getContainCount() > 0; -// } -// else - { - return true; - } - } - - SpawnBehaviorInterface *spawnInterface = getSpawnBehaviorInterface(); - if( spawnInterface ) - { - if( spawnInterface->canAnySlavesAttack() ) - { - return TRUE; - } - } - - if (getTemplate()->isEnterGuard()) - return TRUE; - -//Default is no - return false; -} - -//------------------------------------------------------------------------------------------------- -/** - * Mask/Un-Mask an object - */ -void Object::maskObject( Bool mask ) -{ - - // set or clear the mask bit - setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ), mask ); - - // - // when masking objects they become unselected ... we do this in any situation for - // any player cause you aren't allowed to select masked objects, if the object is not - // selected (ie, belongs to another player) it's no big deal cause it won't be selected - // anyway - // - - if (mask) - TheGameLogic->deselectObject(this, ~getControllingPlayer()->getPlayerMask(), TRUE); - -} // end maskObject - -//------------------------------------------------------------------------------------------------- -/* - * returns true if the current locomotor is an airborne one - */ -Bool Object::isUsingAirborneLocomotor( void ) const -{ - return ( m_ai && m_ai->getCurLocomotor() && ((m_ai->getCurLocomotor()->getLegalSurfaces() & LOCOMOTORSURFACE_AIR) != 0) ); -} - -//------------------------------------------------------------------------------------------------- -//THIS FUNCTION BELONGS AT THE OBJECT LEVEL BECAUSE THERE IS AT LEAST ONE SPECIAL UNIT -//(ANGRY MOB) WHICH NEEDS LOGIC-SIDE POSITION CALC'S... -//IT WOULD PROBABLY BE WISE TO MOVE ALL THE HARD-CODED DEFAULTS BELOW -//INTO A NEW Drawable::getHealthBox..() WHICH USES GEOM0INFO, MODEL DATA, INI DATA, ETC. -void Object::getHealthBoxPosition(Coord3D& pos) const -{ - pos = *getPosition(); - pos.z += getGeometryInfo().getMaxHeightAbovePosition() + 10; - pos.add(&m_healthBoxOffset); - - // this needs to get moved to the mobspawnerupdate - if (isKindOf(KINDOF_MOB_NEXUS)) // quicker idiot test - { - pos.z += 20;// dear God, I confess my kluge, and repent. - } -} - -//------------------------------------------------------------------------------------------------- -//THIS FUNCTION BELONGS AT THE OBJECT LEVEL BECAUSE THERE IS AT LEAST ONE SPECIAL UNIT -//(ANGRY MOB) WHICH NEEDS LOGIC-SIDE POSITION CALC'S... -//IT WOULD PROBABLY BE WISE TO MOVE ALL THE HARD-CODED DEFAULTS BELOW -//INTO A NEW Drawable::getHealthBox..() WHICH USES GEOM0INFO, MODEL DATA, INI DATA, ETC. -Bool Object::getHealthBoxDimensions(Real &healthBoxHeight, Real &healthBoxWidth) const -{ - -#ifdef CALC_HEALTHBAR_FROM_HITPOINTS - Real maxHP = getBodyModule()->getMaxHealth(); - - if( isKindOf( KINDOF_STRUCTURE ) ) - { - //enforce healthBoxHeightMinimum/Maximum - healthBoxHeight = min(3.0f, max(5.0f, maxHP/50)); - //enforce healthBoxWidthMinimum/Maximum - healthBoxWidth = min(150.0f, max(100.0f, maxHP/10)); - return true; - } - else if ( isKindOf(KINDOF_MOB_NEXUS) ) - { - //enforce healthBoxHeightMinimum/Maximum - healthBoxHeight = min(3.0f, max(5.0f, maxHP/50)); - //enforce healthBoxWidthMinimum/Maximum - healthBoxWidth = min(100.0f, max(66.0f, maxHP/10)); - return true; - } - else if ( isKindOf( KINDOF_IGNORED_IN_GUI ) ) - { - healthBoxHeight = 0; - healthBoxWidth = 0; - return false; - } - else - { - //enforce healthBoxHeightMinimum/Maximum - healthBoxHeight = min(3.0f, max(5.0f, maxHP/50)); - //enforce healthBoxWidthMinimum/Maximum - healthBoxWidth = min(150.0f, max(35.0f, maxHP/10)); - return true; - } -#else - - if ( isKindOf( KINDOF_IGNORED_IN_GUI ) ) - { - healthBoxHeight = 0; - healthBoxWidth = 0; - return false; - } - - //just add the major and minor axes - Real size = MAX(20.0f, MIN(150.0f, (getGeometryInfo().getMajorRadius() + getGeometryInfo().getMinorRadius())) ); - healthBoxHeight = 3.0f; - healthBoxWidth = MAX(20.0f, size * 2.0f); - return TRUE; - -#endif - -} - - -//------------------------------------------------------------------------------------------------- -/** - * Update this object instance with properties from the map object - * - */ -void Object::updateObjValuesFromMapProperties(Dict* properties) -{ - Bool exists; - - AsciiString valStr; - Bool valBool = false; - Int valInt = 0; - Real valReal = 0.0f; - - valStr = properties->getAsciiString(TheKey_objectName, &exists); - if (exists) { - setName(valStr); - } - - valInt = properties->getInt(TheKey_objectMaxHPs, &exists); - if (exists && valInt >= 0) { - BodyModuleInterface* body = getBodyModule(); - if (body) { - body->setMaxHealth(valInt); - } - } - - valInt = properties->getInt(TheKey_objectInitialHealth, &exists); - if (exists) { - BodyModuleInterface* body = getBodyModule(); - if (body) { - body->setInitialHealth(valInt); - } - } - - // set the veterancy level - valInt = properties->getInt(TheKey_objectVeterancy, &exists); - if (exists) { - if (m_experienceTracker && m_experienceTracker->isTrainable()) - { - m_experienceTracker->setVeterancyLevel((VeterancyLevel)valInt); - } - } - - // set the aggressiveness/mood - valInt = properties->getInt(TheKey_objectAggressiveness, &exists); - if (exists) { - AIUpdateInterface *ai = getAIUpdateInterface(); - if (ai) - { - ai->setAttitude((AttitudeType)valInt); - } - } - - // set recruitable - valBool = properties->getBool(TheKey_objectRecruitableAI, &exists); - if (exists) { - if (getAIUpdateInterface()) - { - getAIUpdateInterface()->setIsRecruitable(valBool); - } - } - - // set selectable - valBool = properties->getBool(TheKey_objectSelectable, &exists); - if (exists) { - if (valBool != isSelectable()) { - setSelectable(valBool); - } - } - - // set the stopping distance - valReal = properties->getReal(TheKey_objectStoppingDistance, &exists); - if (exists && valReal >= 0.5f) - { - if (getAIUpdateInterface() && getAIUpdateInterface()->getCurLocomotor()) - { - Locomotor *loco = getAIUpdateInterface()->getCurLocomotor(); - loco->setCloseEnoughDist(valReal); - } - } - - // set the disabledness of this object - valBool = properties->getBool(TheKey_objectEnabled, &exists); - if (exists) { - setScriptStatus(OBJECT_STATUS_SCRIPT_DISABLED, !valBool); - } - - // set the disabledness of this object - valBool = properties->getBool(TheKey_objectPowered, &exists); - if (exists) { - setScriptStatus(OBJECT_STATUS_SCRIPT_UNPOWERED, !valBool); - } - - // set the invulnerability of the object - valBool = properties->getBool(TheKey_objectIndestructible, &exists); - if (exists) { - BodyModuleInterface* body = getBodyModule(); - if (body) { - body->setIndestructible(valBool); - } - } - - // set the sellability of the object - valBool = properties->getBool(TheKey_objectUnsellable, &exists); - if (exists) { - setScriptStatus(OBJECT_STATUS_SCRIPT_UNSELLABLE, valBool); - } - - //Set the player targetable setting of the object - valBool = properties->getBool( TheKey_objectTargetable, &exists ); - if( exists ) - { - setScriptStatus(OBJECT_STATUS_SCRIPT_TARGETABLE, valBool); - } - - // adjust the vision distance of this object, overriding its default vision distance - valInt = properties->getInt(TheKey_objectVisualRange, &exists); - if (exists) - { - if (valInt < 0) - valInt = 0; - m_visionRange = INT_TO_REAL(valInt); - } - - // adjust the shroud clearing distance of this object, overriding its default distance - valInt = properties->getInt(TheKey_objectShroudClearingDistance, &exists); - if (exists) - { - if (valInt < 0) - valInt = 0.0f; - m_shroudClearingRange = INT_TO_REAL(valInt); - } - - - Int upgradeNum = 0; - do - { - AsciiString keyName; - keyName.format("%s%d", TheNameKeyGenerator->keyToName(TheKey_objectGrantUpgrade).str(), upgradeNum); - valStr = properties->getAsciiString(NAMEKEY(keyName), &exists); - - if (exists) - { - const UpgradeTemplate *ut = TheUpgradeCenter->findUpgrade(valStr); - if (ut) - giveUpgrade(ut); - } - else - { - valStr.clear(); - } - - ++upgradeNum; - } while (!valStr.isEmpty()); - - Drawable *drawable = getDrawable(); - if ( drawable ) - { - valInt = properties->getInt(TheKey_objectTime, &exists); - if (exists) - { - switch (valInt) - { - case 1: - drawable->clearModelConditionState(MODELCONDITION_NIGHT); - break; - case 2: - drawable->setModelConditionState(MODELCONDITION_NIGHT); - break; - default: - break; - } - } - - valInt = properties->getInt(TheKey_objectWeather, &exists); - if (exists) - { - switch (valInt) - { - case 1: - drawable->clearModelConditionState(MODELCONDITION_SNOW); - break; - case 2: - drawable->setModelConditionState(MODELCONDITION_SNOW); - break; - default: - break; - } - } - - // See if we are supposed to playing the ambient sound - Bool soundEnabledExists; - Bool soundEnabled = properties->getBool( TheKey_objectSoundAmbientEnabled, &soundEnabledExists ); - - DynamicAudioEventInfo * audioToModify = NULL; - Bool infoModified = false; - valStr = properties->getAsciiString( TheKey_objectSoundAmbient, &exists ); - if ( exists ) - { - if ( valStr.isEmpty() ) - { - drawable->setCustomSoundAmbientOff(); - soundEnabledExists = true; - soundEnabled = false; // Don't bother trying to enable later - } - else - { - const AudioEventInfo * baseInfo = TheAudio->findAudioEventInfo( valStr ); - DEBUG_ASSERTCRASH( baseInfo != NULL, ("Cannot find customized ambient sound '%s'", valStr.str() ) ); - if ( baseInfo != NULL ) - { - audioToModify = newInstance( DynamicAudioEventInfo )( *baseInfo ); - infoModified = true; - } - } - } - - // Don't do anything more to audio if we forced the ambient sound off - if ( !( exists && valStr.isEmpty() ) ) - { - valBool = properties->getBool( TheKey_objectSoundAmbientCustomized, &exists ); - if ( exists && valBool ) - { - if ( audioToModify == NULL ) - { - const AudioEventInfo * baseInfo = drawable->getBaseSoundAmbientInfo( ); - DEBUG_ASSERTCRASH( baseInfo != NULL, ("getBaseSoundAmbientInfo() return NULL" ) ); - if ( baseInfo != NULL ) - { - audioToModify = newInstance( DynamicAudioEventInfo )( *baseInfo ); - } - } - - if ( audioToModify != NULL ) - { - valBool = properties->getBool( TheKey_objectSoundAmbientLooping, &exists ); - if ( exists ) - { - audioToModify->overrideLoopFlag( valBool ); - infoModified = true; - } - - valInt = properties->getInt( TheKey_objectSoundAmbientLoopCount, &exists ); - if ( exists && BitIsSet( audioToModify->m_control, AC_LOOP ) ) - { - audioToModify->overrideLoopCount( valInt ); - infoModified = true; - } - - valReal = properties->getReal( TheKey_objectSoundAmbientMinVolume, &exists ); - if ( exists ) - { - audioToModify->overrideMinVolume( valReal ); - infoModified = true; - } - - valReal = properties->getReal( TheKey_objectSoundAmbientVolume, &exists ); - if ( exists ) - { - audioToModify->overrideVolume( valReal ); - infoModified = true; - } - - valReal = properties->getReal( TheKey_objectSoundAmbientMinRange, &exists ); - if ( exists ) - { - audioToModify->overrideMinRange( valReal ); - infoModified = true; - } - - valReal = properties->getReal( TheKey_objectSoundAmbientMaxRange, &exists ); - if ( exists ) - { - audioToModify->overrideMaxRange( valReal ); - infoModified = true; - } - - valInt = properties->getInt( TheKey_objectSoundAmbientPriority, &exists ); - if ( exists ) - { - audioToModify->overridePriority ( (AudioPriority)valInt ); - infoModified = true; - } - } - } - } - - if ( !soundEnabledExists ) - { - // Decide if the sound should start enabled or not, since the map maker didn't record - // a preference. Enable permanently looping sounds, disable one-shot sounds by default - // NOTE: This test should match the tests done in MapObjectProps::mapObjectPageSound::dictToEnabled() - // when it decided whether or not to show a customized sound as enabled - if ( audioToModify != NULL ) - { - soundEnabled = audioToModify->isPermanentSound(); - soundEnabledExists = true; // To get into enableAmbientSoundFromScript() call. - } - else - { - // Use default audio - const AudioEventInfo * baseInfo = drawable->getBaseSoundAmbientInfo( ); - if ( baseInfo != NULL ) - { - soundEnabled = baseInfo->isPermanentSound(); - soundEnabledExists = true; // To get into enableAmbientSoundFromScript() call. - } - } - } - - if ( soundEnabledExists && !soundEnabled ) - { - // Make sure sound doesn't start playing when we set it - // ...FromScript because this is also controlled by the map designer not the game logic - drawable->enableAmbientSoundFromScript( false ); - } - - if ( infoModified && audioToModify != NULL ) - { - // Give a custom, level-specific name - drawable->mangleCustomAudioName( audioToModify ); - - // Pass to TheAudio - TheAudio->addAudioEventInfo( audioToModify ); - - drawable->setCustomSoundAmbientInfo( audioToModify ); - audioToModify = NULL; // Belongs to TheAudio now - } - - if ( audioToModify != NULL ) - { - audioToModify->deleteInstance(); - audioToModify = NULL; - } - - if ( soundEnabledExists && soundEnabled ) - { - // Play sound now that it is set up, if needed. Don't call if already enabled because that - // can cause sound to play twice - // ...FromScript because this is also controlled by the map designer not the game logic - if ( !drawable->getAmbientSoundEnabledFromScript() ) - { - drawable->enableAmbientSoundFromScript( true ); - } - } - } -} - -//------------------------------------------------------------------------------------------------- -void Object::friend_adjustPowerForPlayer( Bool incoming ) -{ - if (isDisabled() && getTemplate()->getEnergyProduction() > 0) - { - // Disabledness only affects Producers, not Consumers. - return; - } - - if (incoming) { - getControllingPlayer()->getEnergy()->objectEnteringInfluence(this); - } else { - getControllingPlayer()->getEnergy()->objectLeavingInfluence(this); - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------------- -void Object::onDisabledEdge(Bool becomingDisabled) -{ - // rip through the behavior modules and call the onDisabledEdge for any modules that care - for( BehaviorModule **module = m_behaviors; *module; ++module ) - (*module)->onDisabledEdge( becomingDisabled ); - - DozerAIInterface *dozerAI = getAI() ? getAI()->getDozerAIInterface() : NULL; - if( becomingDisabled && dozerAI ) - { - // Have to say goodbye to the thing we might be building or repairing so someone else can do it. - if( dozerAI->getCurrentTask() != DOZER_TASK_INVALID ) - dozerAI->cancelTask( dozerAI->getCurrentTask() ); - } - - Player* controller = getControllingPlayer(); - // can be called during game teardown, thus controller can be null - if (controller) - { - //@todo jkmcd - Colin suggested we rewrite this to use the interface stuff. I agree, but need - // to get some more bugs fixed today. - static NameKeyType radar = NAMEKEY("RadarUpgrade"); - Module *mod = mod = findModule(radar); - if (mod) { - RadarUpgrade *radarMod = (RadarUpgrade*) mod; - if (radarMod->isAlreadyUpgraded()) { - // Need to decrement the count here, because we own a radar upgrade - if (becomingDisabled) { - controller->removeRadar(radarMod->getIsDisableProof()); - } else { - controller->addRadar(radarMod->getIsDisableProof()); - } - } - } - } - - // We will need to adjust power ... somehow ... - Int powerToAdjust = getTemplate()->getEnergyProduction(); - - if( powerToAdjust > 0 ) - { - // We can't affect something that consumes, or else we go low power which removes the consumption - // which makes us not low power so we add the consumption so we go low power... - // This check also guaards the IsDisabled in friend_adjustPower above - static NameKeyType powerPlant = NAMEKEY("PowerPlantUpgrade"); - static NameKeyType overCharge = NAMEKEY("OverchargeBehavior"); - - Module* mod = findModule(powerPlant); - if (mod) { - PowerPlantUpgrade *powerPlantMod = (PowerPlantUpgrade*) mod; - if (powerPlantMod->isAlreadyUpgraded()) { - powerToAdjust += getTemplate()->getEnergyBonus(); - } - } - - mod = findModule(overCharge); - if (mod) { - OverchargeBehavior *overChargeMod = (OverchargeBehavior*) mod; - if (overChargeMod->isOverchargeActive()) { - powerToAdjust += getTemplate()->getEnergyBonus(); - } - } - - // Now, adjust the power for the player. - if (controller) - controller->getEnergy()->adjustPower(powerToAdjust, !becomingDisabled); - } -} - -//------------------------------------------------------------------------------------------------- -/** Object CRC implemtation */ -//------------------------------------------------------------------------------------------------- -void Object::crc( Xfer *xfer ) -{ -#ifdef DEBUG_CRC -// g_logObjectCRCs = TRUE; -// Bool g_logAllObjects = TRUE; - AsciiString logString; - AsciiString tmp; - Bool doLogging = g_logObjectCRCs /* && getControllingPlayer()->getPlayerType() == PLAYER_HUMAN */; - if (doLogging) - { - tmp.format("CRC of Object %d (%s), owned by player %d, team: %d, ", m_id, getTemplate()->getName().str(), getControllingPlayer()->getPlayerIndex(), this->getTeam() ? this->getTeam()->getID() : TEAM_ID_INVALID); - logString.concat(tmp); - } -#endif // DEBUG_CRC - - xfer->xferUnsignedByte(&m_privateStatus); -#ifdef DEBUG_CRC - if (doLogging) - { - tmp.format("m_privateStatus: %X, ", (UnsignedInt)m_privateStatus); - logString.concat(tmp); - } -#endif // DEBUG_CRC - - // This is evil - we cast the const Matrix3D * to a Matrix3D * because the XferCRC class must use - // the same interface as the XferLoad class for save game restore. This only works because - // XferCRC does not modify its data. - xfer->xferUser((Matrix3D *)getTransformMatrix(), sizeof(Matrix3D)); -#ifdef DEBUG_CRC - if (doLogging) - { - XferCRC tmpXfer; - tmpXfer.open("tmp"); - tmpXfer.xferUser((Matrix3D *)getTransformMatrix(), sizeof(Matrix3D)); - tmp.format("getTransformMatrix(): %8.8X, ", tmpXfer.getCRC()); - tmpXfer.close(); - logString.concat(tmp); - } -#endif // DEBUG_CRC - - - xfer->xferUser(&m_id, sizeof(m_id)); -#ifdef DEBUG_CRC - if (doLogging) - { - tmp.format("m_id: %d, ", m_id); - logString.concat(tmp); - } -#endif // DEBUG_CRC - xfer->xferUser(&m_objectUpgradesCompleted, sizeof(Int64)); -#ifdef DEBUG_CRC - if (doLogging) - { - tmp.format("m_objectUpgradesCompleted: %I64X, ", m_objectUpgradesCompleted); - logString.concat(tmp); - } -#endif // DEBUG_CRC - if (m_experienceTracker) - xfer->xferSnapshot( m_experienceTracker ); -#ifdef DEBUG_CRC - if (doLogging) - { - XferCRC tmpXfer; - tmpXfer.open("tmp"); - tmpXfer.xferSnapshot(m_experienceTracker); - tmp.format("m_experienceTracker: %8.8X, ", tmpXfer.getCRC()); - tmpXfer.close(); - logString.concat(tmp); - } -#endif // DEBUG_CRC - - Real health = getBodyModule()->getHealth(); - xfer->xferUser(&health, sizeof(health)); -#ifdef DEBUG_CRC - if (doLogging) - { - tmp.format("health: %g/%8.8X, ", health, AS_INT(health)); - logString.concat(tmp); - } -#endif // DEBUG_CRC - - xfer->xferUnsignedInt(&m_weaponBonusCondition); -#ifdef DEBUG_CRC - if (doLogging) - { - tmp.format("m_weaponBonusCondition: %8.8X, ", m_weaponBonusCondition); - logString.concat(tmp); - } -#endif // DEBUG_CRC - - Real scalar = getBodyModule()->getDamageScalar(); - xfer->xferUser(&scalar, sizeof(scalar)); -#ifdef DEBUG_CRC - if (doLogging) - { - tmp.format("damage scalar: %g/%8.8X\n", scalar, AS_INT(scalar)); - logString.concat(tmp); - - CRCDEBUG_LOG(("%s", logString.str())); - } -#endif // DEBUG_CRC - - for (Int i=0; ixferSnapshot( thisWeapon ); - } - } - -} // end crc - -//------------------------------------------------------------------------------------------------- -/** Object xfer implemtation - * Version Info: - * 1: Initial version - * 2: Xfers m_singleUseCommandUsed... determines if the single use command button has been used or not. - * 3: Xfers the solehealingbenefactor ID and expiration frame - * 4: misc stuff that got missed somehow - * 5: m_isReceivingDifficultyBonus - * 6: We do indeed need to save m_containedBy. The comment misrepresents what the contain module will do. - * 7: save full mtx, not pos+orient. - * 8: Kris: Conversion of object status bits from UnsignedInt to BitFlags<> - * 9: Extra sighting for reveal to all with different range units - */ -//------------------------------------------------------------------------------------------------- -void Object::xfer( Xfer *xfer ) -{ - - // version - const XferVersion currentVersion = 9; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // object ID - ObjectID id = getID(); - xfer->xferObjectID( &id ); - setID( id ); - - DEBUG_LOG(("Xfer Object %s id=%d\n",getTemplate()->getName().str(),id)); - - if (version >= 7) - { - Matrix3D mtx = *getTransformMatrix(); - xfer->xferMatrix3D(&mtx); - setTransformMatrix(&mtx); - } - else - { - // object position - Coord3D pos = *getPosition(); - xfer->xferCoord3D( &pos ); - setPosition( &pos ); - - // orientation - Real orientation = getOrientation(); - xfer->xferReal( &orientation ); - setOrientation( orientation ); - } - - // team - TeamID teamID = m_team ? m_team->getID() : TEAM_ID_INVALID; - xfer->xferUser( &teamID, sizeof( TeamID ) ); - // DON'T set the team yet; must wait till we read our status bits, - // since setTeam can affect the player's power usage, but that could - // be done incorrectly if our status bits aren't accurate yet... (srj) - - // producer id - xfer->xferObjectID( &m_producerID ); - - // builder id - xfer->xferObjectID( &m_builderID ); - - // drawable id - Drawable *draw = getDrawable(); - DrawableID drawableID = draw ? draw->getID() : INVALID_DRAWABLE_ID; - xfer->xferDrawableID( &drawableID ); - if( xfer->getXferMode() == XFER_LOAD ) - { - - // change the ID of the drawable attached to be the same ID as it was when it was saved - draw->setID( drawableID ); - - } // end if - - // internal name - xfer->xferAsciiString( &m_name ); - - // status - if( version >= 8 ) - { - m_status.xfer( xfer ); - } - else - { - //We are loading an old version, so we must convert it from a 32-bit int to a bitflag - UnsignedInt oldStatus; - xfer->xferUnsignedInt( &oldStatus ); - - //Clear our status - m_status.clear(); - - for( int i = 0; i < 32; i++ ) - { - UnsignedInt bit = 1<xferUnsignedByte( &m_scriptStatus ); - - // private status - xfer->xferUnsignedByte( &m_privateStatus ); - - // OK, now that we have xferred our status bits, it's safe to set the team... - if( xfer->getXferMode() == XFER_LOAD ) - { - Team *team = TheTeamFactory->findTeamByID( teamID ); - if( team == NULL ) - { - DEBUG_CRASH(( "Object::xfer - Unable to load team\n" )); - throw SC_INVALID_DATA; - } - const Bool restoring = true; - setOrRestoreTeam( team, restoring ); - } - - // geometry info - xfer->xferSnapshot( &m_geometryInfo ); - - // sighting info, last look - must be saved cause we save PartitionCell::m_shroudLevel - xfer->xferSnapshot( m_partitionLastLook ); - - if( version >= 9 ) - xfer->xferSnapshot( m_partitionRevealAllLastLook ); - - // sighting info, last shroud - must be saved cause we save PartitionCell::m_shroudLevel - xfer->xferSnapshot( m_partitionLastShroud ); - - // vision spied by - xfer->xferUser( m_visionSpiedBy, sizeof( Int ) * MAX_PLAYER_COUNT ); - - // vision spied by mask - xfer->xferUser( &m_visionSpiedMask, sizeof( PlayerMaskType ) ); - - // sighting info, last threat - // John M says we don't need to save this (CBD) -// xfer->xferSnapshot( &m_partitionLastThreat ); - - // sighting info, last value - // John M says we don't need to save this (CBD) -// xfer->xferSnapshot( &m_partitionLastValue ); - - // vision range - xfer->xferReal( &m_visionRange ); - - // shroud clearing range - xfer->xferReal( &m_shroudClearingRange ); - - // shroud range - xfer->xferReal( &m_shroudRange ); - - // disabled mask - m_disabledMask.xfer( xfer ); - - //New var added for version 2. Determines if the single use command button has been used or not. - if( xfer->getXferMode() == XFER_SAVE || version >= 2 ) - { - xfer->xferBool( &m_singleUseCommandUsed ); - } - else - { - m_singleUseCommandUsed = false; - } - - // disabled till frame - xfer->xferUser( m_disabledTillFrame, sizeof( UnsignedInt ) * DISABLED_COUNT ); - - // special model condition until - xfer->xferUnsignedInt( &m_smcUntil ); - - // - // radar data ... when loading, we will remove all objects from the radar and let - // the radar system load itself as a separate chunk of data from the save file - // - if( xfer->getXferMode() == XFER_LOAD && m_radarData ) - TheRadar->removeObject( this ); - - // experience tracker - xfer->xferSnapshot( m_experienceTracker ); - - // - // we do not need to do anything with our m_containedBy pointer, the post process - // of that objects contain module will actually re-do the contain process again - // - // m_containedBy <-- do nothing with this right now - if( version >= 6 ) - { - // No, the contain module is just going to friend_ reach in and set this for us. - // Containers more complicated than Open (like Tunnel) can't do that. Our variable, - // our responsibility. - if( xfer->getXferMode() == XFER_SAVE ) - { - if( m_containedBy != NULL ) - m_xferContainedByID = m_containedBy->getID(); - else - m_xferContainedByID = INVALID_ID; - } - - - xfer->xferObjectID( &m_xferContainedByID ); - } - - // contained by frame - xfer->xferUnsignedInt( &m_containedByFrame ); - - // construction percent - xfer->xferReal( &m_constructionPercent ); - - // upgrades completed - xfer->xferUpgradeMask( &m_objectUpgradesCompleted ); - - // original team name - xfer->xferAsciiString( &m_originalTeamName ); - - // indicator color - xfer->xferColor( &m_indicatorColor ); - - // health box offset - xfer->xferCoord3D( &m_healthBoxOffset ); - - // Entered & exited housekeeping. - Int i; - xfer->xferByte(&m_numTriggerAreasActive); - xfer->xferUnsignedInt(&m_enteredOrExitedFrame); - xfer->xferICoord3D(&m_iPos); - if (m_numTriggerAreasActive<0 || m_numTriggerAreasActive>MAX_TRIGGER_AREA_INFOS) { - DEBUG_CRASH(("Invalid m_numTriggerAreasActive = %d, max is %d", m_numTriggerAreasActive, - MAX_TRIGGER_AREA_INFOS)); - throw SC_INVALID_DATA; - } - for (i=0; igetTriggerName(); - } - xfer->xferAsciiString(&triggerName); - if (xfer->getXferMode() == XFER_LOAD) - { - // - // CBD (11-13-2002) I'm disabling this because it appears there might be some areas with - // empty names, see John A. for more info - // - //if (triggerName.isNotEmpty()) - m_triggerInfo[i].pTrigger = TheTerrainLogic->getTriggerAreaByName(triggerName); - } - xfer->xferByte(&m_triggerInfo[i].entered); - xfer->xferByte(&m_triggerInfo[i].exited); - xfer->xferByte(&m_triggerInfo[i].isInside); - } - // Layer object is pathing on. - xfer->xferUser(&m_layer, sizeof(m_layer)); - - // Layer of current path goal. - xfer->xferUser(&m_destinationLayer, sizeof(m_destinationLayer)); - - // Object selectability. - xfer->xferBool(&m_isSelectable); - - xfer->xferUnsignedInt(&m_safeOcclusionFrame); - - // User formations. - xfer->xferUser(&m_formationID, sizeof(m_formationID)); - if (m_formationID!=NO_FORMATION_ID) { - xfer->xferCoord2D(&m_formationOffset); - } - - // module count - UnsignedShort moduleCount = 0; - for (BehaviorModule** b = m_behaviors; *b; ++b) - ++moduleCount; - - xfer->xferUnsignedShort( &moduleCount ); - AsciiString moduleIdentifier; - BehaviorModule *module; - if( xfer->getXferMode() == XFER_SAVE ) - { - - // go through all modules - for (BehaviorModule** b = m_behaviors; *b; ++b) - { - - // get module - module = *b; - - // write module identifier - moduleIdentifier = TheNameKeyGenerator->keyToName( module->getModuleTagNameKey() ); - DEBUG_ASSERTCRASH( moduleIdentifier != AsciiString::TheEmptyString, - ("Object::xfer - Module tag key does not translate to a string!\n") ); - xfer->xferAsciiString( &moduleIdentifier ); - - // begin a data block - xfer->beginBlock(); - - // xfer data - xfer->xferSnapshot( module ); - - // end data block - xfer->endBlock(); - - } // end for, it - - } // end if, save - else - { - AsciiString otherModuleIdentifier; - - // read all module data - for( UnsignedShort i = 0; i < moduleCount; ++i ) - { - - // read module name - xfer->xferAsciiString( &moduleIdentifier ); - NameKeyType moduleIdentifierKey = TheNameKeyGenerator->nameToKey(moduleIdentifier); - - // find the module with this identifier in the module list - module = NULL; - for (BehaviorModule** b = m_behaviors; b && *b; ++b) - { - - if (moduleIdentifierKey == (*b)->getModuleTagNameKey()) - { - module = *b; - break; - } - - } // end for, moduleIt - - // start of a new block - Int dataSize = xfer->beginBlock(); - - // - // if we didn't find the module, it's quite possible that we have removed - // it from the object definition in a future patch, if that is so, we need to - // skip the module data in the file - // - if( module == NULL ) - { - - // for testing purposes, this module better be found -// DEBUG_CRASH(( "Object::xfer - Module '%s' was indicated in file, but not found on object '%s'(%d)\n", -// moduleIdentifier.str(), getTemplate()->getName().str(), getID() )); - - // skip this data in the file - xfer->skip( dataSize ); - - } // end if - else - { - - // xfer the data into this module - xfer->xferSnapshot( module ); - - } // end else - - // end block - xfer->endBlock(); - - } // end for, i module count recorded in file - - } // end else, load - - - if ( version >= 3 ) - { - xfer->xferObjectID( &m_soleHealingBenefactorID ); - xfer->xferUnsignedInt( &m_soleHealingBenefactorExpirationFrame ); - } - else if ( xfer->getXferMode() == XFER_LOAD ) - { - m_soleHealingBenefactorID = INVALID_ID; - m_soleHealingBenefactorExpirationFrame = 0; - } - - // Doesn't need to be saved. These are created as needed. jba. - //AIGroup* m_group; ///< if non-NULL, we are part of this group of agents - - // don't need to save m_partitionData. - DEBUG_ASSERTCRASH(!(xfer->getXferMode() == XFER_LOAD && m_partitionData == NULL), ("should not be in partitionmgr yet")); - - // don't need to be saved or loaded; are inited & cached for runtime only by our ctor (srj) - //m_repulsorHelper; - //m_smcHelper; - //m_wsHelper; - //m_defectionHelper; - //m_firingTracker; - //m_contain; - //m_body; - //m_ai; - //m_physics; -#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) - //m_hasDiedAlready; -#endif - - if (version >= 4) - { - // xfer the weaponSetFlags FIRST, since we need 'em to restore the weaponSet properly. (srj) - m_curWeaponSetFlags.xfer( xfer ); - xfer->xferUnsignedInt(&m_weaponBonusCondition); - xfer->xferUser(&m_lastWeaponCondition, sizeof(m_lastWeaponCondition)); - - // do the weaponSet itself after all the weapon-related stuff, just in case - xfer->xferSnapshot(&m_weaponSet); - - m_specialPowerBits.xfer( xfer ); - - xfer->xferAsciiString(&m_commandSetStringOverride); - - xfer->xferBool(&m_modulesReady); - } - - if (version >= 5) - { - xfer->xferBool(&m_isReceivingDifficultyBonus); - } - else - m_isReceivingDifficultyBonus = FALSE; - -} // end xfer - -//------------------------------------------------------------------------------------------------- -/** Object load game post process phase */ -//------------------------------------------------------------------------------------------------- -void Object::loadPostProcess() -{ - if( m_xferContainedByID != INVALID_ID ) - m_containedBy = TheGameLogic->findObjectByID(m_xferContainedByID); - else - m_containedBy = NULL; - -} // end loadPostProcess - -//------------------------------------------------------------------------------------------------- -/** Does this object have this upgrade */ -//------------------------------------------------------------------------------------------------- -Bool Object::hasUpgrade( const UpgradeTemplate *upgradeT ) const -{ - if( m_objectUpgradesCompleted.testForAll( upgradeT->getUpgradeMask() ) ) - { - return TRUE; - } - return FALSE; -} // end hasUpgrade - -//------------------------------------------------------------------------------------------------- -/** Is this object capable of having this upgrade */ -//------------------------------------------------------------------------------------------------- -Bool Object::affectedByUpgrade( const UpgradeTemplate *upgradeT ) const -{ - UpgradeMaskType objectMask = getObjectCompletedUpgradeMask(); - UpgradeMaskType playerMask = getControllingPlayer()->getCompletedUpgradeMask(); - UpgradeMaskType maskToCheck = playerMask; - maskToCheck.set( objectMask ); - maskToCheck.set( upgradeT->getUpgradeMask() ); - - // We need to add in all of the already owned upgrades to handle "AND" requiring upgrades. - // We combine all the masks in case someone has a Object AND Player combination - - for (BehaviorModule** module = m_behaviors; *module; ++module) - { - UpgradeModuleInterface* upgrade = (*module)->getUpgrade(); - if (!upgrade) - continue; - - if( upgrade->wouldUpgrade( maskToCheck ) ) - { - // if any of my many upgrade modules would execute in response to this flag, say yes. - return TRUE; - } - } - return FALSE; - -} // end affectedByUpgrade - -//------------------------------------------------------------------------------------------------- -/** Give this upgrade to this object */ -//------------------------------------------------------------------------------------------------- -void Object::giveUpgrade( const UpgradeTemplate *upgradeT ) -{ - if (upgradeT) - { - m_objectUpgradesCompleted.set( upgradeT->getUpgradeMask() ); - - // - // iterate through all the upgrade modules of this object and call the method to - // grant a new upgrade - // - updateUpgradeModules(); - } -} // end giveUpgrade - -//------------------------------------------------------------------------------------------------- -/** Remove this upgrade from this object */ -//------------------------------------------------------------------------------------------------- -void Object::removeUpgrade( const UpgradeTemplate *upgradeT ) -{ - m_objectUpgradesCompleted.clear( upgradeT->getUpgradeMask() ); - for (BehaviorModule** module = m_behaviors; *module; ++module) - { - UpgradeModuleInterface* upgrade = (*module)->getUpgrade(); - if (!upgrade) - continue; - - // Whoa, please note that while the function is called Object::RemoveUpgrade, it is not removing anything - // in the sense of undoing the effects. It is just resetting the upgrade so it may be run again. - upgrade->resetUpgrade( upgradeT->getUpgradeMask() ); - } -} - -//------------------------------------------------------------------------------------------------- -/** Central point for onCapture logic */ -//------------------------------------------------------------------------------------------------- -void Object::onCapture( Player *oldOwner, Player *newOwner ) -{ - // Everybody dhills when they captured so they don't keep doing something the new player might not want him to be doing - if( getAIUpdateInterface() && (oldOwner != newOwner) ) - getAIUpdateInterface()->aiIdle(CMD_FROM_AI); - - // this gets the new owner some points - newOwner->getScoreKeeper()->addObjectCaptured(this); - - // rip through the behavior modules and call the onCapture for any modules that care - for( BehaviorModule **module = m_behaviors; *module; ++module ) - (*module)->onCapture( oldOwner, newOwner ); - - // - // We have to undo our look for the old team and redo it for the new. - // onCapture is used now, so it better be called after ownership changes and not before. - // - handlePartitionCellMaintenance(); - - // Design needs the player to be able to sell buildings he steals from the AI's build list, and this is the - // easiest fix. The only snafu would be a key building build listed by the AI that the player can capture - // and the AI tries to capture back but needs to not sell. In that case, a Cinematic Unsellable version - // of the building needs to be made. This fix has been okayed as the most non-lethal in November. - clearScriptStatus(OBJECT_STATUS_SCRIPT_UNSELLABLE); - - // mark the command bar to redraw - TheControlBar->markUIDirty(); - - if (oldOwner!=newOwner && newOwner->isSkirmishAIPlayer()) { - // The skirmish ai doesn't know what to do with captured faction buildings except sell them. - if (isFactionStructure()) { - TheBuildAssistant->sellObject( this ); - } - } - -} // end onCapture - -//------------------------------------------------------------------------------------------------- -/// Object level events that need to happen upon game death -void Object::onDie( DamageInfo *damageInfo ) -{ - - checkAndDetonateBoobyTrap(NULL);// Already dying, so no need to handle death case of explosion - -#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) - DEBUG_ASSERTCRASH(m_hasDiedAlready == false, ("Object::onDie has been called multiple times. This is invalid. jkmcd")); - m_hasDiedAlready = true; -#endif - - Bool selfInflicted = (damageInfo->in.m_sourceID == getID()); - - // FIRST, call our die modules. - for (BehaviorModule** d = m_behaviors; *d; ++d) - { - DieModuleInterface* die = (*d)->getDie(); - if (die) - die->onDie(damageInfo); - } - - // When objects die we remove from the radar as they're really not interesting anymore - if( m_radarData ) - TheRadar->removeObject( this ); - - // Just in case I have been sporting one of thise fancy Terrain Decals, - //I naturally lose it now, because I'm dead. - Drawable *draw = getDrawable(); - if (draw) draw->setTerrainDecalFadeTarget(0.0f, -0.03f);//fade... - //if (draw) draw->setTerrainDecal(TERRAIN_DECAL_NONE);//pop! - - - // objects that were spawned from something, need to tell their spawner that they have died - Object* spawner = TheGameLogic->findObjectByID( getProducerID() ); - if( spawner ) - { - - // get the spawn behavior interface of the spawner - SpawnBehaviorInterface *spawnerBehavior = spawner->getSpawnBehaviorInterface(); - if( spawnerBehavior ) - spawnerBehavior->onSpawnDeath( getID(), damageInfo ); - - } - - handlePartitionCellMaintenance(); - if(m_team) - m_team->notifyTeamOfObjectDeath(); - - if (isLocallyControlled() && !selfInflicted) // wasLocallyControlled? :-) - { - if (isKindOf(KINDOF_STRUCTURE) && isKindOf(KINDOF_MP_COUNT_FOR_VICTORY)) - { - TheEva->setShouldPlay(EVA_BuldingLost); - } - else if (isKindOf(KINDOF_INFANTRY) || isKindOf(KINDOF_VEHICLE)) - { - TheEva->setShouldPlay(EVA_UnitLost); - //Create a fake radar event so the user can use the spacebar to quickly jump to this! - TheRadar->tryEvent( RADAR_EVENT_FAKE, getPosition() ); - } - } - - // This call won't do anything if we aren't actually in the list. - //Kris: Added NULL check to prevent crash with combat bikes & their riders getting deleted on exit. - if( getControllingPlayer() ) - { - TheInGameUI->removeIdleWorker( this, getControllingPlayer()->getPlayerIndex() ); - } - - //When a GLA hole is in the process of rebuilding, and that rebuild is lost, we need to - //tell anyone attacking it to transfer the attack to the hole that still exists. - if( testStatus( OBJECT_STATUS_RECONSTRUCTING ) ) - { - Object *hole = TheGameLogic->findObjectByID( getProducerID() ); - if( hole ) - { - // set the information in the hole about what to build - RebuildHoleBehaviorInterface *rhbi = RebuildHoleBehavior::getRebuildHoleBehaviorInterfaceFromObject( hole ); - - // sanity - DEBUG_ASSERTCRASH( rhbi, ("Object::onDie() - No Rebuild Hole Behavior interface on hole\n") ); - - // start the rebuild process - if( rhbi ) - { - rhbi->startRebuildProcess( getTemplate(), getID() ); - } - - //Transfer any attackers from the destroyed building to the hole. - for ( Object *obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() ) - { - AIUpdateInterface* ai = obj->getAI(); - if (!ai) - continue; - - ai->transferAttack( getID(), hole->getID() ); - } - } - } - -} - -//------------------------------------------------------------------------------------------------- -void Object::setWeaponBonusCondition(WeaponBonusConditionType wst) -{ - WeaponBonusConditionFlags oldCondition = m_weaponBonusCondition; - m_weaponBonusCondition |= (1 << wst); - - if( oldCondition != m_weaponBonusCondition ) - { - // Our weapon bonus just changed, so we need to immediately update our weapons - m_weaponSet.weaponSetOnWeaponBonusChange(this); - } -} - -//------------------------------------------------------------------------------------------------- -void Object::clearWeaponBonusCondition(WeaponBonusConditionType wst) -{ - WeaponBonusConditionFlags oldCondition = m_weaponBonusCondition; - m_weaponBonusCondition &= ~(1 << wst); - - if( oldCondition != m_weaponBonusCondition ) - { - // Our weapon bonus just changed, so we need to immediately update our weapons - m_weaponSet.weaponSetOnWeaponBonusChange(this); - } -} - -//------------------------------------------------------------------------------------------------- -/** - A weapon cannot be in charge of maintaining condition flags as it is all event driven. - I will maintain my ModelCondition myself if it should change. Firing is set by firing logic, - so I don't include it here. It is only the states that expire on timers that noone watches - that I am concerned with. -*/ -//------------------------------------------------------------------------------------------------- -void Object::adjustModelConditionForWeaponStatus() -{ - UnsignedInt now = TheGameLogic->getFrame(); - - for (int i = 0; i < WEAPONSLOT_COUNT; ++i) - { - const Weapon* w = m_weaponSet.getWeaponInWeaponSlot((WeaponSlotType)i); - if (!w) - { - m_lastWeaponCondition[i] = WSF_NONE; - continue; - } - - WeaponSetConditionType conditionToSet = WSF_INVALID; - if (i != m_weaponSet.getCurWeaponSlot()) - { - // if this isn't the current weapon, then we never set ANYTHING for it. - conditionToSet = WSF_NONE; - } - else if (w->getLastShotFrame() == now) - { - // yep, this overrides any weapon-status condition! - conditionToSet = WSF_FIRING; - } - else if (!testStatus( OBJECT_STATUS_IS_ATTACKING )) - { - // srj sez: not 100% sure about this one, but the problem is: say we were attacking, - // then issue a move command. if we didn't do this here, we might still have a 'firing' - // pose, because his weapon might be in 'reloading' mode. since we're not attacking, however, - // we really don't care, so we just force the issue here. (This might still need tweaking for the pursue state.) - conditionToSet = WSF_NONE; - } - else - { - WeaponStatus newStatus = w->getStatus(); - - const static WeaponSetConditionType s_wsfLookup[WEAPON_STATUS_COUNT] = - { - WSF_NONE, // READY_TO_FIRE, - WSF_NONE, // OUT_OF_AMMO, - WSF_BETWEEN, // BETWEEN_FIRING_SHOTS, - WSF_RELOADING, // RELOADING_CLIP, - WSF_PREATTACK // PRE_ATTACK, - }; - conditionToSet = s_wsfLookup[newStatus]; - - // special case this: say we are firing in bursts: pow-pow-pow-pause, etc. - // then we might have a frame where we have reloaded and are ready-to-fire, - // but haven't fired yet this frame. in that case, use 'between' so we still have - // a firing pose, 'cuz if we use 'none' we will 'pop' back to idle for a frame. (srj) - // additional note: only do if aiming or firing, since we could also be in this state if - // we are approaching or pursuing a target! (srj) - if (newStatus == READY_TO_FIRE && conditionToSet == WSF_NONE && testStatus( OBJECT_STATUS_IS_ATTACKING ) && - (testStatus( OBJECT_STATUS_IS_AIMING_WEAPON ) || testStatus( OBJECT_STATUS_IS_FIRING_WEAPON ))) - { - conditionToSet = WSF_BETWEEN; - } - - } - - if (m_drawable) - { - m_drawable->updateDrawableClipStatus( w->getRemainingAmmo(), w->getClipSize(), w->getWeaponSlot() ); - if (conditionToSet != WSF_INVALID && conditionToSet != m_lastWeaponCondition[i]) - { - m_lastWeaponCondition[i] = conditionToSet; - ModelConditionFlags c = m_weaponSet.getModelConditionForWeaponSlot((WeaponSlotType)i, conditionToSet); - m_drawable->clearAndSetModelConditionFlags(s_allWeaponFireFlags[i], c); - if (conditionToSet == WSF_PREATTACK) - { - // in the preattack state, adjust the speed of the preattack anim to match the actual time it will take - UnsignedInt preAttackDone = w->getPreAttackFinishedFrame(); - if (preAttackDone > now) - m_drawable->setAnimationLoopDuration(preAttackDone - now); - } - } - } - } -} - -//------------------------------------------------------------------------------------------------- -/// We have moved a 'significant' amount, so do maintenence that can be considered 'cell-based' -void Object::onPartitionCellChange() -{ - handlePartitionCellMaintenance(); -} - -//------------------------------------------------------------------------------------------------- -void Object::handlePartitionCellMaintenance() -{ - handleShroud(); - handleValueMap(); - handleThreatMap(); -} - -//------------------------------------------------------------------------------------------------- -void Object::handleShroud() -{ - // Undo last looking - unlook(); - // and shrouding - unshroud(); - - // redo shrouding - shroud(); - // Redo looking - look(); -} - -//------------------------------------------------------------------------------------------------- -void Object::handleValueMap() -{ - removeValue(); - addValue(); -} - -//------------------------------------------------------------------------------------------------- -void Object::handleThreatMap() -{ - removeThreat(); - addThreat(); -} - -//------------------------------------------------------------------------------------------------- -void Object::addValue() -{ - if( !m_partitionLastValue->isInvalid() ) - { - DEBUG_CRASH( ("An Object is adding value, but hasn't removed his previous value.") ); - return; - } - - if (!getControllingPlayer()) - return; - - if( getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) || isEffectivelyDead() || getShroudClearingRange() <= 0.0f ) - return; - - - m_partitionLastValue->m_where = *getPosition(); - m_partitionLastValue->m_data = getTemplate()->friend_getBuildCost(); - - m_partitionLastValue->m_forWhom = getControllingPlayer()->getPlayerMask(); - m_partitionLastValue->m_howFar = getVisionRange(); // we are valuable all the way to where we can target. - - ThePartitionManager->doValueAffect(m_partitionLastValue->m_where.x, - m_partitionLastValue->m_where.y, - m_partitionLastValue->m_howFar, - m_partitionLastValue->m_data, - m_partitionLastValue->m_forWhom - ); -} - -//------------------------------------------------------------------------------------------------- -void Object::removeValue() -{ - if( m_partitionLastValue->isInvalid() ) - { - // removing before adding is valid, cause we always remove before adding. (So the first remove - // will occur before the first add) - return; - } - - ThePartitionManager->undoValueAffect(m_partitionLastValue->m_where.x, - m_partitionLastValue->m_where.y, - m_partitionLastValue->m_howFar, - m_partitionLastValue->m_data, - m_partitionLastValue->m_forWhom - ); - - m_partitionLastValue->reset(); -} - -//------------------------------------------------------------------------------------------------- -void Object::addThreat() -{ - if( !m_partitionLastThreat->isInvalid() ) - { - DEBUG_CRASH( ("An Object is adding threat, but hasn't removed his previous threat. (He hasn't finished the threat?)") ); - return; - } - - if (!getControllingPlayer()) - return; - - if( getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) || isEffectivelyDead() || getShroudClearingRange() <= 0.0f ) - return; - - - m_partitionLastThreat->m_where = *getPosition(); - m_partitionLastThreat->m_data = getTemplate()->getThreatValue(); - - m_partitionLastThreat->m_forWhom = getControllingPlayer()->getPlayerMask(); - m_partitionLastThreat->m_howFar = getVisionRange(); // we are threatening all the way to where we can target. - - ThePartitionManager->doThreatAffect(m_partitionLastThreat->m_where.x, - m_partitionLastThreat->m_where.y, - m_partitionLastThreat->m_howFar, - m_partitionLastThreat->m_data, - m_partitionLastThreat->m_forWhom - ); -} - -//------------------------------------------------------------------------------------------------- -void Object::removeThreat() -{ - if( m_partitionLastThreat->isInvalid() ) - { - // removing before adding is valid, cause we always remove before adding. (So the first remove - // will occur before the first add) - return; - } - - ThePartitionManager->undoThreatAffect(m_partitionLastThreat->m_where.x, - m_partitionLastThreat->m_where.y, - m_partitionLastThreat->m_howFar, - m_partitionLastThreat->m_data, - m_partitionLastThreat->m_forWhom - ); - - m_partitionLastThreat->reset(); -} - - - -//------------------------------------------------------------------------------------------------- -void Object::look() -{ - if( ! m_partitionLastLook->isInvalid() ) - { - DEBUG_CRASH( ("An Object is looking, but hasn't unlooked the last one.") ); - return; - } - - Player* controller = getControllingPlayer(); - if ( controller ) - { - // I removed the check for objects under construction by request of designers since - // they want constructing objects to have a reduced sight range now. -MW - // dead or blind things don't reveal shroud - - - - // Some things get Destroyed directly without hitting Death. - if( !isDestroyed() && !isEffectivelyDead() ) - { - - ContainModuleInterface * contain = (getContainedBy() ? getContainedBy()->getContain() : NULL); - if ( contain && !contain->isGarrisonable() ) - return;// dont look, 'cause you are in a tunnel, now - // GS 10-20 Need to expand that exception to all transports or else you get a perma reveal where - // you entered the transport. Remember, this hackiness is caused by the fact that we never realized that - // garrisoned buildings weren't looking, we were just seeing the leftover last look of the guy inside. - // Otherwise we'd just have enclosingContainer control looking which is the 'correct' answer. - - Real shroudClearingRange = getShroudClearingRange(); - if( shroudClearingRange > 0.0f ) - { - PlayerMaskType lookingMask = 0; - - if ( isKindOf(KINDOF_REVEAL_TO_ALL) ) - { - lookingMask = PLAYERMASK_ALL; - } - else - { - for( Int currentIndex = ThePlayerList->getPlayerCount() - 1; currentIndex >=0; currentIndex-- ) - { - const Player *currentPlayer = ThePlayerList->getNthPlayer( currentIndex ); - - // Build mask of of allies who can see me. - // This is the Object-centric game level that cares - if( getControllingPlayer()->getRelationship( currentPlayer->getDefaultTeam() ) == ALLIES ) - { - lookingMask |= currentPlayer->getPlayerMask(); - } - } - - // Other players can also be looking through our eyes. - lookingMask |= m_visionSpiedMask; - } - - Coord3D pos = *getPosition(); - ThePartitionManager->doShroudReveal( pos.x, pos.y, shroudClearingRange, lookingMask ); - - m_partitionLastLook->m_where = pos; - m_partitionLastLook->m_forWhom = lookingMask; - m_partitionLastLook->m_howFar = getShroudClearingRange(); - - // DEBUG_LOG(( "A %s looks at %f, %f for %x at range %f\n", - // getTemplate()->getName().str(), - // pos.x, - // pos.y, - // lookingMask, - // getShroudClearingRange() - // )); - } - - //Now reveal to everyone if we're special. Note this works differently than KINDOF_REVEAL_TO_ALL because - //the kindof uses the same range as allies, spies, and owners would see. This template based shroud - //reveal to all range can specify a different value so we can get a much smaller reveal distance. - // And don't reveal while under construction. When finished, a refresh occurs, so don't worry. - Real shroudRevealToAllRange = getTemplate()->getShroudRevealToAllRange(); - if( shroudRevealToAllRange > 0.0f && !testStatus( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) - { - //Kris: August 20, 2003 - //Seeing I added this concept, I'm changing it now to only reveal to all when the unit is visible. If it's stealthed, - //we won't reveal it anymore (stealth general scudstorm). - Bool stealthedAndNotDetected = testStatus( OBJECT_STATUS_STEALTHED ) && !testStatus( OBJECT_STATUS_DETECTED ) && !testStatus( OBJECT_STATUS_DISGUISED ); - if( !stealthedAndNotDetected ) - { - Coord3D pos = *getPosition(); - PlayerMaskType thePlayersMask = ThePlayerList->getPlayersWithRelationship( getControllingPlayer()->getPlayerIndex(), ALLOW_ENEMIES | ALLOW_NEUTRAL ); - ThePartitionManager->doShroudReveal( pos.x, pos.y, shroudRevealToAllRange, thePlayersMask ); - m_partitionRevealAllLastLook->m_where = pos; - m_partitionRevealAllLastLook->m_forWhom = thePlayersMask; - m_partitionRevealAllLastLook->m_howFar = shroudRevealToAllRange; - } - } - } - } -} - -//------------------------------------------------------------------------------------------------- -void Object::unlook() -{ - if( m_partitionLastLook->isInvalid() ) - { - // Your very first action will be an unlook, so of course you haven't looked yet. This is not an error - // This early return prevents an extra unlook if you never looked. Like you have 0 vision. - return; - } - - ThePartitionManager->queueUndoShroudReveal(m_partitionLastLook->m_where.x, - m_partitionLastLook->m_where.y, - m_partitionLastLook->m_howFar, - m_partitionLastLook->m_forWhom - ); - -// DEBUG_LOG(( "A %s queues an unlook at %f, %f for %x at range %f\n", -// getTemplate()->getName().str(), -// m_partitionLastLook.m_where.x, -// m_partitionLastLook.m_where.y, -// m_partitionLastLook.m_forWhom, -// m_partitionLastLook.m_howFar -// )); - - m_partitionLastLook->reset(); - - if( !m_partitionRevealAllLastLook->isInvalid() ) - { - ThePartitionManager->queueUndoShroudReveal(m_partitionRevealAllLastLook->m_where.x, - m_partitionRevealAllLastLook->m_where.y, - m_partitionRevealAllLastLook->m_howFar, - m_partitionRevealAllLastLook->m_forWhom - ); - - m_partitionRevealAllLastLook->reset(); - } -} - -//------------------------------------------------------------------------------------------------- -void Object::shroud() -{ - if( ! m_partitionLastShroud->isInvalid() ) - { - DEBUG_CRASH( ("An Object is shrouding, but hasn't unshrouded the last one.") ); - return; - } - - Player* controller = getControllingPlayer(); - if ( controller ) - { - // things under construction don't shroud. (srj), nor do dead or blind things - if( !getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) && !isEffectivelyDead() && getShroudRange() > 0.0f ) - { - PlayerMaskType shroudingMask = 0; - for( Int currentIndex = ThePlayerList->getPlayerCount() - 1; currentIndex >=0; currentIndex-- ) - { - const Player *currentPlayer = ThePlayerList->getNthPlayer( currentIndex ); - //Build mask of NON-allies. This is the Object-centric game level that cares - if( getControllingPlayer()->getRelationship( currentPlayer->getDefaultTeam() ) != ALLIES ) - { - shroudingMask |= currentPlayer->getPlayerMask(); - } - } - - Coord3D pos = *getPosition(); - ThePartitionManager->doShroudCover(pos.x, pos.y, - getShroudRange(), - shroudingMask); - - m_partitionLastShroud->m_where = pos; - m_partitionLastShroud->m_forWhom = shroudingMask; - m_partitionLastShroud->m_howFar = getShroudRange(); - } - } -} - -//------------------------------------------------------------------------------------------------- -void Object::unshroud() -{ - if( m_partitionLastShroud->isInvalid() ) - { - // Your very first action will be an unlook, so of course you haven't looked yet. This is not an error - // This early return prevents an extra unlook if you never looked. Like you have 0 shroud generation. - return; - } - - ThePartitionManager->undoShroudCover(m_partitionLastShroud->m_where.x, - m_partitionLastShroud->m_where.y, - m_partitionLastShroud->m_howFar, - m_partitionLastShroud->m_forWhom); - - m_partitionLastShroud->reset(); -} - -//------------------------------------------------------------------------------------------------- -Real Object::getVisionRange() const -{ -#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) - if (TheGlobalData->m_debugVisibility) - { - Vector3 pos(m_visionRange, 0, 0); - for (int i = 0; i < TheGlobalData->m_debugVisibilityTileCount; ++i) - { - pos.Rotate_Z(1.0f * i / TheGlobalData->m_debugVisibilityTileCount * 2 * PI); - Coord3D coord = { pos.X + getPosition()->x, pos.Y + getPosition()->y, pos.Z + getPosition()->z }; - - addIcon(&coord, TheGlobalData->m_debugVisibilityTileWidth, - TheGlobalData->m_debugVisibilityTileDuration, - TheGlobalData->m_debugVisibilityTargettableColor); - } - } -#endif - return m_visionRange; -} - -//------------------------------------------------------------------------------------------------- -void Object::setVisionRange( Real newVisionRange ) -{ - m_visionRange = newVisionRange; -} - -//------------------------------------------------------------------------------------------------- -Real Object::getShroudClearingRange() const -{ - Real shroudClearingRange=m_shroudClearingRange; - - if( getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) - { - //structures under construction have limited vision range. For now, base it - //on the geometry extents so the structure can only see itself. - shroudClearingRange = getGeometryInfo().getBoundingCircleRadius(); - } - -#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) - if (TheGlobalData->m_debugVisibility) - { - Vector3 pos(shroudClearingRange, 0, 0); - for (int i = 0; i < TheGlobalData->m_debugVisibilityTileCount; ++i) - { - pos.Rotate_Z(1.0f * i / TheGlobalData->m_debugVisibilityTileCount * 2 * PI); - Coord3D coord = { pos.X + getPosition()->x, pos.Y + getPosition()->y, pos.Z + getPosition()->z }; - - addIcon(&coord, TheGlobalData->m_debugVisibilityTileWidth, - TheGlobalData->m_debugVisibilityTileDuration, - TheGlobalData->m_debugVisibilityDeshroudColor); - } - } -#endif - - return shroudClearingRange; -} - -//------------------------------------------------------------------------------------------------- -void Object::setShroudClearingRange( Real newShroudClearingRange ) -{ - if( newShroudClearingRange != m_shroudClearingRange ) - { - // The partition cell refresh is a slow operation, so only do it if you really have to. - // Range change is a valid reason to relook. - m_shroudClearingRange = newShroudClearingRange; - - /* - Complete and total monkey hack fix. - - The problem: newObject doesn't get an initial pos, so all objects start at 0,0,0. - Most code paths instantly move 'em to a good pos, but in some cases, that is too late: - If we have search-and-destroy battle plan, we will apply it at that point, and clear out - a vision range based on our current (wrong) location. Doh! - - So, this just sez: if you are at 0,0,0, don't call handlePartitionCellMaintenance()... since - you will either (1) be moved elsewhere immediately, thus forcing it to be called via - another route anyway, or (2) not be moved, which means you are a very naughty and worthless - object anyway and we should just ignore you. - - Proper fix for next version is to require initial pos to be passed in to newObject so that - all objects can start at their proper initial position from the start of the ctor. - - (srj) - */ - const Coord3D* pos = getPosition(); - if (pos->x || pos->y || pos->z) - { - handlePartitionCellMaintenance(); - } - } -} - -//------------------------------------------------------------------------------------------------- -Real Object::getShroudRange() const -{ -#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) - if (TheGlobalData->m_debugVisibility) - { - Vector3 pos(m_shroudRange, 0, 0); - for (int i = 0; i < TheGlobalData->m_debugVisibilityTileCount; ++i) - { - pos.Rotate_Z(1.0f * i / TheGlobalData->m_debugVisibilityTileCount * 2 * PI); - Coord3D coord = { pos.X + getPosition()->x, pos.Y + getPosition()->y, pos.Z + getPosition()->z }; - - addIcon(&coord, TheGlobalData->m_debugVisibilityTileWidth, - TheGlobalData->m_debugVisibilityTileDuration, - TheGlobalData->m_debugVisibilityGapColor); - } - } -#endif - - return m_shroudRange; -} - -//------------------------------------------------------------------------------------------------- -void Object::setShroudRange( Real newShroudRange ) -{ - m_shroudRange = newShroudRange; -} - -//------------------------------------------------------------------------------------------------- -void Object::setVisionSpied(Bool setting, Int byWhom) -{ - Bool needRefresh = FALSE; // If this setting is an edge trigger on the reference count, I need to refresh - - if( setting ) - { - m_visionSpiedBy[ byWhom ] = m_visionSpiedBy[ byWhom ] + 1; - if( m_visionSpiedBy[ byWhom ] == 1 ) - needRefresh = TRUE; - } - else - { - m_visionSpiedBy[ byWhom ] = m_visionSpiedBy[ byWhom ] - 1; - if( m_visionSpiedBy[ byWhom ] == 0 ) - needRefresh = TRUE; - } - - if( needRefresh ) - { - PlayerMaskType workingMask = 0; - for (Int i = 0; i < MAX_PLAYER_COUNT; ++i) - { - if( m_visionSpiedBy[i] > 0 ) - BitSet( workingMask, ( 1 << i ) ); - else - BitClear( workingMask, ( 1 << i ) ); - } - - m_visionSpiedMask = workingMask; - - handlePartitionCellMaintenance(); - } -} - -//------------------------------------------------------------------------------------------------- -void Object::doStatusDamage( ObjectStatusTypes status, Real duration ) -{ - if(m_statusDamageHelper) - m_statusDamageHelper->doStatusDamage(status, duration); -} - -//------------------------------------------------------------------------------------------------- -void Object::doTempWeaponBonus( WeaponBonusConditionType status, UnsignedInt duration, TintStatus tintStatus) -{ - if(m_tempWeaponBonusHelper) - m_tempWeaponBonusHelper->doTempWeaponBonus(status, duration, tintStatus); -} - -//------------------------------------------------------------------------------------------------- -void Object::notifySubdualDamage( Real amount ) -{ - if(m_subdualDamageHelper) - m_subdualDamageHelper->notifySubdualDamage( amount ); - - // If we are gaining subdual damage, we are slowly tinting - if( getDrawable() ) - { - if( amount > 0 ) - getDrawable()->setTintStatus(TINT_STATUS_GAINING_SUBDUAL_DAMAGE); - else - getDrawable()->clearTintStatus(TINT_STATUS_GAINING_SUBDUAL_DAMAGE); - } -} - -//------------------------------------------------------------------------------------------------- -/** Given a special power template, find the module in the object that can implement it. - * There can be at most one */ -//------------------------------------------------------------------------------------------------- -SpecialPowerModuleInterface *Object::getSpecialPowerModule( const SpecialPowerTemplate *specialPowerTemplate ) const -{ - - // sanity - if( specialPowerTemplate == NULL ) - return NULL; - - // search the modules for the one with the matching template - for( BehaviorModule** m = m_behaviors; *m; ++m ) - { - SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); - if (!sp) - continue; - - if( sp->isModuleForPower( specialPowerTemplate ) ) - return sp; - } - - return NULL; - -} - -//------------------------------------------------------------------------------------------------- -/** Execute special power */ -//------------------------------------------------------------------------------------------------- -void Object::doSpecialPower( const SpecialPowerTemplate *specialPowerTemplate, UnsignedInt commandOptions, Bool forced ) -{ - - if (isDisabled()) - return; - - // sanity - if( !forced && TheSpecialPowerStore->canUseSpecialPower( this, specialPowerTemplate ) == FALSE ) - return; - - // get the module and execute - SpecialPowerModuleInterface *mod = getSpecialPowerModule( specialPowerTemplate ); - if( mod ) - mod->doSpecialPower( commandOptions ); - -} - -//------------------------------------------------------------------------------------------------- -/** Execute special power */ -//------------------------------------------------------------------------------------------------- -void Object::doSpecialPowerAtObject( const SpecialPowerTemplate *specialPowerTemplate, Object *obj, UnsignedInt commandOptions, Bool forced ) -{ - - if (isDisabled()) - return; - - // sanity - if( !forced && TheSpecialPowerStore->canUseSpecialPower( this, specialPowerTemplate ) == FALSE ) - return; - - // get the module and execute - SpecialPowerModuleInterface *mod = getSpecialPowerModule( specialPowerTemplate ); - if( mod ) - mod->doSpecialPowerAtObject( obj, commandOptions ); -} - -//------------------------------------------------------------------------------------------------- -/** Execute special power */ -//------------------------------------------------------------------------------------------------- -void Object::doSpecialPowerAtLocation( const SpecialPowerTemplate *specialPowerTemplate, - const Coord3D *loc, Real angle, UnsignedInt commandOptions, Bool forced ) -{ - - if (isDisabled()) - return; - - // sanity - if( !forced && TheSpecialPowerStore->canUseSpecialPower( this, specialPowerTemplate ) == FALSE ) - return; - - // get the module and execute - SpecialPowerModuleInterface *mod = getSpecialPowerModule( specialPowerTemplate ); - if( mod ) - mod->doSpecialPowerAtLocation( loc, angle, commandOptions ); - -} - -//------------------------------------------------------------------------------------------------- -/** Execute special power */ -//------------------------------------------------------------------------------------------------- -void Object::doSpecialPowerUsingWaypoints( const SpecialPowerTemplate *specialPowerTemplate, const Waypoint *way, UnsignedInt commandOptions, Bool forced ) -{ - - if (isDisabled()) - return; - - // sanity - if( !forced && TheSpecialPowerStore->canUseSpecialPower( this, specialPowerTemplate ) == FALSE ) - return; - - // get the module and execute - SpecialPowerModuleInterface *mod = getSpecialPowerModule( specialPowerTemplate ); - if( mod ) - mod->doSpecialPowerUsingWaypoints( way, commandOptions ); - -} - -//------------------------------------------------------------------------------------------------- -/** Execute command button ability */ -//------------------------------------------------------------------------------------------------- -void Object::doCommandButton( const CommandButton *commandButton, CommandSourceType cmdSource ) -{ - if (isDisabled()) - return; - - AIUpdateInterface *ai = getAIUpdateInterface(); - if( commandButton ) - { - switch( commandButton->getCommandType() ) - { - case GUI_COMMAND_SPECIAL_POWER: - if( commandButton->getSpecialPowerTemplate() ) - { - CommandOption commandOptions = (CommandOption)(commandButton->getOptions() | COMMAND_FIRED_BY_SCRIPT); - doSpecialPower( commandButton->getSpecialPowerTemplate(), commandOptions, cmdSource == CMD_FROM_SCRIPT ); - return; - } - break; - case GUI_COMMAND_STOP: - if( ai ) - { - ai->aiIdle( cmdSource ); - return; - } - break; - - case GUI_COMMAND_SWITCH_WEAPON: - { - WeaponSlotType weaponSlot = commandButton->getWeaponSlot(); - // GUI_COMMAND_SWITCH_WEAPON switches until un-switched, or switched to something else. - setWeaponLock( weaponSlot, LOCKED_PERMANENTLY ); - return; - } - - case GUI_COMMAND_FIRE_WEAPON: - if( ai ) - { - if( !BitIsSet( commandButton->getOptions(), COMMAND_OPTION_NEED_OBJECT_TARGET ) && !BitIsSet( commandButton->getOptions(), NEED_TARGET_POS ) ) - { - setWeaponLock( commandButton->getWeaponSlot(), LOCKED_TEMPORARILY ); - //LOCATION BASED FIRE WEAPON - ai->aiAttackPosition( NULL, commandButton->getMaxShotsToFire(), cmdSource ); - } - else - { - DEBUG_CRASH( ("WARNING: Script doCommandButton for button %s cannot fire weapon with NO POSITION. Skipping.", commandButton->getName().str()) ); - } - return; - } - break; - - case GUI_COMMAND_OBJECT_UPGRADE: - case GUI_COMMAND_PLAYER_UPGRADE: - { - const UpgradeTemplate *upgradeT = commandButton->getUpgradeTemplate(); - DEBUG_ASSERTCRASH( upgradeT, ("Undefined upgrade '%s' in player upgrade command\n", "UNKNOWN") ); - // sanity - if( upgradeT == NULL ) - break; - if( upgradeT->getUpgradeType() == UPGRADE_TYPE_OBJECT ) - { - if( hasUpgrade( upgradeT ) || !affectedByUpgrade( upgradeT ) ) - break; - } - // producer must have a production update - ProductionUpdateInterface *pu = getProductionUpdateInterface(); - if( pu == NULL ) - break; - // queue the upgrade "research" - pu->queueUpgrade( upgradeT ); - } - return; - case GUI_COMMAND_UNIT_BUILD: - case GUI_COMMAND_DOZER_CONSTRUCT: { - const ThingTemplate *tt = commandButton->getThingTemplate(); - ProductionUpdateInterface *pu = this->getProductionUpdateInterface(); - if (pu && tt) { - pu->queueCreateUnit( tt, pu->requestUniqueUnitID()); - return; - } - break; - } - case GUI_COMMAND_HACK_INTERNET:{ - if( ai ) - { - ai->aiHackInternet( cmdSource ); - return; - } - break; - } - - case GUI_COMMAND_SELL: - TheBuildAssistant->sellObject( this ); - return; - - //Feel free to implement object based command buttons. - case GUI_COMMAND_COMBATDROP: - case GUI_COMMAND_DOZER_CONSTRUCT_CANCEL: - case GUI_COMMAND_CANCEL_UNIT_BUILD: - case GUI_COMMAND_CANCEL_UPGRADE: - case GUI_COMMAND_ATTACK_MOVE: - case GUI_COMMAND_GUARD: - case GUI_COMMAND_GUARD_WITHOUT_PURSUIT: - case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY: - case GUI_COMMAND_WAYPOINTS: - case GUI_COMMAND_EXIT_CONTAINER: - case GUI_COMMAND_EVACUATE: - case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT: - case GUI_COMMAND_BEACON_DELETE: - case GUI_COMMAND_SET_RALLY_POINT: - case GUI_COMMAND_TOGGLE_OVERCHARGE: -#ifdef ALLOW_SURRENDER - case GUI_COMMAND_POW_RETURN_TO_PRISON: -#endif - case GUICOMMANDMODE_HIJACK_VEHICLE: - case GUICOMMANDMODE_CONVERT_TO_CARBOMB: -#ifdef ALLOW_SURRENDER - case GUICOMMANDMODE_PICK_UP_PRISONER: -#endif - default: - break; - } - DEBUG_CRASH( ("WARNING: Script doCommandButton for button %s not implemented. Doing nothing.", commandButton->getName().str()) ); - } -} - -//------------------------------------------------------------------------------------------------- -/** Execute command button ability directed at an object target */ -//------------------------------------------------------------------------------------------------- -void Object::doCommandButtonAtObject( const CommandButton *commandButton, Object *obj, CommandSourceType cmdSource ) -{ - if (isDisabled()) - return; - - AIUpdateInterface *ai = getAIUpdateInterface(); - if( commandButton ) - { - switch( commandButton->getCommandType() ) - { - case GUI_COMMAND_COMBATDROP: - if( ai ) - { - ai->aiCombatDrop( obj, *(obj->getPosition()), cmdSource ); - } - return; - case GUI_COMMAND_SPECIAL_POWER: - { - if( commandButton->getSpecialPowerTemplate() ) - { - CommandOption commandOptions = (CommandOption)(commandButton->getOptions() | COMMAND_FIRED_BY_SCRIPT); - doSpecialPowerAtObject( commandButton->getSpecialPowerTemplate(), obj, commandOptions, cmdSource == CMD_FROM_SCRIPT ); - } - return; - } - - case GUI_COMMAND_STOP: - if( ai ) - { - ai->aiIdle( cmdSource ); - } - return; - - case GUI_COMMAND_FIRE_WEAPON: - if( ai ) - { - if( BitIsSet( commandButton->getOptions(), COMMAND_OPTION_NEED_OBJECT_TARGET ) ) - { - //OBJECT BASED FIRE WEAPON - if( !obj ) - { - break; - } - - if( !commandButton->isValidObjectTarget( this, obj ) ) - { - break; - } - - setWeaponLock( commandButton->getWeaponSlot(), LOCKED_TEMPORARILY ); - - if( BitIsSet( commandButton->getOptions(), ATTACK_OBJECTS_POSITION ) ) - { - //Actually, you know what.... we want to attack the object's location instead. - ai->aiAttackPosition( obj->getPosition(), commandButton->getMaxShotsToFire(), cmdSource ); - } - else - { - ai->aiAttackObject( obj, commandButton->getMaxShotsToFire(), cmdSource ); - } - } - else - { - DEBUG_CRASH( ("WARNING: Script doCommandButtonAtObject for button %s cannot fire weapon at AN OBJECT. Skipping.", commandButton->getName().str()) ); - } - return; - } - break; - - case GUICOMMANDMODE_HIJACK_VEHICLE: - case GUICOMMANDMODE_CONVERT_TO_CARBOMB: - case GUICOMMANDMODE_SABOTAGE_BUILDING: - if( ai ) - { - ai->aiEnter( obj, cmdSource ); - } - return; - - //Feel free to implement object based command buttons. - case GUI_COMMAND_DOZER_CONSTRUCT: - case GUI_COMMAND_DOZER_CONSTRUCT_CANCEL: - case GUI_COMMAND_UNIT_BUILD: - case GUI_COMMAND_CANCEL_UNIT_BUILD: - case GUI_COMMAND_PLAYER_UPGRADE: - case GUI_COMMAND_OBJECT_UPGRADE: - case GUI_COMMAND_CANCEL_UPGRADE: - case GUI_COMMAND_ATTACK_MOVE: - case GUI_COMMAND_GUARD: - case GUI_COMMAND_GUARD_WITHOUT_PURSUIT: - case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY: - case GUI_COMMAND_WAYPOINTS: - case GUI_COMMAND_EXIT_CONTAINER: - case GUI_COMMAND_EVACUATE: - case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT: - case GUI_COMMAND_BEACON_DELETE: - case GUI_COMMAND_SET_RALLY_POINT: - case GUI_COMMAND_SELL: - case GUI_COMMAND_HACK_INTERNET: - case GUI_COMMAND_TOGGLE_OVERCHARGE: - case GUI_COMMAND_SWITCH_WEAPON: - -#ifdef ALLOW_SURRENDER - case GUI_COMMAND_POW_RETURN_TO_PRISON: - case GUICOMMANDMODE_PICK_UP_PRISONER: -#endif - default: - break; - } - DEBUG_CRASH( ("WARNING: Script doCommandButtonAtObject for button %s not implemented. Doing nothing.", commandButton->getName().str()) ); - } -} - -//------------------------------------------------------------------------------------------------- -/** Execute command button ability directed at a location */ -//------------------------------------------------------------------------------------------------- -void Object::doCommandButtonAtPosition( const CommandButton *commandButton, const Coord3D *pos, CommandSourceType cmdSource ) -{ - if (isDisabled()) - return; - - AIUpdateInterface *ai = getAIUpdateInterface(); - if( commandButton ) - { - switch( commandButton->getCommandType() ) - { - case GUI_COMMAND_SPECIAL_POWER: - { - if( commandButton->getSpecialPowerTemplate() ) - { - CommandOption commandOptions = (CommandOption)(commandButton->getOptions() | COMMAND_FIRED_BY_SCRIPT); - doSpecialPowerAtLocation( commandButton->getSpecialPowerTemplate(), pos, INVALID_ANGLE, commandOptions, cmdSource == CMD_FROM_SCRIPT ); - return; - } - break; - } - case GUI_COMMAND_ATTACK_MOVE: - if( ai ) - { - ai->aiAttackMoveToPosition( pos, commandButton->getMaxShotsToFire(), cmdSource ); - return; - } - break; - case GUI_COMMAND_STOP: - if( ai ) - { - ai->aiIdle( cmdSource ); - return; - } - break; - - case GUI_COMMAND_DOZER_CONSTRUCT: - TheBuildAssistant->buildObjectNow( this, commandButton->getThingTemplate(), pos, 0.0f, getControllingPlayer() ); - return; - - case GUI_COMMAND_FIRE_WEAPON: - if( ai ) - { - if( BitIsSet( commandButton->getOptions(), NEED_TARGET_POS ) ) - { - //LOCATION BASED FIRE WEAPON - if( !pos ) - { - break; - } - setWeaponLock( commandButton->getWeaponSlot(), LOCKED_TEMPORARILY ); - ai->aiAttackPosition( pos, commandButton->getMaxShotsToFire(), cmdSource ); - } - else - { - DEBUG_CRASH( ("WARNING: Script doCommandButtonAtPosition for button %s cannot fire weapon at A POSITION. Skipping.", commandButton->getName().str()) ); - } - return; - } - break; - - case GUI_COMMAND_DOZER_CONSTRUCT_CANCEL: - case GUI_COMMAND_UNIT_BUILD: - case GUI_COMMAND_CANCEL_UNIT_BUILD: - case GUI_COMMAND_PLAYER_UPGRADE: - case GUI_COMMAND_OBJECT_UPGRADE: - case GUI_COMMAND_CANCEL_UPGRADE: - case GUI_COMMAND_GUARD: - case GUI_COMMAND_GUARD_WITHOUT_PURSUIT: - case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY: - case GUI_COMMAND_WAYPOINTS: - case GUI_COMMAND_EXIT_CONTAINER: - case GUI_COMMAND_EVACUATE: - case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT: - case GUI_COMMAND_BEACON_DELETE: - case GUI_COMMAND_SET_RALLY_POINT: - case GUI_COMMAND_SELL: - case GUI_COMMAND_HACK_INTERNET: - case GUI_COMMAND_TOGGLE_OVERCHARGE: -#ifdef ALLOW_SURRENDER - case GUI_COMMAND_POW_RETURN_TO_PRISON: -#endif - case GUI_COMMAND_COMBATDROP: - case GUI_COMMAND_SWITCH_WEAPON: - case GUICOMMANDMODE_HIJACK_VEHICLE: - case GUICOMMANDMODE_CONVERT_TO_CARBOMB: -#ifdef ALLOW_SURRENDER - case GUICOMMANDMODE_PICK_UP_PRISONER: -#endif - default: - break; - } - DEBUG_CRASH( ("WARNING: Script doCommandButtonAtPosition for button %s not implemented. Doing nothing.", commandButton->getName().str()) ); - } -} - -//------------------------------------------------------------------------------------------------- -/** Execute command button ability directed at a location */ -//------------------------------------------------------------------------------------------------- -void Object::doCommandButtonUsingWaypoints( const CommandButton *commandButton, const Waypoint *way, CommandSourceType cmdSource ) -{ - if (isDisabled()) - return; - - if( commandButton ) - { - if( !BitIsSet( commandButton->getOptions(), CAN_USE_WAYPOINTS ) ) - { - //Our button doesn't support waypoints. - DEBUG_CRASH( ("WARNING: Script doCommandButtonUsingWaypoints for button %s lacks CAN_USE_WAYPOINTS option. Doing nothing.", commandButton->getName().str()) ); - return; - } - switch( commandButton->getCommandType() ) - { - case GUI_COMMAND_SPECIAL_POWER: - { - if( commandButton->getSpecialPowerTemplate() ) - { - CommandOption commandOptions = (CommandOption)(commandButton->getOptions() | COMMAND_FIRED_BY_SCRIPT); - doSpecialPowerUsingWaypoints( commandButton->getSpecialPowerTemplate(), way, commandOptions, cmdSource == CMD_FROM_SCRIPT ); - return; - } - break; - } - case GUI_COMMAND_ATTACK_MOVE: - case GUI_COMMAND_STOP: - case GUI_COMMAND_DOZER_CONSTRUCT: - case GUI_COMMAND_DOZER_CONSTRUCT_CANCEL: - case GUI_COMMAND_UNIT_BUILD: - case GUI_COMMAND_CANCEL_UNIT_BUILD: - case GUI_COMMAND_PLAYER_UPGRADE: - case GUI_COMMAND_OBJECT_UPGRADE: - case GUI_COMMAND_CANCEL_UPGRADE: - case GUI_COMMAND_GUARD: - case GUI_COMMAND_GUARD_WITHOUT_PURSUIT: - case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY: - case GUI_COMMAND_WAYPOINTS: - case GUI_COMMAND_EXIT_CONTAINER: - case GUI_COMMAND_EVACUATE: - case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT: - case GUI_COMMAND_BEACON_DELETE: - case GUI_COMMAND_SET_RALLY_POINT: - case GUI_COMMAND_SELL: - case GUI_COMMAND_FIRE_WEAPON: - case GUI_COMMAND_HACK_INTERNET: - case GUI_COMMAND_TOGGLE_OVERCHARGE: -#ifdef ALLOW_SURRENDER - case GUI_COMMAND_POW_RETURN_TO_PRISON: -#endif - case GUI_COMMAND_COMBATDROP: - case GUI_COMMAND_SWITCH_WEAPON: - case GUICOMMANDMODE_HIJACK_VEHICLE: - case GUICOMMANDMODE_CONVERT_TO_CARBOMB: -#ifdef ALLOW_SURRENDER - case GUICOMMANDMODE_PICK_UP_PRISONER: -#endif - default: - break; - } - DEBUG_CRASH( ("WARNING: Script doCommandButtonUsingWaypoints for button %s not implemented. Doing nothing.", commandButton->getName().str()) ); - } -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void Object::clearLeechRangeModeForAllWeapons() -{ - m_weaponSet.clearLeechRangeModeForAllWeapons(); -} - -// ------------------------------------------------------------------------------------------------ -/** Search our update modules for a production update interface and return it if one is found */ -// ------------------------------------------------------------------------------------------------ -ProductionUpdateInterface* Object::getProductionUpdateInterface( void ) -{ - ProductionUpdateInterface *pui; - - // tell our update modules that we intend to do this special power. - for( BehaviorModule** u = m_behaviors; *u; ++u ) - { - - pui = (*u)->getProductionUpdateInterface(); - if( pui ) - return pui; - - } // end for - - return NULL; - -} // end getProductionUpdateInterface - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -DockUpdateInterface *Object::getDockUpdateInterface( void ) -{ - DockUpdateInterface *dock = NULL; - - for( BehaviorModule **u = m_behaviors; *u; ++u ) - { - if( (dock = (*u)->getDockUpdateInterface()) != NULL ) - return dock; - } - - return NULL; - -} // end getDockUpdateInterface - -// ------------------------------------------------------------------------------------------------ -// Search our special power modules for a specific one. -// ------------------------------------------------------------------------------------------------ -SpecialPowerModuleInterface* Object::findSpecialPowerModuleInterface( SpecialPowerType type ) const -{ - for (BehaviorModule** m = m_behaviors; *m; ++m) - { - SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); - if (!sp) - continue; - - const SpecialPowerTemplate *spTemplate = sp->getSpecialPowerTemplate(); - if (spTemplate && spTemplate->getSpecialPowerType() == type || type == SPECIAL_INVALID ) - { - return sp; - } - } - return NULL; -} - -// ------------------------------------------------------------------------------------------------ -// Search our special power modules for the first occurrence of a shortcut special. -// ------------------------------------------------------------------------------------------------ -SpecialPowerModuleInterface* Object::findAnyShortcutSpecialPowerModuleInterface() const -{ - for( BehaviorModule** m = m_behaviors; *m; ++m ) - { - SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); - if (!sp) - continue; - - const SpecialPowerTemplate *spTemplate = sp->getSpecialPowerTemplate(); - if( spTemplate && spTemplate->isShortcutPower() ) - { - return sp; - } - } - return NULL; -} - -// ------------------------------------------------------------------------------------------------ -/** Get spawn behavior interface from object */ -// ------------------------------------------------------------------------------------------------ -SpawnBehaviorInterface* Object::getSpawnBehaviorInterface() const -{ - for (BehaviorModule** m = m_behaviors; *m; ++m) - { - SpawnBehaviorInterface *sbi = (*m)->getSpawnBehaviorInterface(); - if( sbi ) - { - return sbi; - } - } - return NULL; -} // end getSpawnBehaviorInterfaceFromObject - -// ------------------------------------------------------------------------------------------------ -ProjectileUpdateInterface* Object::getProjectileUpdateInterface() const -{ - for (BehaviorModule** m = m_behaviors; *m; ++m) - { - ProjectileUpdateInterface *pui = (*m)->getProjectileUpdateInterface(); - if( pui ) - { - return pui; - } - } - return NULL; -} - -// ------------------------------------------------------------------------------------------------ -// Simply find the special power module that is currently allowing plotting of positions to target. -// ------------------------------------------------------------------------------------------------ -SpecialPowerUpdateInterface* Object::findSpecialPowerWithOverridableDestinationActive( SpecialPowerType type ) const -{ - for( BehaviorModule** u = m_behaviors; *u; ++u ) - { - SpecialPowerUpdateInterface *spInterface = (*u)->getSpecialPowerUpdateInterface(); - if( spInterface ) - { - if( spInterface->doesSpecialPowerHaveOverridableDestinationActive() ) - { - return spInterface; - } - } - } // end for - return NULL; -} - -// ------------------------------------------------------------------------------------------------ -// Simply find the special power module that is potentially allowed to plot positions to target. -// ------------------------------------------------------------------------------------------------ -SpecialPowerUpdateInterface* Object::findSpecialPowerWithOverridableDestination( SpecialPowerType type ) const -{ - for( BehaviorModule** u = m_behaviors; *u; ++u ) - { - SpecialPowerUpdateInterface *spInterface = (*u)->getSpecialPowerUpdateInterface(); - if( spInterface ) - { - if( spInterface->doesSpecialPowerHaveOverridableDestination() ) - { - return spInterface; - } - } - } // end for - return NULL; -} - - -// ------------------------------------------------------------------------------------------------ -// Search our special ability updates for a specific one. -// ------------------------------------------------------------------------------------------------ -SpecialAbilityUpdate* Object::findSpecialAbilityUpdate( SpecialPowerType type ) const -{ - for( BehaviorModule** u = m_behaviors; *u; ++u ) - { - SpecialPowerUpdateInterface *spInterface = (*u)->getSpecialPowerUpdateInterface(); - if( spInterface && spInterface->isSpecialAbility() ) - { - SpecialAbilityUpdate *spUpdate = (SpecialAbilityUpdate*)spInterface; - if( spUpdate->getSpecialPowerType() == type ) - { - return spUpdate; - } - } - } // end for - - return NULL; -} - -// ------------------------------------------------------------------------------------------------ -SpecialPowerCompletionDie* Object::findSpecialPowerCompletionDie() const -{ - static NameKeyType key_SpecialPowerCompletionDie = NAMEKEY("SpecialPowerCompletionDie"); - return (SpecialPowerCompletionDie*)findModule(key_SpecialPowerCompletionDie); -} - -// ------------------------------------------------------------------------------------------------ -Int Object::getNumConsecutiveShotsFiredAtTarget( const Object *victim ) const -{ - return m_firingTracker ? m_firingTracker->getNumConsecutiveShotsAtVictim( victim ) : 0; -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -Bool Object::getSingleLogicalBonePosition(const char* boneName, Coord3D* position, Matrix3D* transform) const -{ - if (m_drawable && m_drawable->getPristineBonePositions( boneName, 0, position, transform, 1 ) == 1 ) - { - m_drawable->convertBonePosToWorldPos( position, transform, position, transform ); - return true; - } - else - { - if (position) - *position = *getPosition(); - if (transform) - *transform = *getTransformMatrix(); - return false; - } -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -Bool Object::getSingleLogicalBonePositionOnTurret( WhichTurretType whichTurret, const char* boneName, Coord3D* position, Matrix3D* transform ) const -{ - Coord3D turretPosition; - Coord3D bonePosition; - if( getDrawable() == NULL || getAI() == NULL ) - return FALSE; - - // We need to find the TurretBone's pristine position. - getDrawable()->getProjectileLaunchOffset( PRIMARY_WEAPON, 1, NULL, whichTurret, &turretPosition, NULL ); - // And the required bone's pristine position - if( getDrawable()->getPristineBonePositions(boneName, 0, &bonePosition, NULL, 1) != 1 ) - return FALSE; - //Then we mojo the Logic position of the required bone like Missile firing does. Using the logic twist of the turret - Real turretRotation; - getAI()->getTurretRotAndPitch( whichTurret, &turretRotation, NULL ); - - Matrix3D boneOffset(TRUE);// This will be from the turret to the requested bone - -// Vector3 bonePositionVector( bonePosition.x - turretPosition.x, -// bonePosition.y - turretPosition.y, -// bonePosition.z - turretPosition.z ); - Vector3 bonePositionVector( bonePosition.x, - bonePosition.y, - bonePosition.z ); - boneOffset.Translate(bonePositionVector); - - Matrix3D turnAdjustment(TRUE);// this is the turret twist to be applied to the final answer - - turnAdjustment.Translate( turretPosition.x, turretPosition.y, turretPosition.z ); - turnAdjustment.In_Place_Pre_Rotate_Z(turretRotation); - turnAdjustment.Translate( -turretPosition.x, -turretPosition.y, -turretPosition.z ); - - Matrix3D boneLogicTransform; - boneLogicTransform.mul( turnAdjustment, boneOffset ); - - Matrix3D worldTransform; - convertBonePosToWorldPos(NULL, &boneLogicTransform, NULL, &worldTransform); - - Vector3 tmp = worldTransform.Get_Translation(); - Coord3D worldPos; - worldPos.x = tmp.X; - worldPos.y = tmp.Y; - worldPos.z = tmp.Z; - - if( position ) - *position = worldPos; - if( transform ) - *transform = worldTransform; - - return TRUE; -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -Int Object::getMultiLogicalBonePosition(const char* boneNamePrefix, Int maxBones, - Coord3D* positions, Matrix3D* transforms, - Bool convertToWorld ) const -{ - Int count; - if (m_drawable && (count = m_drawable->getPristineBonePositions( boneNamePrefix, 1, positions, transforms, maxBones )) > 0 ) - { - if( convertToWorld ) - { - for (Int i = 0; i < count; ++i) - m_drawable->convertBonePosToWorldPos( positions ? &positions[i] : NULL, transforms ? &transforms[i] : NULL, positions ? &positions[i] : NULL, transforms ? &transforms[i] : NULL ); - } - return count; - } - else - { - return 0; - } -} - -//============================================================================= -const AsciiString& Object::getCommandSetString() const -{ - if (m_commandSetStringOverride.isNotEmpty()) - return m_commandSetStringOverride; - - return getTemplate()->friend_getCommandSetString(); -} - -//============================================================================= -Bool Object::canProduceUpgrade( const UpgradeTemplate *upgrade ) -{ - // We need to have the button to make the upgrade. CommandSets are a weird Logic/Client hybrid. - const CommandSet *set = TheControlBar->findCommandSet(getCommandSetString()); - - for( Int buttonIndex = 0; buttonIndex < MAX_COMMANDS_PER_SET; buttonIndex++ ) - { - const CommandButton *button = set->getCommandButton(buttonIndex); - if( button && button->getUpgradeTemplate() && (button->getUpgradeTemplate() == upgrade) ) - return TRUE; // getUpgradeTemplate only returns something if it is actually an upgrade - } - - return FALSE;// Cheatin' punk. -} - -//============================================================================= -// Object::defect, and related methods = -//============================================================================= -void Object::defect( Team* newTeam, UnsignedInt detectionTime ) -{ - if ( isContained() ) //@todo (KRIS?) make contained units unselectable, until then... lorenzen - { - return; - } - - Player *player = getControllingPlayer(); - if ( !player ) - return; - - Team* myTeam = player->getDefaultTeam(); - if ( myTeam == newTeam ) // can't defect from my own team, that would be silly - return; - - // things that are under construction, or sold, cannot defect. - if (testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) || - testStatus(OBJECT_STATUS_SOLD)) - { - return; - } - - // Before switch //////////////////////////////////////// - - //Design says: - ProductionUpdateInterface *production = getProductionUpdateInterface(); - if ( production ) - { - production->cancelAndRefundAllProduction(); - } - - // pop it up on the radar, so as to warn those who care - // do this first, since after setTeam() the infiltrator - // becomes the controllingplayer, not me - - // But don't do this is if the new team is not a real team. "'Enemy' infiltration" wouldn't make - // sense, and we are probably just reverting a cave or something. - if( friend_getRadarData() && newTeam->getControllingPlayer()->isPlayableSide() && myTeam->getControllingPlayer()->isPlayableSide()) - { - TheRadar->tryInfiltrationEvent( this ); - } - - friend_setUndetectedDefector( detectionTime > 0 ); - - if (m_defectionHelper) - m_defectionHelper->startDefectionTimer(detectionTime); - - // Switch //////////////////////////////////////// - setTeam( newTeam ); - - // After switch //////////////////////////////////////// - - AIUpdateInterface *ai = getAI(); - - handlePartitionCellMaintenance();// to clear the shoud for my new master - - if ( ai ) - { - ai->aiIdle( CMD_FROM_AI ); - } - - // Play our sound indicating we've been defected. (weird verbage, but true.) - AudioEventRTS voiceDefect = *getTemplate()->getVoiceDefect(); - voiceDefect.setObjectID(getID()); - TheAudio->addAudioEvent(&voiceDefect); - - //make the new recruit the only selected thing, awaiting new command to move, attack, etc... - Drawable *dr = getDrawable(); - if (dr) - { - dr->flashAsSelected(); //This is the first of several flashes which get cue'd by doDefectorUpdateStuff() - AudioEventRTS defectorTimerSound = TheAudio->getMiscAudio()->m_defectorTimerTickSound; - defectorTimerSound.setObjectID( getID() ); - TheAudio->addAudioEvent(&defectorTimerSound); - } - - ContainModuleInterface *ct = getContain(); - if( ct && ct->isKickOutOnCapture() ) - { - // Caves really really don't want to do this. - ct->removeAllContained( TRUE ); - } - - // if it has parking places, defect anything parked there. - for (BehaviorModule** i = getBehaviorModules(); *i; ++i) - { - ParkingPlaceBehaviorInterface* pp = (*i)->getParkingPlaceBehaviorInterface(); - if (pp) - { - pp->defectAllParkedUnits(newTeam, detectionTime); - break; - } - } - - // defect any mines that are owned by this structure, right now. - // unfortunately, structures don't keep list of mines they own, so we must do - // this the hard way :-( [fortunately, this doens't happen very often, so this - // is probably an acceptable, if icky, solution.] (srj) - for (Object* mine = TheGameLogic->getFirstObject(); mine; mine = mine->getNextObject()) - { - if (mine->isKindOf(KINDOF_MINE)) - { - if (mine->getProducerID() == this->getID()) - { - mine->setTeam(newTeam); - } - } - } - -} - -//============================================================================= -// Object::goInvulnerable -//============================================================================= -void Object::goInvulnerable( UnsignedInt time ) -{ - const Bool WITHOUT_DEFECTOR_FX = FALSE; - - - friend_setUndetectedDefector( time > 0 ); - - if (m_defectionHelper) - m_defectionHelper->startDefectionTimer(time, WITHOUT_DEFECTOR_FX); - -} - -// ------------------------------------------------------------------------------------------------ -/** Return the radar priority for this object type */ -// ------------------------------------------------------------------------------------------------ -RadarPriorityType Object::getRadarPriority( void ) const -{ - RadarPriorityType priority = RADAR_PRIORITY_INVALID; - - // first, get the priority at the thing template level - priority = getTemplate()->getDefaultRadarPriority(); - - // - // there are some objects that we want to show up on the radar when they have - // certain properties ... here we will check for those properties unless the INI - // setting of "not on radar" has been manually entered which explicitly forbids an - // object from being on the radar ... by default objects get an "invalid" priority - // on the radar and this means that we are free to decide one here if we want - // - if( priority == RADAR_PRIORITY_INVALID ) - { - - // objects that are "garrisonable" show up on the radar - ContainModuleInterface *cmi = getContain(); - if( cmi && cmi->isGarrisonable() ) - priority = RADAR_PRIORITY_STRUCTURE; - - // objects that are "capturable" show up on the radar - if( isKindOf( KINDOF_CAPTURABLE ) ) - priority = RADAR_PRIORITY_STRUCTURE; - - - } // end if - - // Carbombs will show up as units regardless of their default priority - if ( testStatus( OBJECT_STATUS_IS_CARBOMB ) ) - priority = RADAR_PRIORITY_UNIT; - - - // return the priority we're going to use - return priority; - -} // end getRadarPriority - -// ------------------------------------------------------------------------------------------------ -AIGroup *Object::getGroup(void) -{ - return m_group; -} - -//------------------------------------------------------------------------------------------------- -void Object::enterGroup( AIGroup *group ) -{ -// DEBUG_LOG(("***AIGROUP %x involved in enterGroup on %x\n", group, this)); - // if we are in another group, remove ourselves from it first - leaveGroup(); - - m_group = group; -} - -//------------------------------------------------------------------------------------------------- -void Object::leaveGroup( void ) -{ -// DEBUG_LOG(("***AIGROUP %x involved in leaveGroup on %x\n", m_group, this)); - // if we are in a group, remove ourselves from it - if (m_group) - { - // to avoid recursion, set m_group to NULL before removing - AIGroup *group = m_group; - m_group = NULL; - group->remove( this ); - } -} - -//------------------------------------------------------------------------------------------------- -Real Object::getCarrierDeckHeight() const -{ - Object *producer = TheGameLogic->findObjectByID( getProducerID() ); - if( producer ) - { - // Find a parking place behavior. - for( BehaviorModule** i = producer->getBehaviorModules(); *i; ++i ) - { - ParkingPlaceBehaviorInterface* pp = (*i)->getParkingPlaceBehaviorInterface(); - if( pp ) - { - return pp->getLandingDeckHeightOffset(); - } - } - } - return 0.0f; -} - -//------------------------------------------------------------------------------------------------- -CountermeasuresBehaviorInterface* Object::getCountermeasuresBehaviorInterface() -{ - for( BehaviorModule** i = getBehaviorModules(); *i; ++i ) - { - CountermeasuresBehaviorInterface* cbi = (*i)->getCountermeasuresBehaviorInterface(); - if( cbi ) - { - return cbi; - } - } - return NULL; -} - -//------------------------------------------------------------------------------------------------- -const CountermeasuresBehaviorInterface* Object::getCountermeasuresBehaviorInterface() const -{ - for( BehaviorModule** i = getBehaviorModules(); *i; ++i ) - { - const CountermeasuresBehaviorInterface* cbi = (*i)->getCountermeasuresBehaviorInterface(); - if( cbi ) - { - return cbi; - } - } - return NULL; -} - -//------------------------------------------------------------------------------------------------- -Bool Object::hasCountermeasures() const -{ - const CountermeasuresBehaviorInterface* cbi = getCountermeasuresBehaviorInterface(); - if( cbi && cbi->isActive() ) - { - return TRUE; - } - return FALSE; -} - -//------------------------------------------------------------------------------------------------- -void Object::reportMissileForCountermeasures( Object *missile ) -{ - for( BehaviorModule** i = getBehaviorModules(); *i; ++i ) - { - CountermeasuresBehaviorInterface* cbi = (*i)->getCountermeasuresBehaviorInterface(); - if( cbi ) - { - cbi->reportMissileForCountermeasures( missile ); - } - } -} - -//------------------------------------------------------------------------------------------------- -ObjectID Object::calculateCountermeasureToDivertTo( const Object& victim ) -{ - AIUpdateInterface *ai = getAI(); - if( ai ) - { - for( BehaviorModule** i = victim.getBehaviorModules(); *i; ++i ) - { - CountermeasuresBehaviorInterface* cbi = (*i)->getCountermeasuresBehaviorInterface(); - if( cbi ) - { - ObjectID decoyID = cbi->calculateCountermeasureToDivertTo( victim ); - return decoyID; - } - } - } - return INVALID_ID; -} +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE Object.cpp //////////////////////////////////////////////////////////////////////////////// +// Simple base object +// Author: Michael S. Booth, October 2000 +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine +#define DEFINE_WEAPONCONDITIONMAP +#include "Common/BitFlagsIO.h" +#include "Common/BuildAssistant.h" +#include "Common/Dict.h" +#include "Common/GameCommon.h" +#include "Common/GameEngine.h" +#include "Common/GameState.h" +#include "Common/ModuleFactory.h" +#include "Common/Player.h" +#include "Common/PlayerList.h" +#include "Common/Radar.h" +#include "Common/SpecialPower.h" +#include "Common/Team.h" +#include "Common/ThingFactory.h" +#include "Common/ThingTemplate.h" +#include "Common/Upgrade.h" +#include "Common/WellKnownKeys.h" +#include "Common/Xfer.h" +#include "Common/XferCRC.h" +#include "Common/PerfTimer.h" + +#include "GameClient/Anim2D.h" +#include "GameClient/ControlBar.h" +#include "GameClient/Drawable.h" +#include "GameClient/Eva.h" +#include "GameClient/GameClient.h" +#include "GameClient/InGameUI.h" + +#include "GameLogic/AI.h" +#include "GameLogic/AIPathfind.h" +#include "GameLogic/ExperienceTracker.h" +#include "GameLogic/FiringTracker.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/Locomotor.h" + +#include "GameLogic/Module/AIUpdate.h" +#include "GameLogic/Module/AutoHealBehavior.h" +#include "GameLogic/Module/BehaviorModule.h" +#include "GameLogic/Module/BodyModule.h" +#include "GameLogic/Module/CollideModule.h" +#include "GameLogic/Module/ContainModule.h" +#include "GameLogic/Module/CountermeasuresBehavior.h" +#include "GameLogic/Module/CreateModule.h" +#include "GameLogic/Module/DamageModule.h" +#include "GameLogic/Module/DeletionUpdate.h" +#include "GameLogic/Module/DestroyModule.h" +#include "GameLogic/Module/DieModule.h" +#include "GameLogic/Module/DozerAIUpdate.h" +#include "GameLogic/Module/ObjectDefectionHelper.h" +#include "GameLogic/Module/ObjectRepulsorHelper.h" +#include "GameLogic/Module/ObjectSMCHelper.h" +#include "GameLogic/Module/ObjectWeaponStatusHelper.h" +#include "GameLogic/Module/OverchargeBehavior.h" +#include "GameLogic/Module/PhysicsUpdate.h" +#include "GameLogic/Module/PowerPlantUpgrade.h" +#include "GameLogic/Module/ProductionUpdate.h" +#include "GameLogic/Module/RadarUpgrade.h" +#include "GameLogic/Module/RebuildHoleBehavior.h" +#include "GameLogic/Module/SpawnBehavior.h" +#include "GameLogic/Module/SpecialPowerModule.h" +#include "GameLogic/Module/SpecialAbilityUpdate.h" +#include "GameLogic/Module/StatusDamageHelper.h" +#include "GameLogic/Module/StickyBombUpdate.h" +#include "GameLogic/Module/SubdualDamageHelper.h" +#include "GameLogic/Module/ChronoDamageHelper.h" +#include "GameLogic/Module/TempWeaponBonusHelper.h" +#include "GameLogic/Module/ToppleUpdate.h" +#include "GameLogic/Module/UpdateModule.h" +#include "GameLogic/Module/UpgradeModule.h" + +#include "GameLogic/Object.h" +#include "GameLogic/PartitionManager.h" +#include "GameLogic/PolygonTrigger.h" +#include "GameLogic/ScriptEngine.h" +#include "GameLogic/Weapon.h" +#include "GameLogic/WeaponSet.h" +#include "GameLogic/Module/RadarUpdate.h" +#include "GameLogic/Module/PowerPlantUpdate.h" + +#include "Common/CRCDebug.h" +#include "Common/MiscAudio.h" +#include "Common/AudioEventInfo.h" +#include "Common/DynamicAudioEventInfo.h" + +#ifdef RTS_INTERNAL +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +#ifdef DEBUG_OBJECT_ID_EXISTS +ObjectID TheObjectIDToDebug = INVALID_ID; +#endif + +// ------------------------------------------------------------------------------------------------ +static const ModelConditionFlags s_allWeaponFireFlags[WEAPONSLOT_COUNT] = +{ + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_A, + MODELCONDITION_BETWEEN_FIRING_SHOTS_A, + MODELCONDITION_RELOADING_A, + MODELCONDITION_PREATTACK_A, + MODELCONDITION_USING_WEAPON_A + ), + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_B, + MODELCONDITION_BETWEEN_FIRING_SHOTS_B, + MODELCONDITION_RELOADING_B, + MODELCONDITION_PREATTACK_B, + MODELCONDITION_USING_WEAPON_B + ), + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_C, + MODELCONDITION_BETWEEN_FIRING_SHOTS_C, + MODELCONDITION_RELOADING_C, + MODELCONDITION_PREATTACK_C, + MODELCONDITION_USING_WEAPON_C + ), + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_D, + MODELCONDITION_BETWEEN_FIRING_SHOTS_D, + MODELCONDITION_RELOADING_D, + MODELCONDITION_PREATTACK_D, + MODELCONDITION_USING_WEAPON_D + ), + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_E, + MODELCONDITION_BETWEEN_FIRING_SHOTS_E, + MODELCONDITION_RELOADING_E, + MODELCONDITION_PREATTACK_E, + MODELCONDITION_USING_WEAPON_E + ), + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_F, + MODELCONDITION_BETWEEN_FIRING_SHOTS_F, + MODELCONDITION_RELOADING_F, + MODELCONDITION_PREATTACK_F, + MODELCONDITION_USING_WEAPON_F + ), + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_G, + MODELCONDITION_BETWEEN_FIRING_SHOTS_G, + MODELCONDITION_RELOADING_G, + MODELCONDITION_PREATTACK_G, + MODELCONDITION_USING_WEAPON_G + ), + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_H, + MODELCONDITION_BETWEEN_FIRING_SHOTS_H, + MODELCONDITION_RELOADING_H, + MODELCONDITION_PREATTACK_H, + MODELCONDITION_USING_WEAPON_H + ) +}; + +//------------------------------------------------------------------------------------------------- +extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color); + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +AsciiString DebugDescribeObject(const Object *obj) +{ + if (!obj) + return ""; + + AsciiString ret; + + if (obj->getName().isNotEmpty()) + { + ret.format("Object %d (%s) [%s, owned by player %d (%ls)]", + obj->getID(), obj->getName().str(), obj->getTemplate()->getName().str(), + obj->getControllingPlayer()->getPlayerIndex(), + obj->getControllingPlayer()->getPlayerDisplayName().str()); + } + else + { + ret.format("Object %d [%s, owned by player %d (%ls)]", + obj->getID(), obj->getTemplate()->getName().str(), + obj->getControllingPlayer()->getPlayerIndex(), + obj->getControllingPlayer()->getPlayerDisplayName().str()); + } + + return ret; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Object::Object( const ThingTemplate *tt, const ObjectStatusMaskType &objectStatusMask, Team *team ) : + Thing(tt), + m_indicatorColor(0), + m_ai(NULL), + m_physics(NULL), + m_geometryInfo(tt->getTemplateGeometryInfo()), + m_containedBy(NULL), + m_xferContainedByID(INVALID_ID), + m_containedByFrame(0), + m_behaviors(NULL), + m_body(NULL), + m_contain(NULL), + m_stealth(NULL), + m_partitionData(NULL), + m_radarData(NULL), + m_drawable(NULL), + m_next(NULL), + m_prev(NULL), + m_team(NULL), + m_experienceTracker(NULL), + m_firingTracker(NULL), + m_repulsorHelper(NULL), + m_statusDamageHelper(NULL), + m_tempWeaponBonusHelper(NULL), + m_subdualDamageHelper(NULL), + m_chronoDamageHelper(NULL), + m_smcHelper(NULL), + m_wsHelper(NULL), + m_defectionHelper(NULL), + m_partitionLastLook(NULL), + m_partitionRevealAllLastLook(NULL), + m_partitionLastShroud(NULL), + m_partitionLastThreat(NULL), + m_partitionLastValue(NULL), + m_smcUntil(NEVER), + m_privateStatus(0), + m_formationID(NO_FORMATION_ID), + m_isReceivingDifficultyBonus(FALSE), + m_singleUseCommandUsed(FALSE), + m_scriptStatus(0), + m_enteredOrExitedFrame(0), + m_visionSpiedMask (PLAYERMASK_NONE), + m_numTriggerAreasActive(0) +{ +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + m_hasDiedAlready = false; +#endif + //Modules have not been created yet! + m_modulesReady = false; + + // Force the thing template to use the most overridden version of itself - jkmcd + // Note that after this, the object will be using m_template, which forces the usage of the + // most overridden version of tt, so this is okay. + tt = (const ThingTemplate *) tt->getFinalOverride(); + + Int i, modIdx; + AsciiString modName; + + //Added By Sadullah Nader + //Initializations inserted + m_formationOffset.x = m_formationOffset.y = 0.0f; + m_iPos.zero(); + // + for (i = 0; i < MAX_PLAYER_COUNT; ++i) + { + m_visionSpiedBy[i] = 0; + } + + for( i = 0; i < DISABLED_COUNT; i++ ) + { + m_disabledTillFrame[ i ] = NEVER; + } + + m_weaponBonusCondition = 0; + m_curWeaponSetFlags.clear(); + + // sanity + if( TheGameLogic == NULL || tt == NULL ) + { + + assert( 0 ); + return; + + } // end if + + // Object's set of these persist for the life of the object. + m_partitionLastLook = newInstance(SightingInfo); + m_partitionLastLook->reset(); + m_partitionRevealAllLastLook = newInstance(SightingInfo); + m_partitionRevealAllLastLook->reset(); + m_partitionLastShroud = newInstance(SightingInfo); + m_partitionLastShroud->reset(); + m_partitionLastThreat = newInstance(SightingInfo); + m_partitionLastThreat->reset(); + m_partitionLastValue = newInstance(SightingInfo); + m_partitionLastValue->reset(); + + // must set ID to zero, since some of these set methods + // will cause network messages to be sent + // which use this ID. + m_id = INVALID_ID; + m_producerID = INVALID_ID; + m_builderID = INVALID_ID; + + m_status = objectStatusMask; + m_layer = LAYER_GROUND; + + m_group = NULL; + + m_constructionPercent = CONSTRUCTION_COMPLETE; // complete by default + + m_visionRange = tt->friend_calcVisionRange(); + m_shroudClearingRange = tt->friend_calcShroudClearingRange(); + if( m_shroudClearingRange == -1.0f ) + m_shroudClearingRange = m_visionRange;// Backwards compatible, and perfectly logical default to assign + m_shroudRange = 0.0f; + + m_singleUseCommandUsed = false; + + // assign unique object id + setID( TheGameLogic->allocateObjectID() ); + + // + // allocate any modules we need to, we should keep + // this at or near the end of the drawable construction so that we have + // all the valid data about the thing when we create the module + // + Int totalModules = tt->getBehaviorModuleInfo().getCount() + NUM_SLEEP_HELPERS; // need to take into account all the helper modules + + // allocate the publicModule arrays +// pool[]ify + m_behaviors = MSGNEW("ModulePtrs") BehaviorModule*[totalModules + 1]; + BehaviorModule** curB = m_behaviors; + const ModuleInfo& mi = tt->getBehaviorModuleInfo(); + + // set m_team to null before the first call, to avoid naughtiness... + // If no team is specified in the constructor, then assign the object + // to the neutral team. + setTeam(team ? team : ThePlayerList->getNeutralPlayer()->getDefaultTeam()); + + // the helpers are done first -- even before Behaviors! -- in case a module needs + // to call something that uses them. + static const NameKeyType smcHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_SMCHelper" ); + static ObjectSMCHelperModuleData smcModuleData; + smcModuleData.setModuleTagNameKey( smcHelperModuleDataTagNameKey ); + m_smcHelper = newInstance(ObjectSMCHelper)(this, &smcModuleData); + *curB++ = m_smcHelper; + + //Inactive bodies can't take special damage since they can't take damage + Bool isInactiveBody = FALSE; + for( Int infoIndex = 0; infoIndex < mi.getCount(); ++infoIndex ) + { + modName = mi.getNthName(infoIndex); + if (modName.isEmpty()) + continue; + + if( modName.compare("InactiveBody") == 0 ) + { + isInactiveBody = TRUE; + break; + } + } + + if( !isInactiveBody ) + { + static const NameKeyType statusHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_StatusDamageHelper" ); + static StatusDamageHelperModuleData statusModuleData; + statusModuleData.setModuleTagNameKey( statusHelperModuleDataTagNameKey ); + m_statusDamageHelper = newInstance(StatusDamageHelper)(this, &statusModuleData); + *curB++ = m_statusDamageHelper; + + static const NameKeyType subdualHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_SubdualDamageHelper" ); + static SubdualDamageHelperModuleData subdualModuleData; + subdualModuleData.setModuleTagNameKey( subdualHelperModuleDataTagNameKey ); + m_subdualDamageHelper = newInstance(SubdualDamageHelper)(this, &subdualModuleData); + *curB++ = m_subdualDamageHelper; + + static const NameKeyType chronoHelperModuleDataTagNameKey = NAMEKEY("ModuleTag_ChronoDamageHelper"); + static ChronoDamageHelperModuleData chronoModuleData; + chronoModuleData.setModuleTagNameKey(chronoHelperModuleDataTagNameKey); + m_chronoDamageHelper = newInstance(ChronoDamageHelper)(this, &chronoModuleData); + *curB++ = m_chronoDamageHelper; + } + + if (TheAI != NULL + && TheAI->getAiData()->m_enableRepulsors + && isKindOf(KINDOF_CAN_BE_REPULSED)) + { + // if we can ever be a temporary-repulsor, make a repulsor helper. (srj) + static const NameKeyType repulsorHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_RepulsorHelper" ); + static ObjectRepulsorHelperModuleData repulsorModuleData; + repulsorModuleData.setModuleTagNameKey( repulsorHelperModuleDataTagNameKey ); + m_repulsorHelper = newInstance(ObjectRepulsorHelper)(this, &repulsorModuleData); + *curB++ = m_repulsorHelper; + } + + /** @todo srj -- figure out how to create this only on demand. + currently we don't have a good way to add/remove update modules from + an object on-the-fly, so we fake it here, and just skip the creation + if it is impossible for this object to ever defect... */ + + // shrubbery cannot defect. no, really. + if (!tt->isKindOf(KINDOF_SHRUBBERY)) + { + static const NameKeyType defectionModuleDataTagNameKey = NAMEKEY( "ModuleTag_DefectionHelper" ); + static ObjectDefectionHelperModuleData defectionModuleData; + defectionModuleData.setModuleTagNameKey( defectionModuleDataTagNameKey ); + m_defectionHelper = newInstance(ObjectDefectionHelper)(this, &defectionModuleData); + *curB++ = m_defectionHelper; + } + + if (tt->canPossiblyHaveAnyWeapon()) + { + // we only need a firingtracker and wshelper if we can possibly have a weapon. + static const NameKeyType weaponStatusModuleDataTagNameKey = NAMEKEY( "ModuleTag_WeaponStatusHelper" ); + static ObjectWeaponStatusHelperModuleData weaponStatusModuleData; + weaponStatusModuleData.setModuleTagNameKey( weaponStatusModuleDataTagNameKey ); + m_wsHelper = newInstance(ObjectWeaponStatusHelper)(this, &weaponStatusModuleData); + *curB++ = m_wsHelper; + + static const NameKeyType firingTrackerModuleDataTagNameKey = NAMEKEY( "ModuleTag_FiringTrackerHelper" ); + static FiringTrackerModuleData firingTrackerModuleData; + firingTrackerModuleData.setModuleTagNameKey( firingTrackerModuleDataTagNameKey ); + m_firingTracker = newInstance(FiringTracker)(this, &firingTrackerModuleData); + *curB++ = m_firingTracker; + + static const NameKeyType tempWeaponBonusHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_TempWeaponBonusHelper" ); + static TempWeaponBonusHelperModuleData tempWeaponBonusModuleData; + tempWeaponBonusModuleData.setModuleTagNameKey( tempWeaponBonusHelperModuleDataTagNameKey ); + m_tempWeaponBonusHelper = newInstance(TempWeaponBonusHelper)(this, &tempWeaponBonusModuleData); + *curB++ = m_tempWeaponBonusHelper; + } + + // behaviors are always done first, so they get into the publicModule arrays + // before anything else. + for (modIdx = 0; modIdx < mi.getCount(); ++modIdx) + { + modName = mi.getNthName(modIdx); + if (modName.isEmpty()) + continue; + + BehaviorModule* newMod = (BehaviorModule*)TheModuleFactory->newModule(this, modName, mi.getNthData(modIdx), MODULETYPE_BEHAVIOR); + *curB++ = newMod; + + BodyModuleInterface* body = newMod->getBody(); + if (body) + { + DEBUG_ASSERTCRASH(m_body == NULL, ("Duplicate bodies")); + m_body = body; + } + + ContainModuleInterface* contain = newMod->getContain(); + if (contain) + { + DEBUG_ASSERTCRASH(m_contain == NULL, ("Duplicate containers")); + m_contain = contain; + } + + StealthUpdate* stealth = (StealthUpdate*)newMod->getStealth(); + if ( stealth ) + { + DEBUG_ASSERTCRASH( m_stealth == NULL, ("DuplicateStealthUpdates!") ); + m_stealth = stealth; + } + + + AIUpdateInterface* ai = newMod->getAIUpdateInterface(); + if (ai) + { + if( m_ai ) + { + DEBUG_ASSERTCRASH( m_ai == NULL, ("%s has more than one AI module. This is illegal!\n", getTemplate()->getName().str()) ); + } + m_ai = ai; + } + + static NameKeyType key_PhysicsUpdate = NAMEKEY("PhysicsBehavior"); + if (newMod->getModuleNameKey() == key_PhysicsUpdate) + { + DEBUG_ASSERTCRASH(m_physics == NULL, ("You should never have more than one Physics module (%s)\n",getTemplate()->getName().str())); + m_physics = (PhysicsBehavior*)newMod; + } + } + + *curB = NULL; + + AIUpdateInterface *ai = getAIUpdateInterface(); + if (ai) { + ai->setAttitude(getTeam()->getPrototype()->getTemplateInfo()->m_initialTeamAttitude); + if (m_team && m_team->getPrototype() && m_team->getPrototype()->getAttackPriorityName().isNotEmpty()) { + AsciiString name = m_team->getPrototype()->getAttackPriorityName(); + const AttackPriorityInfo *info = TheScriptEngine->getAttackInfo(name); + if (info && info->getName().isNotEmpty()) { + ai->setAttackInfo(info); + } + } + } + + // allocate experience tracker + m_experienceTracker = newInstance(ExperienceTracker)(this); + + // If a valid team has been assigned me, then I have a Player I can ask about my starting level + const Player* controller = getControllingPlayer(); + m_experienceTracker->setVeterancyLevel( controller->getProductionVeterancyLevel( getTemplate()->getName() ) ); + + /// allow for inter-Module resolution + for (BehaviorModule** b = m_behaviors; *b; ++b) + { + (*b)->onObjectCreated(); + } + + m_numTriggerAreasActive = 0; + m_enteredOrExitedFrame = 0; + m_isSelectable = tt->isKindOf(KINDOF_SELECTABLE); + + m_healthBoxOffset.zero();// this is used for units that are amorphous, like angry mob + + //Modules have now been completely created! + m_modulesReady = true; + + TheRadar->addObject( this ); + + // register the object with the GameLogic + TheGameLogic->registerObject( this ); + + //disable occlusion for some time after object is created to allow them to exit the factory/building. + m_safeOcclusionFrame = TheGameLogic->getFrame()+tt->getOcclusionDelay(); + + + m_soleHealingBenefactorID = INVALID_ID; ///< who is the only other object that can give me this non-stacking heal benefit? + m_soleHealingBenefactorExpirationFrame = 0; ///< on what frame can I accept healing (thus to switch) from a new benefactor + + + +} // end Object + +//------------------------------------------------------------------------------------------------- +/** Emit message announcing object's creation + * Note: Have to do this in virtual init() method because virtual methods + * don't become virtual until AFTER the constructor has completed, and we + * need to send our type in this message via virtual getType(). */ +//------------------------------------------------------------------------------------------------- +void Object::initObject() +{ + // Weapons & Damage ------------------------------------------------------------------------------------------------- + // Force the initial weapon set to be instantiated & reloaded. + + //GS No Bad Wrong + // The flags are constructed to empty, and between then and now they may be set in valid ways by onCreate modules. + // We don't want to blow that away. updateWeaponSet is safe to call on its own, so I will move that to the end. +// m_curWeaponSetFlags.clear(); +// m_weaponSet.updateWeaponSet(this); +// m_weaponBonusCondition = 0; + + for (int i = 0; i < WEAPONSLOT_COUNT; ++i) + m_lastWeaponCondition[i] = WSF_INVALID; + + // emit message announcing object's creation + TheGameLogic->sendObjectCreated( this ); + + // If I have a valid team assigned, I can run through my Upgrade modules with his flags + updateUpgradeModules(); + + //If the player has battle plans (America Strategy Center), then apply those bonuses + //to this object if applicable. Internally it validates certain kinds of objects. + const Player* controller = getControllingPlayer(); + if (controller) + { + if (!getReceivingDifficultyBonus() && TheScriptEngine->getObjectsShouldReceiveDifficultyBonus()) + { + setReceivingDifficultyBonus(TRUE); + } + + if (controller->getNumBattlePlansActive() > 0) + { + controller->applyBattlePlanBonusesForObject( this ); + } + } + + + //For each special power module that we have, add it's type to the specialpower bits. This is + //for optimal access later. + for (BehaviorModule** m = m_behaviors; *m; ++m) + { + SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); + if (!sp) + continue; + + const SpecialPowerTemplate *spTemplate = sp->getSpecialPowerTemplate(); + if( spTemplate ) + { + SET_SPECIALPOWERMASK( m_specialPowerBits, spTemplate->getSpecialPowerType() ); + } + } + + // Kris -- All missiles must be projectiles! This is the perfect place to assert them! + // srj: yes, but only in debug... +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + if( !isKindOf( KINDOF_PROJECTILE ) ) + { + if( isKindOf( KINDOF_SMALL_MISSILE ) || isKindOf( KINDOF_BALLISTIC_MISSILE ) ) + { + //Warning only... + DEBUG_CRASH( ("Missile %s must also be a KindOf = PROJECTILE in addition to being either a SMALL_MISSILE or PROJECTILE_MISSILE -- call Kris (36844) for questions!", getTemplate()->getName().str() ) ); + } + } +#endif + if (!isKindOf(KINDOF_PROJECTILE) && !isKindOf(KINDOF_INERT)) { + // Notify script conditions to update conditions that consider unit counts. + // We ignore projectiles cause they are frequently created & destroyed, and are not + // of general interest. Normal unit count tests consider tanks or infantry or planes, etc. jba. + TheScriptEngine->notifyOfObjectCreationOrDestruction(); + TheGameLogic->updateObjectsChangedTriggerAreas(); + } + + // Everything (like weaponSet flags) is inited, so check if the WeaponSet needs to change. + m_weaponSet.updateWeaponSet(this); + + if( isKindOf( KINDOF_MINE ) || isKindOf( KINDOF_BOOBY_TRAP ) || isKindOf( KINDOF_DEMOTRAP ) ) + { + ThePlayerList->getNeutralPlayer()->getAcademyStats()->recordMine(); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Object::~Object() +{ + + // tell the AI the building is gone + /// @todo Generalize the notion of objects entering and leaving the world, so we don't have to special case this + TheAI->pathfinder()->removeObjectFromPathfindMap( this ); + + if (!isKindOf(KINDOF_PROJECTILE) && !isKindOf(KINDOF_INERT)) { + // Notify script conditions to update conditions that consider unit counts. + // We ignore projectiles cause they are frequently created & destroyed, and are not + // of general interest. Normal unit count tests consider tanks or infantry or planes, etc. jba. + TheGameLogic->updateObjectsChangedTriggerAreas(); + TheScriptEngine->notifyOfObjectCreationOrDestruction(); + } + + // + // remove from radar before we NULL out the team ... the order of ops are critical here + // because the radar code will sometimes look at the team info and it is assumed through + // the team and player code that the team is valid + // + if( m_radarData ) + TheRadar->removeObject( this ); + + // emit message announcing object's destruction. Again, order is important; we must do this + // before wiping out the team. + TheGameLogic->sendObjectDestroyed( this ); + + // empty the team + setTeam( NULL ); + + // Object's set of these persist for the life of the object. + m_partitionLastLook->deleteInstance(); + m_partitionLastLook = NULL; + m_partitionRevealAllLastLook->deleteInstance(); + m_partitionRevealAllLastLook = NULL; + m_partitionLastShroud->deleteInstance(); + m_partitionLastShroud = NULL; + m_partitionLastThreat->deleteInstance(); + m_partitionLastThreat = NULL; + m_partitionLastValue->deleteInstance(); + m_partitionLastValue = NULL; + + // remove the object from the partition system if present + if( m_partitionData ) + ThePartitionManager->unRegisterObject( this ); + + // if we are in a group, remove us + if (m_group) + m_group->remove( this ); + + // note, do NOT free these, there are just a shadow copy! + m_ai = NULL; + m_physics = NULL; + + // delete any modules present + for (BehaviorModule** b = m_behaviors; *b; ++b) + { + (*b)->deleteInstance(); + *b = NULL; // in case other modules call findModule from their dtor! + } + + delete [] m_behaviors; + m_behaviors = NULL; + + if( m_experienceTracker ) + m_experienceTracker->deleteInstance(); + + m_experienceTracker = NULL; + + // we don't need to delete these, there were deleted on the m_behaviors list + m_firingTracker = NULL; + m_repulsorHelper = NULL; + + m_statusDamageHelper = NULL; + m_tempWeaponBonusHelper = NULL; + m_subdualDamageHelper = NULL; + m_chronoDamageHelper = NULL; + m_smcHelper = NULL; + m_wsHelper = NULL; + m_defectionHelper = NULL; + + // reset id to zero so we never mistaken grab "dead" objects + m_id = INVALID_ID; + + // Instead of removing it from the named cache, notify the script engine that it has died. + // The script engine will remove it from the cache if necessary. The script engine needs to take + // a crack at this in case it is the current "This Object" pointer. + TheScriptEngine->notifyOfObjectDestruction(this); +} + +//------------------------------------------------------------------------------------------------- +/// this object now contained in "containedBy" +//------------------------------------------------------------------------------------------------- +void Object::onContainedBy( Object *containedBy ) +{ + setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_UNSELECTABLE ) ); + if (containedBy && containedBy->getContain()->isEnclosingContainerFor(this)) + setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) ); + else + clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) ); + m_containedBy = containedBy; + m_containedByFrame = TheGameLogic->getFrame(); + + handlePartitionCellMaintenance(); // which should unlook me now that I am contained + +} + +//------------------------------------------------------------------------------------------------- +/// this object no longer contained in "containedBy" +//------------------------------------------------------------------------------------------------- +void Object::onRemovedFrom( Object *removedFrom ) +{ + clearStatus( MAKE_OBJECT_STATUS_MASK2( OBJECT_STATUS_MASKED, OBJECT_STATUS_UNSELECTABLE ) ); + m_containedBy = NULL; + m_containedByFrame = 0; + + handlePartitionCellMaintenance(); // get a clean look, now that I am outdoors, again + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Int Object::getTransportSlotCount() const +{ + Int count = getTemplate()->getRawTransportSlotCount(); + ContainModuleInterface* contain = getContain(); + if ( contain && contain->isSpecialZeroSlotContainer() ) + { + count = 0; + const ContainedItemsList* items = contain->getContainedItemsList(); + if (items) + { + for (ContainedItemsList::const_iterator it = items->begin(); it != items->end(); ++it) + { + count += (*it)->getTransportSlotCount(); + } + } + } + return count; +} + +//------------------------------------------------------------------------------------------------- +/** Run from GameLogic::destroyObject */ +//------------------------------------------------------------------------------------------------- +void Object::onDestroy() +{ + + // This is the old cleanUpContain safeguard. Say goodbye so they don't try to look us up. + if( m_containedBy && m_containedBy->getContain() ) + { + m_containedBy->getContain()->removeFromContain( this ); + } + + // + // run the onDelete on all modules present so they each have an opportunity to cleanup + // anything they need to ... including talking to any other modules + // + for (BehaviorModule** b = m_behaviors; *b; ++b) + { + (*b)->onDelete(); + } + + //Have to remove ourself from looking as well. RebuildHoleWorkers definately hit here. + handlePartitionCellMaintenance(); +} // end onDestroy + +//============================================================================= +//============================================================================= +void Object::setGeometryInfo(const GeometryInfo& geom) +{ + m_geometryInfo = geom; + if( m_partitionData ) + { + // if our geometry changes, we unregister and re-register with the partitionmgr + // so that our size gets updated appropriately. this shouldn't be a problem + // unless setGeometryInfo gets called frequently. (srj) + ThePartitionManager->unRegisterObject( this ); + ThePartitionManager->registerObject( this ); + } + + if (m_drawable) + m_drawable->reactToGeometryChange(); +} + +//============================================================================= +//============================================================================= +void Object::setGeometryInfoZ( Real newZ ) +{ + // A Z change only does not need to un/register with the PartitionManager + m_geometryInfo.setMaxHeightAbovePosition( newZ ); + + if (m_drawable) + m_drawable->reactToGeometryChange(); +} + +//============================================================================= +void Object::friend_setUndetectedDefector( Bool status ) +{ + if (status) + m_privateStatus |= UNDETECTED_DEFECTOR; + else + m_privateStatus &= ~UNDETECTED_DEFECTOR; +} + +//============================================================================= +void Object::restoreOriginalTeam() +{ + if( m_team == NULL || m_originalTeamName.isEmpty() ) + return; + + Team* origTeam = TheTeamFactory->findTeam(m_originalTeamName); + if (origTeam == NULL) + { + DEBUG_CRASH(("Object original team (%s) could not be found or created! (srj)\n",m_originalTeamName.str())); + return; + } + + if (m_team == origTeam) + { + DEBUG_CRASH(("Object appears to still be on its original team, so why are we attempting to restore it? (srj)\n")); + return; + } + + setTeam(origTeam); +} + +//============================================================================= +//============================================================================= +void Object::setTeam( Team *team ) +{ + // In order to prevent spawning useful units for a player after he dies, we + // just assign objects to the neutral player if we try to misbehave. + if (team && !team->getControllingPlayer()->isPlayerActive()) + team = ThePlayerList->getNeutralPlayer()->getDefaultTeam(); + + setTemporaryTeam(team); + m_originalTeamName = m_team ? m_team->getName() : AsciiString::TheEmptyString; +} + +//============================================================================= +//============================================================================= +void Object::setTemporaryTeam( Team *team ) +{ + const Bool restoring = false; + setOrRestoreTeam(team, restoring); +} + +//============================================================================= +//============================================================================= +void Object::setOrRestoreTeam( Team* team, Bool restoring ) +{ + // don't do anything if the team hasn't changed + if( m_team == team ) + return; + + Team* oldTeam = m_team; + + // Before Switch ////////////////////////// + if (m_team) + { + if (m_team->isInList_TeamMemberList(this)) + { + m_team->removeFrom_TeamMemberList(this); + m_team->getControllingPlayer()->becomingTeamMember(this, false); + } + } + + // Switch ////////////////////////// + m_team = team; + + // After Switch ////////////////////////// + if (m_team) + { + if (!m_team->isInList_TeamMemberList(this)) + { + m_team->prependTo_TeamMemberList(this); + m_team->getControllingPlayer()->becomingTeamMember(this, true); + } + + // now, adjust the attitude of the unit to its new team. + const TeamPrototype* proto = m_team->getPrototype(); + if (proto && proto->getTemplateInfo()) + { + AIUpdateInterface *ai = getAIUpdateInterface(); + if (ai) + { + ai->setAttitude(proto->getTemplateInfo()->m_initialTeamAttitude); + if (proto->getAttackPriorityName().isNotEmpty()) { + AsciiString name = proto->getAttackPriorityName(); + const AttackPriorityInfo *info = TheScriptEngine->getAttackInfo(name); + if (info && info->getName().isNotEmpty()) { + ai->setAttackInfo(info); + } + } + } + } + // emit message announcing object's new alliance + Drawable *draw = getDrawable(); + if (draw) + draw->changedTeam(); + } + + // This can't just go in ::defect, because some things just do setTeam. The act of + // setting a new team needs to tell the modules and do other important stuff. + // And it needs to happen after the switch. + if( oldTeam && team && !restoring ) + onCapture( oldTeam->getControllingPlayer(), team->getControllingPlayer() ); + + // + // the team changed we have a change in priorities on the radar if we are + // a candidate for the radar as it is + // + if( m_radarData ) + { + + // removing it and adding it will cause a resort to happen + TheRadar->removeObject( this ); + TheRadar->addObject( this ); + } + + // Tell TheInGameUI that the object has changed hands + Int oldPlayerIndex = (oldTeam)?(oldTeam->getControllingPlayer()->getPlayerIndex()):-1; + Int newPlayerIndex = (m_team)?(m_team->getControllingPlayer()->getPlayerIndex()):-1; + if (oldPlayerIndex != newPlayerIndex) + TheInGameUI->objectChangedTeam(this, oldPlayerIndex, newPlayerIndex); +} + +//============================================================================= +enum +{ + BOOBY_TRAP_SCAN_RANGE = 25 +}; +Bool Object::checkAndDetonateBoobyTrap(const Object *victim) +{ + if( !testStatus(OBJECT_STATUS_BOOBY_TRAPPED) ) + return FALSE; + + PartitionFilterAcceptByKindOf kindFilter(MAKE_KINDOF_MASK(KINDOF_BOOBY_TRAP), KINDOFMASK_NONE); + PartitionFilterSameMapStatus filterMapStatus(this); + PartitionFilter *filters[3]; + filters[0] = &kindFilter; + filters[1] = &filterMapStatus; + filters[2] = NULL; + + ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( getPosition(), BOOBY_TRAP_SCAN_RANGE + getGeometryInfo().getBoundingCircleRadius(), + FROM_CENTER_2D, filters, ITER_SORTED_NEAR_TO_FAR ); + MemoryPoolObjectHolder hold(iter);// This is the magic thing that frees the dynamically made iter in its destructor + + Object *ourBoobyTrap = NULL; + for( Object *other = iter->first(); other; other = iter->next() ) + { + if( other->getProducerID() == getID() )// Sticky bombs call the thing they are on their producer for just such an occasion + { + ourBoobyTrap = other; + break; + } + } + + if( ourBoobyTrap ) + { + static NameKeyType key_StickyBombUpdate = NAMEKEY( "StickyBombUpdate" ); + StickyBombUpdate *update = (StickyBombUpdate*)ourBoobyTrap->findUpdateModule( key_StickyBombUpdate ); + if( update ) + { + if( victim && ourBoobyTrap->getControllingPlayer()->getRelationship(victim->getTeam()) == ALLIES ) + return FALSE;// Friends don't touch friends boobies. + + update->detonate(); + return TRUE;// Booby Trapped status will be cleared by stickybomb, as they set it + } + } + + return FALSE; +} + +//============================================================================= +void Object::setStatus( ObjectStatusMaskType objectStatus, Bool set ) +{ + ObjectStatusMaskType oldStatus = m_status; + + if (set) + m_status.set( objectStatus ); + else + m_status.clear( objectStatus ); + + if (m_status != oldStatus) + { + if( set && objectStatus.test( OBJECT_STATUS_REPULSOR ) && m_repulsorHelper != NULL ) + { + // Damaged repulsable civilians scare (repulse) other civs, but only + // for a short amount of time... use the repulsor helper to turn off repulsion shortly. + m_repulsorHelper->sleepUntil(TheGameLogic->getFrame() + 2*LOGICFRAMES_PER_SECOND); + } + + if( objectStatus.test( OBJECT_STATUS_STEALTHED ) || objectStatus.test( OBJECT_STATUS_DETECTED ) || objectStatus.test( OBJECT_STATUS_DISGUISED ) ) + { + //Kris: Aug 20, 2003 + //When any of the three key status bits for stealth go on or off, then handle partition updates for vision. + if( getTemplate()->getShroudRevealToAllRange() > 0.0f ) + { + handlePartitionCellMaintenance(); + } + } + + + // when an object's construction status changes, it needs to have its partition data updated, + // in order to maintain the shroud correctly. + if( m_status.test( OBJECT_STATUS_UNDER_CONSTRUCTION ) != oldStatus.test( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) + { + + // CHECK FOR MINES, AND DETONATE THEM NOW + ObjectIterator *iter = + ThePartitionManager->iteratePotentialCollisions( getPosition(), getGeometryInfo(), getOrientation() ); + MemoryPoolObjectHolder hold( iter ); + Object *them; + for( them = iter->first(); them; them = iter->next() ) + { + if (them->isKindOf( KINDOF_MINE )) + { + //DETONATE ANY ENEMY MINES, OR DELETE FRIENDLY ONES + Relationship r = getRelationship(them); + if (r == ENEMIES) + { + them->kill(); // detonate mine + } + else + { + TheGameLogic->destroyObject(them); + } + } + }// next object + + if (m_partitionData) + m_partitionData->makeDirty(true); + } + + } + +} + +//============================================================================= +void Object::setScriptStatus( ObjectScriptStatusBit bit, Bool set ) +{ + UnsignedInt oldScriptStatus = m_scriptStatus; + + if( set ) + { + m_scriptStatus |= bit; + } + else + { + m_scriptStatus &= ~bit; + } + + if( m_scriptStatus != oldScriptStatus ) + { + if( (m_scriptStatus & OBJECT_STATUS_SCRIPT_DISABLED) != (oldScriptStatus & OBJECT_STATUS_SCRIPT_DISABLED) ) + { + if( m_partitionData ) + { + // if an object becomes disabled or unpowered, then you have to update its partition data because it will + // change how far it can see. + m_partitionData->makeDirty(true); + } + if( m_scriptStatus & OBJECT_STATUS_SCRIPT_DISABLED ) + { + //I am now disabled, so tell the main game engine! + setDisabled( DISABLED_SCRIPT_DISABLED ); + } + else + { + //I am no longer disabled, so tell the main game engine! + clearDisabled( DISABLED_SCRIPT_DISABLED ); + } + } + if( (m_scriptStatus & OBJECT_STATUS_SCRIPT_UNPOWERED) != (oldScriptStatus & OBJECT_STATUS_SCRIPT_UNPOWERED) ) + { + if( m_partitionData ) + { + // if an object becomes disabled or unpowered, then you have to update its partition data because it will + // change how far it can see. + m_partitionData->makeDirty(true); + } + if( m_scriptStatus & OBJECT_STATUS_SCRIPT_UNPOWERED ) + { + //I am now underpowered, so tell the main game engine! + setDisabled( DISABLED_SCRIPT_UNDERPOWERED ); + } + else + { + //I am no longer undperpowered, so tell the main game engine! + clearDisabled( DISABLED_SCRIPT_UNDERPOWERED ); + } + } + } +} + +//============================================================================= +Bool Object::canCrushOrSquish(Object *otherObj, CrushSquishTestType testType ) const +{ + DEBUG_ASSERTCRASH(this, ("null this in canCrushOrSquish")); + + if( !otherObj ) + { + //Can't crush anything. + return false; + } + + if( isDisabledByType( DISABLED_UNMANNED ) ) + { + //Unmanned vehicles cannot crush troops. This was happening when Jarmen Kell sniped + //the vehicle and booted the guys out while still moving, as the vehicle is now + //on a different team. + return false; + } + + UnsignedByte crusherLevel = getCrusherLevel(); + + // order matters: we want to know if I consider it to be an ally, not vice versa + if( getRelationship( otherObj ) == ALLIES ) + { + //Friends don't let friends crush friends. + return false; + } + + if( !crusherLevel ) + { + //Can't crush anything! + return false; + } + + //Test this case for generic infantry getting squished by vehicles! + if( testType == TEST_SQUISH_ONLY || testType == TEST_CRUSH_OR_SQUISH ) + { + + //**************************************************************************************** + //NOTE: This section of code is used by the pathfinder to determine if the object should + // move to the target. I don't think it's the right place to check for this because + // the semantics check to see if we can squish something -- not approach it. However + // I'm not moving it for fear of some major breakage! -- KM + //Bool squisher = crusherLevel > 0; + //if( !squisher ) + //{ + // Weapon *weapon = getCurrentWeapon(); + // if( weapon && weapon->isContactWeapon() ) + // { + // squisher = true; + // } + //} + //if( squisher ) + //NOTE2: *** IF YOU REENABLE THIS CODE -- Move the "if( !crusherLevel ) return false" below + // this squish section. + //**************************************************************************************** + { + // See if other is squishable + static NameKeyType key_squish = NAMEKEY( "SquishCollide" ); + if( otherObj->findModule( key_squish ) ) + { + return true; // squishable. + } + } + } + + + UnsignedByte crushableLevel = otherObj->getCrushableLevel(); + + if( testType == TEST_CRUSH_ONLY || testType == TEST_CRUSH_OR_SQUISH ) + { + if( crusherLevel > crushableLevel ) + { + return true; + } + } + + return false; +} + +//------------------------------------------------------------------------------------------------- +UnsignedByte Object::getCrusherLevel() const +{ + return getTemplate()->getCrusherLevel(); +} + +//------------------------------------------------------------------------------------------------- +UnsignedByte Object::getCrushableLevel() const +{ + return getTemplate()->getCrushableLevel(); +} + + +// ------------------------------------------------------------------------------------------------ +/** Topple an object, if possible */ +// ------------------------------------------------------------------------------------------------ +void Object::topple( const Coord3D *toppleDirection, Real toppleSpeed, UnsignedInt options ) +{ + static NameKeyType key_ToppleUpdate = NAMEKEY("ToppleUpdate"); + + ToppleUpdate* toppleUpdate = (ToppleUpdate*)findModule(key_ToppleUpdate); + if( toppleUpdate && toppleUpdate->isAbleToBeToppled() ) + { + + // apply the topple force + toppleUpdate->applyTopplingForce( toppleDirection, toppleSpeed, options ); + + } // end if + +} // end topple + +//============================================================================= +void Object::setArmorSetFlag(ArmorSetType ast) +{ + m_body->setArmorSetFlag(ast); +} + +//============================================================================= +void Object::clearArmorSetFlag(ArmorSetType ast) +{ + m_body->clearArmorSetFlag(ast); +} + +//============================================================================= +Bool Object::testArmorSetFlag(ArmorSetType ast) const +{ + return m_body->testArmorSetFlag(ast); +} + +//============================================================================= +void Object::reloadAllAmmo(Bool now) +{ + m_weaponSet.reloadAllAmmo(this, now); +} + +//============================================================================= +Bool Object::isOutOfAmmo() const +{ + return m_weaponSet.isOutOfAmmo(); +} + +//============================================================================= +Bool Object::hasAnyWeapon() const +{ + return m_weaponSet.hasAnyWeapon(); +} + +//============================================================================= +Bool Object::hasAnyDamageWeapon() const +{ + //First check to see if we have any weapons -- if not return false. + if( !m_weaponSet.hasAnyDamageWeapon() ) + { + return FALSE; + } + return TRUE; +} + +//============================================================================= +UnsignedInt Object::getMostPercentReadyToFireAnyWeapon() const +{ + return m_weaponSet.getMostPercentReadyToFireAnyWeapon(); +} + +//============================================================================= +Bool Object::getWeaponInWeaponSlotSyncedToSlot(WeaponSlotType thisSlot, WeaponSlotType otherSlot) const +{ + CommandSourceMask mask = getWeaponInWeaponSlotCommandSourceMask(thisSlot); + + //Bool value0a = mask & (1 << CMD_SYNC_TO_PRIMARY); + //Bool value0b = (otherSlot == PRIMARY_WEAPON); + //Bool value1a = mask & (1 << CMD_SYNC_TO_SECONDARY); + //Bool value1b = (otherSlot == SECONDARY_WEAPON); + //Bool value2a = mask & (1 << CMD_SYNC_TO_TERTIARY); + //Bool value2b = (otherSlot == TERTIARY_WEAPON); + + //DEBUG_LOG(("- getWeaponInWeaponSlotSyncedToSlot (thisSlot=%d, otherSlot=%d): mask = %d --> value0 = %d/%d, value1 = %d/%d, value2 = %d/%d.\n", + // thisSlot, otherSlot, static_cast(mask), value0a, value0b, value1a, value1b, value2a, value2b)); + + return ((Int)mask >= 0) && + ((mask & (1 << CMD_SYNC_TO_PRIMARY) && otherSlot == PRIMARY_WEAPON) || + (mask & (1 << CMD_SYNC_TO_SECONDARY) && otherSlot == SECONDARY_WEAPON) || + (mask & (1 << CMD_SYNC_TO_TERTIARY) && otherSlot == TERTIARY_WEAPON) || + (mask & (1 << CMD_SYNC_TO_FOUR) && otherSlot == WEAPON_FOUR) || + (mask & (1 << CMD_SYNC_TO_FIVE) && otherSlot == WEAPON_FIVE) || + (mask & (1 << CMD_SYNC_TO_SIX) && otherSlot == WEAPON_SIX) || + (mask & (1 << CMD_SYNC_TO_SEVEN) && otherSlot == WEAPON_SEVEN) || + (mask & (1 << CMD_SYNC_TO_EIGHT) && otherSlot == WEAPON_EIGHT)); + +} + +//============================================================================= +Bool Object::hasWeaponToDealDamageType(DamageType typeToDeal) const +{ + return m_weaponSet.hasWeaponToDealDamageType(typeToDeal); +} + +//============================================================================= +Real Object::getLargestWeaponRange() const +{ + Real retVal = -1; + for (Int i = PRIMARY_WEAPON; i < WEAPONSLOT_COUNT; ++i) { + Weapon* weapon = m_weaponSet.getWeaponInWeaponSlot((WeaponSlotType)i); + if (!weapon) { + continue; + } + + Real tmpVal = weapon->getAttackRange(this); + if (tmpVal > retVal) { + retVal = tmpVal; + } + } + return retVal; +} + +//============================================================================= +void Object::setFiringConditionForCurrentWeapon() const +{ + if (m_drawable) + { + WeaponSlotType wslot = m_weaponSet.getCurWeaponSlot(); + ModelConditionFlags c = m_weaponSet.getModelConditionForWeaponSlot(wslot, WSF_FIRING); + m_drawable->clearAndSetModelConditionFlags(s_allWeaponFireFlags[wslot], c); + } +} + +//============================================================================= +void Object::setModelConditionState( ModelConditionFlagType a ) +{ + if (m_drawable) + { + m_drawable->setModelConditionState(a); + } +} + +//============================================================================= +void Object::clearModelConditionState( ModelConditionFlagType a ) +{ + if (m_drawable) + { + m_drawable->clearModelConditionState(a); + } +} + +//============================================================================= +void Object::clearAndSetModelConditionState( ModelConditionFlagType clr, ModelConditionFlagType set ) +{ + if (m_drawable) + { + m_drawable->clearAndSetModelConditionState(clr, set); + } +} + +//============================================================================= +void Object::clearModelConditionFlags( const ModelConditionFlags& clr ) +{ + if (m_drawable) + { + m_drawable->clearModelConditionFlags(clr); + } +} + +//============================================================================= +void Object::setModelConditionFlags( const ModelConditionFlags& set ) +{ + if (m_drawable) + { + m_drawable->setModelConditionFlags(set); + } +} + +//============================================================================= +void Object::clearAndSetModelConditionFlags( const ModelConditionFlags& clr, const ModelConditionFlags& set ) +{ + if (m_drawable) + { + m_drawable->clearAndSetModelConditionFlags(clr, set); + } +} + +//============================================================================= +// Special model states are states that are turned on for a period of time, and +// turned off automatically -- used for cheer, and scripted special moment +// animations. Setting a special state will automatically clear any other +// special states that may be turned on so you can only have one at a time. +//============================================================================= +void Object::setSpecialModelConditionState( ModelConditionFlagType set, UnsignedInt frames ) +{ + clearSpecialModelConditionStates(); + + setModelConditionState( set ); + + if( frames == 0 ) + { + frames = 1; + } + + m_smcUntil = TheGameLogic->getFrame() + frames; + m_smcHelper->sleepUntil(m_smcUntil); +} + +//============================================================================= +void Object::clearSpecialModelConditionStates() +{ + clearModelConditionFlags( MAKE_MODELCONDITION_MASK( MODELCONDITION_SPECIAL_CHEERING ) ); + m_smcUntil = NEVER; +} + +// Lorenzen has some interest in this, ask before deleting +//============================================================================= +//const ModelConditionFlags& Object::getModelConditionFlags() const +//{ +// if (m_drawable) +// { +// return m_drawable->getModelConditionFlags(); +// } +// else +// { +// DEBUG_CRASH(("NULL Drawable at this point, you can't get modelconditionflags now.")); +// static ModelConditionFlags noFlags; +// return noFlags; +// } +//} + +//============================================================================= +Weapon* Object::getCurrentWeapon(WeaponSlotType* wslot) +{ + if (!m_weaponSet.hasAnyWeapon()) + return NULL; + + if (wslot) + *wslot = m_weaponSet.getCurWeaponSlot(); + return m_weaponSet.getCurWeapon(); +} + +//============================================================================= +const Weapon* Object::getCurrentWeapon(WeaponSlotType* wslot) const +{ + if (!m_weaponSet.hasAnyWeapon()) + return NULL; + + if (wslot) + *wslot = m_weaponSet.getCurWeaponSlot(); + return m_weaponSet.getCurWeapon(); +} + +//============================================================================= +Weapon* Object::findWaypointFollowingCapableWeapon() +{ + return m_weaponSet.findWaypointFollowingCapableWeapon(); +} + +//============================================================================= +Bool Object::getAmmoPipShowingInfo(Int& numTotal, Int& numFull) const +{ +/// @todo srj -- may need to cache this inside weaponset. + const Weapon* w = m_weaponSet.findAmmoPipShowingWeapon(); + if (w) + { + numTotal = w->getClipSize(); + numFull = w->getRemainingAmmo(); + return true; + } + else + { + return false; + } +} + +//============================================================================= +/* + NOTE: getAbleToAttackSpecificObject NO LONGER internally calls isAbleToAttack(), + since that isn't an incredibly fast call, and this is called repeatedly in some inner loops + where we already know that isAbleToAttack() == true. so you should always + call isAbleToAttack prior to calling this! (srj) +*/ +CanAttackResult Object::getAbleToAttackSpecificObject( AbleToAttackType t, const Object* target, CommandSourceType commandSource, WeaponSlotType specificSlot ) const +{ + // NO! BAD! WRONG! + // If we can't attack at all, then we cannot attack this + //if (!isAbleToAttack()) + // return FALSE; + + // Otherwise leave it up to our weapons. + return m_weaponSet.getAbleToAttackSpecificObject( t, this, target, commandSource, specificSlot ); +} + +//============================================================================= +//Used for base defenses and otherwise stationary units to see if you can attack a position potentially out of range. +CanAttackResult Object::getAbleToUseWeaponAgainstTarget( AbleToAttackType attackType, const Object *victim, const Coord3D *pos, CommandSourceType commandSource, WeaponSlotType specificSlot ) const +{ + return m_weaponSet.getAbleToUseWeaponAgainstTarget( attackType, this, victim, pos, commandSource, specificSlot ); +} + + +//============================================================================= +Bool Object::chooseBestWeaponForTarget(const Object* target, WeaponChoiceCriteria criteria, CommandSourceType cmdSource ) +{ + return m_weaponSet.chooseBestWeaponForTarget(this, target, criteria, cmdSource ); +} + +//DECLARE_PERF_TIMER(fireCurrentWeapon) +//============================================================================= +void Object::fireCurrentWeapon(Object *target) +{ + //USE_PERF_TIMER(fireCurrentWeapon) + + // victim may have already been destroyed + if (target == NULL) + return; + + Weapon* weapon = m_weaponSet.getCurWeapon(); + if (weapon && (weapon->getStatus() == READY_TO_FIRE)) + { + Bool reloaded = weapon->fireWeapon(this, target); + DEBUG_ASSERTCRASH(m_firingTracker, ("hey, we are firing but have no firing tracker. this is wrong.")); + if (m_firingTracker) + m_firingTracker->shotFired(weapon, target->getID()); + if (reloaded) + releaseWeaponLock(LOCKED_TEMPORARILY); // release any temporary locks. + + friend_setUndetectedDefector( FALSE );// My secret is out + } +} + +//============================================================================= +void Object::fireCurrentWeapon(const Coord3D* pos) +{ + //USE_PERF_TIMER(fireCurrentWeapon) + + if (pos == NULL) + return; + + Weapon* weapon = m_weaponSet.getCurWeapon(); + if (weapon && (weapon->getStatus() == READY_TO_FIRE)) + { + Bool reloaded = weapon->fireWeapon(this, pos); + DEBUG_ASSERTCRASH(m_firingTracker, ("hey, we are firing but have no firing tracker. this is wrong.")); + if (m_firingTracker) + m_firingTracker->shotFired(weapon, INVALID_ID); + if (reloaded) + releaseWeaponLock(LOCKED_TEMPORARILY); // release any temporary locks. + + friend_setUndetectedDefector( FALSE );// My secret is out + } +} + +//============================================================================== +void Object::notifyFiringTrackerShotFired( const Weapon* weaponFired, ObjectID victimID ) +{ + if ( m_firingTracker ) + m_firingTracker->shotFired( weaponFired, victimID ); +} + + +//============================================================================= +void Object::preFireCurrentWeapon( const Object *victim ) +{ + Weapon* weapon = m_weaponSet.getCurWeapon(); + + //If we are going to be capable of firing our weapon NEXT frame, set the pre-attack + //up now. This gets called by AIAttackFireWeaponState::onEnter().. but the update happens + //next frame. + if (weapon && TheGameLogic->getFrame() + 1 >= weapon->getPossibleNextShotFrame() ) + { + weapon->preFireWeapon( this, victim ); + friend_setUndetectedDefector( FALSE );// My secret is out + } +} + +//============================================================================= +void Object::preFireCurrentWeapon(const Coord3D* pos) +{ + Weapon* weapon = m_weaponSet.getCurWeapon(); + + //If we are going to be capable of firing our weapon NEXT frame, set the pre-attack + //up now. This gets called by AIAttackFireWeaponState::onEnter().. but the update happens + //next frame. + if (weapon && TheGameLogic->getFrame() + 1 >= weapon->getPossibleNextShotFrame()) + { + weapon->preFireWeapon(this, pos); + friend_setUndetectedDefector(FALSE);// My secret is out + } +} + +// ============================================================================ +/** Using the firing tracker, return the frame a shot was last fired on */ +// ============================================================================ +UnsignedInt Object::getLastShotFiredFrame() const +{ + UnsignedInt recent = 0; + for (int i = 0; i < WEAPONSLOT_COUNT; ++i) + { + const Weapon* w = m_weaponSet.getWeaponInWeaponSlot((WeaponSlotType)i); + if (w) + { + UnsignedInt when = w->getLastShotFrame(); + if (when > recent) + recent = when; + } + } + return recent; +} + +// ============================================================================ +/** Get the victim ID we last shot at */ +// ============================================================================ +ObjectID Object::getLastVictimID() const +{ + return m_firingTracker ? m_firingTracker->getLastShotVictim() : INVALID_ID; +} + +//============================================================================= +// Object::getRelationship +//============================================================================= +Relationship Object::getRelationship(const Object *that) const +{ + const Team *myTeam = getTeam(); + + if (myTeam && that) + { + if (getIsUndetectedDefector()) + { + return NEUTRAL; // so my AI does not give away my position by auto acquire + } + else if (that->getIsUndetectedDefector()) + { + return ALLIES; // so I treat undetecteddefectors like they were my very own + } + else + { + return myTeam->getRelationship( that->getTeam() ); + } + } + + return NEUTRAL; + +} + +//============================================================================= +// Object::getControllingPlayer +//============================================================================= +Player * Object::getControllingPlayer() const +{ + const Team* myTeam = this->getTeam(); + if (myTeam) + return myTeam->getControllingPlayer(); + + return NULL; +} + +//============================================================================= +void Object::setProducer(const Object* obj) +{ + m_producerID = obj ? obj->getID() : INVALID_ID; +// seems like a good idea, but is not. (srj) +// if (obj) +// m_indicatorColor = obj->m_indicatorColor; +} + +//============================================================================= +void Object::setBuilder( const Object *obj ) +{ + + m_builderID = obj ? obj->getID() : INVALID_ID; + +} + +//============================================================================= +void Object::setCustomIndicatorColor(Color c) +{ + if (m_indicatorColor != c) + { + m_indicatorColor = c; + if (m_drawable) + m_drawable->changedTeam(); + } +} + +//============================================================================= +void Object::removeCustomIndicatorColor() +{ + setCustomIndicatorColor(0); +} + +//============================================================================= +// Object::getIndicatorColor +//============================================================================= +Color Object::getIndicatorColor() const +{ + if (m_indicatorColor == 0) + { + const Team *myTeam = getTeam(); + if (myTeam) + { + const Player* p = myTeam->getControllingPlayer(); + if (p) + { + return p->getPlayerColor(); + } + } + return GameMakeColor(0, 0, 0, 255); + } + else + { + return m_indicatorColor; + } +} + +//============================================================================= +// Object::getNightIndicatorColor - used to make blue/purple easier to see on night models. +//============================================================================= +Color Object::getNightIndicatorColor() const +{ + if (m_indicatorColor == 0) + { + const Team *myTeam = getTeam(); + if (myTeam) + { + const Player* p = myTeam->getControllingPlayer(); + if (p) + { + return p->getPlayerNightColor(); + } + } + return GameMakeColor(0, 0, 0, 255); + } + else + { + return m_indicatorColor; + } +} + +//============================================================================= +// Object::isLocallyControlled +//============================================================================= +Bool Object::isLocallyControlled() const +{ + return getControllingPlayer() == ThePlayerList->getLocalPlayer(); +} + +//============================================================================= +// Object::isLocallyControlled +//============================================================================= +Bool Object::isNeutralControlled() const +{ + return getControllingPlayer() == ThePlayerList->getNeutralPlayer(); +} + +//------------------------------------------------------------------------------------------------- +inline Bool isPosDifferent(const Coord3D* a, const Coord3D* b) +{ + // this is necessary because PhysicsBehavior may generate tiny changes even when + // "standing still", due to roundoff errors. It's important that we only invalidate + // the PartitionManager stuff when the pos/orientation really changes (for efficiency purposes) + // so we must put in some cleverness... + const Real THRESH = 0.01f; + + if (fabs(a->x - b->x) > THRESH) + return true; + + if (fabs(a->y - b->y) > THRESH) + return true; + + if (fabs(a->z - b->z) > THRESH) + return true; + + return false; +} + +//------------------------------------------------------------------------------------------------- +inline Bool isAngleDifferent(Real a, Real b) +{ + // this is necessary because PhysicsBehavior may generate tiny changes even when + // "standing still", due to roundoff errors. It's important that we only invalidate + // the PartitionManager stuff when the pos/orientation really changes (for efficiency purposes) + // so we must put in some cleverness... + + const Real THRESH = 0.01f; // in radians, this is approx 1/2 degree. + + if (fabs(a - b) > THRESH) + return true; + + return false; +} + +//------------------------------------------------------------------------------------------------- +void Object::reactToTurretChange( WhichTurretType turret, Real oldRotation, Real oldPitch ) +{ + Real currentRotation = 0.0f; + Real currentPitch = 0.0f; + if( getAI() ) + { + getAI()->getTurretRotAndPitch( turret, ¤tRotation, ¤tPitch ); + } + Bool rotationChange = (currentRotation != oldRotation); +// Bool pitchChange = (currentPitch != oldPitch); + + if( rotationChange ) + { + if (getContain()) + getContain()->containReactToTransformChange(); + } +} + +//------------------------------------------------------------------------------------------------- +//DECLARE_PERF_TIMER(Object_reactToTransformChange) +void Object::reactToTransformChange(const Matrix3D* oldMtx, const Coord3D* oldPos, Real oldAngle) +{ + //USE_PERF_TIMER(Object_reactToTransformChange) + if(_isnan(getPosition()->x) || _isnan(getPosition()->y) || _isnan(getPosition()->z)) { + DEBUG_CRASH(("Object pos is nan.")); + TheGameLogic->destroyObject(this); + } + if (m_drawable) + { + m_drawable->setTransformMatrix( this->getTransformMatrix() ); + } + + Bool posDiff = isPosDifferent(oldPos, getPosition()); + Bool angDiff = isAngleDifferent(oldAngle, getOrientation()); + + if (posDiff || angDiff) + { + if (m_partitionData) + m_partitionData->makeDirty(true); + + if (getContain()) + getContain()->containReactToTransformChange(); + } + + if (posDiff) + { + setTriggerAreaFlagsForChangeInPosition(); // Update for entered/exited + + Region3D mapExtent; + TheTerrainLogic->getExtent(&mapExtent); + if (mapExtent.isInRegionNoZ(getPosition())) + m_privateStatus &= ~OFF_MAP; + else + m_privateStatus |= OFF_MAP; + } +} + +//------------------------------------------------------------------------------------------------- +ObjectShroudStatus Object::getShroudedStatus(Int playerIndex) const +{ + if (getTemplate()->isKindOf( KINDOF_ALWAYS_VISIBLE )) + return OBJECTSHROUD_CLEAR; + + if (m_partitionData) + return m_partitionData->getShroudedStatus(playerIndex); + + // This can happen for objects removed from the partition system (e.g., + // for soldiers that are garrisoned inside a building). + return OBJECTSHROUD_CLEAR; +} + +//------------------------------------------------------------------------------------------------- +/** Something is attempting to damage this object */ +//------------------------------------------------------------------------------------------------- +void Object::attemptDamage( DamageInfo *damageInfo ) +{ + BodyModuleInterface* body = getBodyModule(); + if (body) + body->attemptDamage( damageInfo ); + + // Process any shockwave forces that might affect this object due to the incurred damage + if (damageInfo->in.m_shockWaveAmount > 0.0f && damageInfo->in.m_shockWaveRadius > 0.0f) + { + //KindOfMaskType immuneToShockwaveKindofs; //NEW RESTRICTIONS ADDED + //immuneToShockwaveKindofs.set(KINDOF_PROJECTILE);// projectiles go idle in midair when they get sw'd //NEW RESTRICTIONS ADDED + //immuneToShockwaveKindofs.set(KINDOF_PRODUCED_AT_HELIPAD);//helicopters go all wonky when they get shockwaved //NEW RESTRICTIONS ADDED + + PhysicsBehavior *behavior = getPhysics(); + if ( behavior && (isAirborneTarget() == FALSE) && (! isKindOf(KINDOF_PROJECTILE) ) ) +// if (behavior && isAnyKindOf( immuneToShockwaveKindofs ) == FALSE )//NEW RESTRICTIONS ADDED + { + // Calculate the shockwave taperoff amount due to distance from ground zero + Real shockWaveScalar = damageInfo->in.m_shockWaveVector.length(); + Real distanceFromCenter = min(1.0f, shockWaveScalar / damageInfo->in.m_shockWaveRadius); + Real distanceTaper = (distanceFromCenter) * (1.0f - damageInfo->in.m_shockWaveTaperOff); + Real shockTaperMult = 1.0f - distanceTaper; + + // Set up the shockwave force to use apply on object + Coord3D shockWaveForce; + shockWaveForce.set( &damageInfo->in.m_shockWaveVector ); + shockWaveForce.normalize(); + shockWaveForce.scale( damageInfo->in.m_shockWaveAmount * shockTaperMult ); + shockWaveForce.z = shockWaveForce.length(); // Apply up force equal to the lateral force for dramatic effect + + // Apply the shock to the object + behavior->applyShock(&shockWaveForce); + + // Add random rotation to the object for drama + + behavior->applyRandomRotation(); + + // Set stunned state due to the shock for the object + behavior->setStunned(true); + + setModelConditionState(MODELCONDITION_STUNNED_FLAILING); + } + } + + + /// @todo track damage dealt/attempted + + // + // if actual damage occurred, and this is an object owned by the local player we + // might do a radar event for under attack. Note that we do not even try + // to do radar events for DAMAGE_PENALTY as that damage type is a type of damage + // that occurs with explicit player knowledge + // + if( damageInfo->out.m_actualDamageDealt > 0.0f && + damageInfo->in.m_damageType != DAMAGE_PENALTY && + damageInfo->in.m_damageType != DAMAGE_HEALING && + getControllingPlayer() && + !BitIsSet(damageInfo->in.m_sourcePlayerMask, getControllingPlayer()->getPlayerMask()) && + m_radarData != NULL && + getControllingPlayer() == ThePlayerList->getLocalPlayer() ) + TheRadar->tryUnderAttackEvent( this ); + +} + +//------------------------------------------------------------------------------------------------- +void Object::attemptHealing(Real amount, const Object* source) +{ + BodyModuleInterface* body = getBodyModule(); + if (body) + { + DamageInfo damageInfo; + damageInfo.in.m_damageType = DAMAGE_HEALING; + damageInfo.in.m_deathType = DEATH_NONE; + damageInfo.in.m_sourceID = source ? source->getID() : INVALID_ID; + damageInfo.in.m_amount = amount; + body->attemptHealing( &damageInfo ); + } +} + +ObjectID Object::getSoleHealingBenefactor( void ) const +{ + UnsignedInt now = TheGameLogic->getFrame(); + if( now > m_soleHealingBenefactorExpirationFrame ) + return INVALID_ID; + + return m_soleHealingBenefactorID; + +} + +Bool Object::attemptHealingFromSoleBenefactor ( Real amount, const Object* source, UnsignedInt duration ) +{///< for the non-stacking healers like ambulance and propaganda + + if( ! source ) // sanity + return FALSE; + + UnsignedInt now = TheGameLogic->getFrame(); + ObjectID id = source->getID(); + +// Either it is ok to accept healing from any who offer or this is my guy, calling again + if( now > m_soleHealingBenefactorExpirationFrame || m_soleHealingBenefactorID == id ) + { + m_soleHealingBenefactorID = id; + m_soleHealingBenefactorExpirationFrame = now + duration; + + BodyModuleInterface* body = getBodyModule(); + if (body) + { + DamageInfo damageInfo; + damageInfo.in.m_damageType = DAMAGE_HEALING; + damageInfo.in.m_deathType = DEATH_NONE; + damageInfo.in.m_sourceID = source ? source->getID() : INVALID_ID; + damageInfo.in.m_amount = amount; + body->attemptHealing( &damageInfo ); + } + + return TRUE; + } + + return FALSE; + +} + + +//------------------------------------------------------------------------------------------------- +Real Object::estimateDamage( DamageInfoInput& damageInfo ) const +{ + BodyModuleInterface* body = getBodyModule(); + if (body) + return body->estimateDamage( damageInfo ); + + return 0.0f; +} + +//------------------------------------------------------------------------------------------------- +/** Do so much damage to an object that it will certainly die */ +//------------------------------------------------------------------------------------------------- +void Object::kill( DamageType damageType, DeathType deathType ) +{ + DamageInfo damageInfo; + + // Do unmodifiable damage equal to their max health to kill. + damageInfo.in.m_damageType = damageType; + damageInfo.in.m_deathType = deathType; + damageInfo.in.m_sourceID = INVALID_ID; + damageInfo.in.m_amount = getBodyModule()->getMaxHealth(); + damageInfo.in.m_kill = TRUE; // Triggers object to die no matter what. + attemptDamage( &damageInfo ); + + DEBUG_ASSERTCRASH(!damageInfo.out.m_noEffect, ("Attempting to kill an unKillable object (InactiveBody?)\n")); + +} // end kill + +//------------------------------------------------------------------------------------------------- +/** Restore max health to this Object */ +//------------------------------------------------------------------------------------------------- +void Object::healCompletely() +{ + attemptHealing(HUGE_DAMAGE_AMOUNT, NULL); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void Object::setEffectivelyDead(Bool dead) +{ + if (dead) + BitSet(m_privateStatus, EFFECTIVELY_DEAD); + else + BitClear(m_privateStatus, EFFECTIVELY_DEAD); + + if (dead) + { + if( m_radarData ) + TheRadar->removeObject( this ); + } +} + +//------------------------------------------------------------------------------------------------- +void Object::setCaptured(Bool isCaptured) +{ + if (isCaptured) + BitSet(m_privateStatus, CAPTURED); + else + { + DEBUG_LOG(("Clearing Captured Status. This should never happen. jkmcd")); + BitClear(m_privateStatus, CAPTURED); + } + + // No need to see if we should skip updates, this flag has no effect on skipping updates. +} + + + +//------------------------------------------------------------------------------------------------- +Bool Object::isStructure(void) const +{ + return isKindOf(KINDOF_STRUCTURE); +} + +//------------------------------------------------------------------------------------------------- +Bool Object::isFactionStructure(void) const +{ + return isAnyKindOf( KINDOFMASK_FS ); +} + +//------------------------------------------------------------------------------------------------- +Bool Object::isNonFactionStructure(void) const +{ + return isStructure() && !isFactionStructure(); +} + +void localIsHero( Object *obj, void* userData ) +{ + Bool *hero = (Bool*)userData; + + if( obj && obj->isKindOf( KINDOF_HERO ) ) + { + *hero = TRUE; + } +} + +//------------------------------------------------------------------------------------------------- +Bool Object::isHero(void) const +{ + ContainModuleInterface *contain = getContain(); + if( contain ) + { + Bool heroInside = FALSE; + contain->iterateContained( localIsHero, (void*)(&heroInside), FALSE ); + if( heroInside ) + { + return TRUE; + } + } + return isKindOf( KINDOF_HERO ); +} + +//------------------------------------------------------------------------------------------------- +void Object::setReceivingDifficultyBonus(Bool receive) +{ + if (receive == m_isReceivingDifficultyBonus) { + return; + } + + m_isReceivingDifficultyBonus = receive; + getControllingPlayer()->friend_applyDifficultyBonusesForObject(this, m_isReceivingDifficultyBonus); +} + +//------------------------------------------------------------------------------------------------- +//- DISABLEDNESS STUFF ---------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void Object::setDisabled( DisabledType type ) +{ + setDisabledUntil(type, FOREVER); +} + +//------------------------------------------------------------------------------------------------- +void Object::setDisabledUntil( DisabledType type, UnsignedInt frame ) +{ + Bool edgeCase = !isDisabled(); + + if( type < 0 || type >= DISABLED_COUNT ) + { + DEBUG_CRASH( ("Invalid disabled type value %d specified -- doesn't not exist!", type ) ); + return; + } + + //Handle audio events! + AudioEventRTS sound; + if( type == DISABLED_UNMANNED && !isKindOf( KINDOF_DRONE ) ) + { + //We've been sniped! Play a splatter sound for the pilot losing his face. + sound = TheAudio->getMiscAudio()->m_splatterVehiclePilotsBrain; + sound.setPosition( getPosition() ); + TheAudio->addAudioEvent( &sound ); + } + else if( type == DISABLED_UNDERPOWERED || type == DISABLED_EMP || type == DISABLED_SUBDUED || type == DISABLED_HACKED ) + { + //We've lost power -- make sure we aren't already out of power as the sounds shouldn't happen + //if you were already disabled. + if( !isDisabledByType( DISABLED_UNDERPOWERED ) && + !isDisabledByType( DISABLED_EMP ) && + !isDisabledByType( DISABLED_SUBDUED ) && + !isDisabledByType( DISABLED_HACKED ) ) + { + if( isKindOf( KINDOF_STRUCTURE ) ) + { + sound = TheAudio->getMiscAudio()->m_buildingDisabled; + sound.setPosition( getPosition() ); + TheAudio->addAudioEvent( &sound ); + } + else if( isKindOf( KINDOF_VEHICLE ) ) + { + sound = TheAudio->getMiscAudio()->m_vehicleDisabled; + sound.setPosition( getPosition() ); + TheAudio->addAudioEvent( &sound ); + } + } + } + + if( m_disabledTillFrame[ type ] != frame ) + { + // an edge-test for disabledness, for type. This INCREMENTS m_pauseCount + // srj sez: HELD nevers disables special powers. + if ( type != DISABLED_HELD && !isDisabledByType( type ) ) + pauseAllSpecialPowers( TRUE ); + + m_disabledTillFrame[ type ] = frame; + m_disabledMask.set( type, frame > TheGameLogic->getFrame() ); + + if( m_drawable ) + { + if( isDisabled() ) + { + // Held does not tint anybody. If we are multiply disabled, the other setting will hit the tint, + // and in clear, only-held and not-disabled are both causes to untint. + // Doh. Also shouldn't be tinting when disabled by scripting. + // Doh^2. Also shouldn't be CLEARING tinting if we're disabling by held or script disabledness + // Doh^3. Unmanned is no tint too + if( type != DISABLED_HELD && type != DISABLED_SCRIPT_DISABLED && type != DISABLED_UNMANNED && type != DISABLED_TELEPORT) + { + m_drawable->setTintStatus( TINT_STATUS_DISABLED ); + } + } + } + + ContainModuleInterface *contain = getContain(); + if ( contain ) + { + Object *rider = (Object*)contain->friend_getRider(); + if ( rider ) + { + rider->setDisabledUntil(type, frame); + } + } + + if ( isKindOf( KINDOF_SPAWNS_ARE_THE_WEAPONS ) ) + { + SpawnBehaviorInterface *sbi = this->getSpawnBehaviorInterface(); + if ( sbi ) + { + //Kris: Patch 1.01 - November 12, 2003 + //Actually, we want to disable the slaves, not order them to go idle! This fix was made to + //stinger sites getting hit by an EMP to prevent the soldiers from attacking. + //sbi->orderSlavesToGoIdle( CMD_FROM_AI ); // the canattack() will take care of any future attempts to fire + sbi->orderSlavesDisabledUntil( type, frame ); + } + + } + + } + + if( type == DISABLED_UNMANNED && !isKindOf( KINDOF_DRONE ) ) + { + //strange but true: If I am a carbomb, + //my driver actually has a dead-man's + //trigger for my dynamite... + //If he gets sniped, I blow up! Wheeee! + + WeaponSetFlags flags; + flags.set( WEAPONSET_CARBOMB ); + const WeaponTemplateSet* set = getTemplate()->findWeaponTemplateSet( flags ); + if( set && set->testWeaponSetFlag( WEAPONSET_CARBOMB ) ) + { + Object* sniper = TheGameLogic->findObjectByID( getBodyModule()->getLastDamageInfo()->in.m_sourceID ); + if ( sniper ) + sniper->scoreTheKill( this ); + + kill(); + } + else + { + //This vehicle's pilot has been sniped, so we want to clear the veterancy rating (if any) + ExperienceTracker *xpTracker = getExperienceTracker(); + if( xpTracker ) + { + xpTracker->setExperienceAndLevel( 0, FALSE ); + } + //Not only that, but it also loses any healing bonuses it may have earned in its prior life + { + static const NameKeyType key_AutoHealBehavior = NAMEKEY("AutoHealBehavior"); + AutoHealBehavior* autoHeal = (AutoHealBehavior*)(findUpdateModule( key_AutoHealBehavior )); + if (autoHeal) + autoHeal->undoUpgrade(); + + + } + } + + } + + // This will only be called if we were NOT disabled before coming into this function. + if (edgeCase) { + onDisabledEdge(true); + } +} + +//------------------------------------------------------------------------------------------------- +UnsignedInt Object::getDisabledUntil( DisabledType type ) const +{ + if( type == DISABLED_ANY ) + { + UnsignedInt highestFrame = 0; + //Iterate through each disabled type and return the one with the highest frame. + for( Int i = 0; i < DISABLED_COUNT; i++ ) + { + if( m_disabledMask.test( i ) && m_disabledTillFrame[ i ] > highestFrame ) + { + highestFrame = m_disabledTillFrame[ i ]; + } + } + return highestFrame; + } + else if( m_disabledMask.test( type ) ) + { + //Specific query. + return m_disabledTillFrame[ type ]; + } + //Not disabled. + return 0; +} + +//------------------------------------------------------------------------------------------------- +Bool Object::clearDisabled( DisabledType type ) +{ + if( type < 0 || type >= DISABLED_COUNT ) + { + DEBUG_CRASH( ("Invalid disabled type value %d specified -- doesn't not exist!", type ) ); + return FALSE; + } + + if (!isDisabledByType(type)) { + return FALSE; + } + + if( type == DISABLED_UNDERPOWERED || type == DISABLED_EMP || type == DISABLED_SUBDUED || type == DISABLED_HACKED ) + { + //We've regained power-- make sure we aren't still disabled by another type. + AudioEventRTS sound; + if( (!isDisabledByType( DISABLED_UNDERPOWERED ) || type == DISABLED_UNDERPOWERED ) && + (!isDisabledByType( DISABLED_EMP ) || type == DISABLED_EMP ) && + (!isDisabledByType( DISABLED_SUBDUED ) || type == DISABLED_SUBDUED ) && + (!isDisabledByType( DISABLED_HACKED ) || type == DISABLED_HACKED ) ) + { + if( isKindOf( KINDOF_STRUCTURE ) ) + { + sound = TheAudio->getMiscAudio()->m_buildingReenabled; + sound.setPosition( getPosition() ); + TheAudio->addAudioEvent( &sound ); + } + else if( isKindOf( KINDOF_VEHICLE ) ) + { + sound = TheAudio->getMiscAudio()->m_vehicleReenabled; + sound.setPosition( getPosition() ); + TheAudio->addAudioEvent( &sound ); + } + } + } + + + // an edge-test for disabledness, for type. This DECREMENTS m_pauseCount + // srj sez: HELD nevers disables special powers. + if ( type != DISABLED_HELD && isDisabledByType( type ) ) + pauseAllSpecialPowers( FALSE ); + + ContainModuleInterface *contain = getContain(); + if ( contain ) + { + // We explicitly pass stuff in up in the set, so we need to turn it off if it is a forever type + Object *rider = (Object*)contain->friend_getRider(); + if( rider && (m_disabledTillFrame[ type ] == FOREVER) ) + { + rider->clearDisabled(type); + } + } + + if ( isKindOf( KINDOF_SPAWNS_ARE_THE_WEAPONS ) ) + { + SpawnBehaviorInterface *sbi = this->getSpawnBehaviorInterface(); + if ( sbi ) + { + //Kris: Patch 1.02 - December 17, 2003 + //Make sure slaves can recover from being disabled by subdual (stinger site soldier case) + sbi->orderSlavesToClearDisabled( type ); + } + + } + + m_disabledTillFrame[ type ] = NEVER; + m_disabledMask.set( type, 0 ); + + DisabledMaskType exceptions; + exceptions.set(DISABLED_HELD); + exceptions.set(DISABLED_SCRIPT_DISABLED); + exceptions.set(DISABLED_UNMANNED); + exceptions.set(DISABLED_TELEPORT); + + DisabledMaskType myFlagsMinusExceptions = getDisabledFlags(); + myFlagsMinusExceptions.clearAndSet(exceptions, DISABLEDMASK_NONE); + + // to clarify, if I am NOT disabled by anything other than DISABLED_HELD, or DISABLED_SCRIPT_DISABLED + + // to clarify, count inverse intersection gives you the number of exceptions you don't have, + // and has nothing to do with checking other disabled types +// if( !isDisabled() || getDisabledFlags().countInverseIntersection( exceptions ) == 0 ) + if( myFlagsMinusExceptions.count() == 0 ) + { + // I have no disabled flag that is not one of the exceptions above. + if (m_drawable) + m_drawable->clearTintStatus( TINT_STATUS_DISABLED ); + } + + checkDisabledStatus();// in case we just edged + + // if we're no longer disabled by anything, then call the edge function. + if (!isDisabled()) { + onDisabledEdge(false); + } + return TRUE; +} + + +//------------------------------------------------------------------------------------------------- +//Checks any timers and clears disabled statii that have expired. +//------------------------------------------------------------------------------------------------- +void Object::checkDisabledStatus() +{ + UnsignedInt now = TheGameLogic->getFrame(); + for( int i = 0; i < DISABLED_COUNT; i++ ) + { + DisabledType type = (DisabledType)i; + if( isDisabledByType( type ) ) + { + if ( now >= m_disabledTillFrame[ i ] ) + { + clearDisabled( type ); // This will also DECREMENT m_pauseCount in all specialpowers + m_disabledMask.set( type, 0 ); + } + } + } +} + +//------------------------------------------------------------------------------------------------- +void Object::pauseAllSpecialPowers( const Bool disabling ) const +{ + for (BehaviorModule** m = m_behaviors; *m; ++m) + { + SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); + if (!sp) + continue; + + sp->pauseCountdown( disabling );// So it will pause if we are disabling. + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +/** Clear the previous entered/exited flags. */ +//------------------------------------------------------------------------------------------------- +void Object::updateTriggerAreaFlags() +{ + Int j = 0; + // Update the flags, and remove any trigger areas that this object isn't inside. + for (Int i=0; igetCollide(); + if (!collide) + continue; + + // check each time thru the loop, in case a collide module sets it + if( getStatusBits().test( OBJECT_STATUS_NO_COLLISIONS ) ) + { +#ifdef DEBUG_CRC + //DEBUG_LOG(("Object::onCollide() - OBJECT_STATUS_NO_COLLISIONS set\n")); +#endif + break; + } +#ifdef DEBUG_CRC + //DEBUG_LOG(("Object::onCollide() - calling collide module\n")); +#endif + collide->onCollide(other, loc, normal); + } +} + +//------------------------------------------------------------------------------------------------- +Bool Object::isSalvageCrate() const +{ + for( BehaviorModule** m = m_behaviors; *m; ++m ) + { + CollideModuleInterface* collide = (*m)->getCollide(); + if( collide && collide->isSalvageCrateCollide() ) + { + return true; + } + } + return false; +} + +//------------------------------------------------------------------------------------------------- +/** + Our owning player is telling us to recheck our UpgradeModules, as an upgrade has completed + */ +void Object::updateUpgradeModules() +{ + if( testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) ) + return; // No upgrade can run if we are under construction. The three places that clear UnderConstruction will re-update us. + + if( testStatus( OBJECT_STATUS_DESTROYED ) ) + return; // Patch 1.03 -- Fixes crash when you upgrade a fake GLA command center to a real one if (toxic or demo). + + if( getControllingPlayer() == NULL ) + return; // This can only happen in game teardown. No upgrades for you without a player. Weird crashes are bad. + + UpgradeMaskType playerMask = getControllingPlayer()->getCompletedUpgradeMask(); + UpgradeMaskType objectMask = getObjectCompletedUpgradeMask(); + UpgradeMaskType maskToCheck = playerMask; + maskToCheck.set( objectMask ); + // We need to add in all of the already owned upgrades to handle "AND" requiring upgrades. + // We combine all the masks in case someone has a Object AND Player combination + + for (BehaviorModule** module = m_behaviors; *module; ++module) + { + UpgradeModuleInterface* upgrade = (*module)->getUpgrade(); + if (!upgrade) + continue; + + if( !upgrade->isAlreadyUpgraded() ) + { + upgrade->attemptUpgrade( maskToCheck ); + } + } +} + +//------------------------------------------------------------------------------------------------- +//This function sucks. +//It was added for objects that can disguise as other objects and contain upgraded subobject overrides. +//A concrete example is the bomb truck. Different payloads are displayed based on which upgrades have been +//made. When the bomb truck disguises as something else, these subobjects are lost because the vector is +//stored in W3DDrawModule. When we revert back to the original bomb truck, we call this function to +//recalculate those upgraded subobjects. +//------------------------------------------------------------------------------------------------- +void Object::forceRefreshSubObjectUpgradeStatus() +{ + for (BehaviorModule** module = m_behaviors; *module; ++module) + { + UpgradeModuleInterface* upgrade = (*module)->getUpgrade(); + if (!upgrade) + continue; + + if( upgrade->isSubObjectsUpgrade() ) + { + upgrade->forceRefreshUpgrade(); + } + } +} + +//------------------------------------------------------------------------------------------------- +/** Returns whether an object entered or exited an area. */ +//------------------------------------------------------------------------------------------------- +Bool Object::didEnterOrExit() const +{ + if (isKindOf(KINDOF_INERT)) { + return FALSE; + } + // note that this needs to return true if we + // entered or exited on the current frame OR + // the previous frame... since the current execution + // order is ScriptEngine, then ObjectUpdates, + // enter/exits detected in ObjectUpdate on frame N + // won't be noticed by the ScriptEngine till frame N+1. + UnsignedInt now = TheGameLogic->getFrame(); + return m_enteredOrExitedFrame == now || m_enteredOrExitedFrame == now - 1; +} + +//------------------------------------------------------------------------------------------------- +/** Returns whether an object entered an area. */ +//------------------------------------------------------------------------------------------------- +Bool Object::didEnter(const PolygonTrigger *pTrigger) const +{ + if (!didEnterOrExit()) + return false; + + DEBUG_ASSERTCRASH(!isKindOf(KINDOF_INERT), ("Asking whether an inert object entered or exited. This is invalid.\n")); + + for (Int i=0; igetUpdateExitInterface()) != NULL ) + break; + } + + // If you don't have a fancy one, you may have one from your contain module, + // since if you can contain something, they will need to get out. + if( exitInterface == NULL ) + { + ContainModuleInterface *cmod = getContain(); + if( cmod ) + { + exitInterface = cmod->getContainExitInterface(); + } + } + + return exitInterface; + +} // end getObjectExitInterface + +//------------------------------------------------------------------------------------------------- +/** Checks the object against trigger areas when the position changes. */ +//------------------------------------------------------------------------------------------------- +void Object::setTriggerAreaFlagsForChangeInPosition() +{ + // projectiles cannot trigger areas. (jkmcd) + // neither can inert objects, like the radar ping, etc. (jkmcd) + if (isKindOf(KINDOF_PROJECTILE) || isKindOf(KINDOF_INERT)) + return; + + ICoord3D iPos; + Coord3D pos = *getPosition(); + iPos.x = REAL_TO_INT(pos.x); + iPos.y = REAL_TO_INT(pos.y); + iPos.z = 0; // Trigger areas compare on xy only. + if (m_iPos.x == iPos.x && m_iPos.y == iPos.y) + { + return; // didn't move enough to change integer position. + } + + if (!isKindOf(KINDOF_IMMOBILE)) { + if (isKindOf(KINDOF_INFANTRY) || isKindOf(KINDOF_VEHICLE) ) { + TheGameClient->notifyTerrainObjectMoved(this); + } + } + + if (getAIUpdateInterface()) + { + TheAI->pathfinder()->updatePos(this, getPosition()); + } + + UnsignedInt now = TheGameLogic->getFrame(); + if (m_enteredOrExitedFrame != 0 && m_enteredOrExitedFrame != now) + updateTriggerAreaFlags(); + + // Check for exited. + Int i; + for (i=0; ipointInTrigger(m_iPos)) + { + m_triggerInfo[i].isInside = false; + m_triggerInfo[i].exited = true; + m_enteredOrExitedFrame = now; + if (m_team) + m_team->setEnteredExited(); + TheGameLogic->updateObjectsChangedTriggerAreas(); +#ifdef RTS_DEBUG + //TheScriptEngine->AppendDebugMessage("Object exited.", false); +#endif + } + } + + m_iPos = iPos; + + for (const PolygonTrigger *pTrig = PolygonTrigger::getFirstPolygonTrigger(); pTrig; pTrig = pTrig->getNext()) + { + Bool skip = false; + for (i = 0; i < m_numTriggerAreasActive; i++) + { + if (m_triggerInfo[i].pTrigger == pTrig) + { + // Already handled this one in the check for exited above. + skip = true; + break; + } + } + if (skip) + continue; + if (pTrig->pointInTrigger(m_iPos)) + { + if (m_numTriggerAreasActive < MAX_TRIGGER_AREA_INFOS) + { + m_triggerInfo[m_numTriggerAreasActive].isInside = true; + m_triggerInfo[m_numTriggerAreasActive].entered = true; + m_triggerInfo[m_numTriggerAreasActive].exited = false; + m_triggerInfo[m_numTriggerAreasActive].pTrigger = pTrig; + m_enteredOrExitedFrame = now; + if (m_team) + m_team->setEnteredExited(); + TheGameLogic->updateObjectsChangedTriggerAreas(); + ++m_numTriggerAreasActive; +#ifdef RTS_DEBUG + //TheScriptEngine->AppendDebugMessage("Object entered.", false); +#endif + } + else + { + // Shouldn't happen. + static Bool didWarn = false; + if (!didWarn) + { + didWarn = true; + TheScriptEngine->AppendDebugMessage("***WARNING - Too many nested trigger areas. ***", true); + } + } + } + + } + +} + + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool Object::isInList(Object **pListHead) const +{ + Bool result = m_prev || m_next || *pListHead == this; +#ifdef INTENSE_DEBUG + Bool found = false; + for (Object* o = *pListHead; o; o = o->m_next) + { + if (o == this) + { + found = true; + break; + } + } + DEBUG_ASSERTCRASH(found==result,("inconsistent links in Object::isInList")); +#endif + return result; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void Object::prependToList(Object **pListHead) +{ + DEBUG_ASSERTCRASH(!isInList(pListHead), ("obj is already in a list")); + + m_prev = NULL; + m_next = *pListHead; + if (*pListHead) + (*pListHead)->m_prev = this; + *pListHead = this; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void Object::setLayer(PathfindLayerEnum layer) +{ + if (layer!=m_layer) { +#define no_SET_LAYER_INTENSE_DEBUG +#ifdef SET_LAYER_INTENSE_DEBUG + DEBUG_LOG(("Changing layer from %d to %d\n", m_layer, layer)); + if (m_layer != LAYER_GROUND) { + if (TheTerrainLogic->objectInteractsWithBridgeLayer(this, m_layer)) { + DEBUG_CRASH(("Probably shouldn't be chaging layer. jba.")); + } + } +#endif + TheAI->pathfinder()->removePos(this); + m_layer = layer; + TheAI->pathfinder()->updatePos(this, getPosition()); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void Object::setDestinationLayer(PathfindLayerEnum layer) +{ + if (layer!=m_destinationLayer) { + m_destinationLayer = layer; + } +} + +// ------------------------------------------------------------------------------------------------ +/** Set unique ID */ +// ------------------------------------------------------------------------------------------------ +void Object::setID( ObjectID id ) +{ + + // sanity + DEBUG_ASSERTCRASH( id != INVALID_ID, ("Object::setID - Invalid id\n") ); + + // if id hasn't changed do nothing + if( m_id == id ) + return; + + // remove this objects previous id from the lookup table + TheGameLogic->removeObjectFromLookupTable( this ); + + // assign new id + m_id = id; + + // add new id to lookup table + TheGameLogic->addObjectToLookupTable( this ); + +} // end setID + +// ------------------------------------------------------------------------------------------------ +Real Object::calculateHeightAboveTerrain(void) const +{ + const Coord3D* pos = getPosition(); + Real terrainZ = TheTerrainLogic->getLayerHeight( pos->x, pos->y, m_layer ); + Real myZ = pos->z; + return myZ - terrainZ; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void Object::removeFromList(Object **pListHead) +{ + if (m_next) + m_next->m_prev = m_prev; + + if (m_prev) + m_prev->m_next = m_next; + else + *pListHead = m_next; + + m_prev = NULL; + m_next = NULL; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void Object::friend_prepareForMapBoundaryAdjust(void) +{ + // NOTE - DO NOT remove from pathfind map. jba. + // NO NO. jba. TheAI->pathfinder()->removeObjectFromPathfindMap( this ); + + // remove from the radar, remove from the partition manager + TheRadar->removeObject(this); + ThePartitionManager->unRegisterObject(this); + + // The whole PartitionManager and all of the Looker data is about to be blown away, + // so forget what I think I have done + m_partitionLastLook->reset(); + m_partitionRevealAllLastLook->reset(); + m_partitionLastShroud->reset(); + + m_partitionLastThreat->reset(); + m_partitionLastValue->reset(); + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void Object::friend_notifyOfNewMapBoundary(void) +{ + ThePartitionManager->registerObject(this); + TheRadar->addObject(this); + TheAI->pathfinder()->addObjectToPathfindMap( this ); + + // Now that the PartitionManager has finished its reset, we need to relook + handlePartitionCellMaintenance(); + + Region3D mapExtent; + TheTerrainLogic->getExtent(&mapExtent); + if (mapExtent.isInRegionNoZ(getPosition())) + m_privateStatus &= ~OFF_MAP; + else + m_privateStatus |= OFF_MAP; +} + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void Object::calcNaturalRallyPoint(Coord2D *pt) +{ + const Matrix3D *transform = getTransformMatrix(); + Vector3 v; + + // + // get the natural rally point from the template, this coord is in model space relative + // to the model (0,0,0) + // +/* + const Coord3D *naturalRallyPoint; + naturalRallyPoint = m_template->getNaturalRallyPoint(); + v.X = naturalRallyPoint->x; + v.Y = naturalRallyPoint->y; + v.Z = naturalRallyPoint->z; +*/ + v.Set( 0, 0, 0 ); + + // transform the point into world space + transform->Transform_Vector( *transform, v, &v ); + + // we're only concerned with the 2D elements for now + pt->x = v.X; + pt->y = v.Y; + +} + +//------------------------------------------------------------------------------------------------- +Module* Object::findModule(NameKeyType key) const +{ + Module* m = NULL; + + for (BehaviorModule** b = m_behaviors; *b; ++b) + { + if ((*b)->getModuleNameKey() == key) + { +#ifdef INTENSE_DEBUG + if (m == NULL) + { + m = *b; + } + else + { + DEBUG_CRASH(("Duplicate modules found for name %s!\n",TheNameKeyGenerator->keyToName(key).str())); + } +#else + m = *b; + break; +#endif + } + } + + return m; +} + +//------------------------------------------------------------------------------------------------- +/** + * Returns true if object is currently able to move. + */ +Bool Object::isMobile() const +{ + if (isKindOf(KINDOF_IMMOBILE)) + return false; + + // AW: This excemption is needed, because teleporters still need to listen to AI commands when disabled + if( isDisabled() && !isDisabledByType(DISABLED_TELEPORT) ) + return false; + + return true; +} + +//------------------------------------------------------------------------------------------------- +void Object::scoreTheKill( const Object *victim ) +{ + // Do stuff that has nothing to do with experience points here, like tell our Player we killed something + /// @todo Multiplayer score hook location? + + Player* victimController = victim->getControllingPlayer(); + // if the other player is not a playable side (i.e. they are civilian, observer, whatever) + // we shouldn't count the kill. + if (victimController->isPlayableSide() == FALSE) + { + return; + } + + + if ( victim->isKindOf( KINDOF_IGNORED_IN_GUI ) ) + return; + + + Player* controller = getControllingPlayer(); + + if (victimController) + { + victimController->getScoreKeeper()->addObjectLost(victim); + } + + Relationship r = getRelationship(victim); + if (r != ENEMIES) + return; + + // Don't count kills that I do on my own buildings or units, cause thats just silly. + if (controller == victimController) + { + return; + } + + if (controller) + { + controller->getScoreKeeper()->addObjectDestroyed(victim); + controller->addSkillPointsForKill(this, victim); + controller->doBountyForKill(this, victim); + } + + // Now handle experience, if we can gain any + if (m_experienceTracker && m_experienceTracker->isAcceptingExperiencePoints()) + { + // srj sez: per dustin, no experience (et al) for killing things under construction. + if (!victim->testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION)) + { + Int experienceValue = victim->getExperienceTracker()->getExperienceValue( this ); + getExperienceTracker()->addExperiencePoints( experienceValue ); + } + } +} + +//------------------------------------------------------------------------------------------------- +VeterancyLevel Object::getVeterancyLevel() const +{ + return m_experienceTracker ? m_experienceTracker->getVeterancyLevel() : LEVEL_REGULAR; +} + +//------------------------------------------------------------------------------------------------- +void Object::friend_bindToDrawable( Drawable *draw ) +{ + m_drawable = draw; + if (m_drawable) + { + ModelConditionFlags set; + ModelConditionFlags clr; + for (int i = 0; i < WEAPONSET_COUNT; ++i) + { + ModelConditionFlagType mcs = TheWeaponSetTypeToModelConditionTypeMap[i]; + if( mcs != MODELCONDITION_INVALID ) + { + if (m_curWeaponSetFlags.test(i)) + set.set(mcs); + else + clr.set(mcs); + } + } + if (TheGlobalData) + { + if (TheGlobalData->m_forceModelsToFollowTimeOfDay) + { + set.set(MODELCONDITION_NIGHT, (TheGlobalData->m_timeOfDay == TIME_OF_DAY_NIGHT) ? 1 : 0); + } + + if (TheGlobalData->m_forceModelsToFollowWeather) + { + set.set(MODELCONDITION_SNOW, (TheGlobalData->m_weather == WEATHER_SNOWY) ? 1 : 0); + } + } + m_drawable->clearAndSetModelConditionFlags(clr, set); + } + + for (BehaviorModule** b = m_behaviors; *b; ++b) + { + (*b)->onDrawableBoundToObject(); + } +} + +//------------------------------------------------------------------------------------------------- +void Object::setSelectable(Bool selectable) +{ + m_isSelectable = selectable; + if (m_drawable) + { + m_drawable->setSelectable(selectable); + } +} + +//------------------------------------------------------------------------------------------------- +Bool Object::isSelectable() const +{ +// return getTemplate()->isKindOf(KINDOF_ALWAYS_SELECTABLE) +// || (m_isSelectable +// && !testStatus(OBJECT_STATUS_UNSELECTABLE) +// && !isEffectivelyDead() +// && !getTemplate()->isKindOf(KINDOF_DRONE)//Most drones are unselectable from being slaved, but the SpyDrone needs help +// ); + + + if (getTemplate()->isKindOf(KINDOF_ALWAYS_SELECTABLE)) + return TRUE; + + if ( m_isSelectable ) + if ( !testStatus(OBJECT_STATUS_UNSELECTABLE) ) + if ( !isEffectivelyDead() ) + //if ( !getTemplate()->isKindOf(KINDOF_DRONE) )//Most drones are unselectable from being slaved, but the SpyDrone needs help + return TRUE; + + return FALSE; +} + +//------------------------------------------------------------------------------------------------- +Bool Object::isMassSelectable() const +{ + return isSelectable() && !isKindOf(KINDOF_STRUCTURE); +} + +//------------------------------------------------------------------------------------------------- +void Object::setWeaponSetFlag(WeaponSetType wst) +{ + m_curWeaponSetFlags.set(wst); + m_weaponSet.updateWeaponSet(this); + if (m_drawable) + { + m_drawable->setModelConditionState(TheWeaponSetTypeToModelConditionTypeMap[wst]); + } +} + +//------------------------------------------------------------------------------------------------- +void Object::clearWeaponSetFlag(WeaponSetType wst) +{ + m_curWeaponSetFlags.set(wst, 0); + m_weaponSet.updateWeaponSet(this); + if (m_drawable) + { + m_drawable->clearModelConditionState(TheWeaponSetTypeToModelConditionTypeMap[wst]); + } +} + +//------------------------------------------------------------------------------------------------- +Bool Object::hasSpecialPower( SpecialPowerType type ) const +{ + return TEST_SPECIALPOWERMASK( m_specialPowerBits, type ); +} + +//------------------------------------------------------------------------------------------------- +Bool Object::hasAnySpecialPower() const +{ + return SPECIALPOWERMASK_ANY_SET( m_specialPowerBits ); +} + +//------------------------------------------------------------------------------------------------- +void Object::onVeterancyLevelChanged( VeterancyLevel oldLevel, VeterancyLevel newLevel, Bool provideFeedback ) +{ + updateUpgradeModules(); + + const UpgradeTemplate* up = TheUpgradeCenter->findVeterancyUpgrade(newLevel); + if (up) + giveUpgrade(up); + + BodyModuleInterface* body = getBodyModule(); + if (body) + body->onVeterancyLevelChanged( oldLevel, newLevel, provideFeedback ); + + Bool hideAnimationForStealth = FALSE; + if( !isLocallyControlled() && + testStatus( OBJECT_STATUS_STEALTHED ) && + !testStatus( OBJECT_STATUS_DETECTED ) && + !testStatus( OBJECT_STATUS_DISGUISED ) ) + { + hideAnimationForStealth = TRUE; + } + + Bool doAnimation = ( ! hideAnimationForStealth + && (newLevel > oldLevel) + && ( ! isKindOf(KINDOF_IGNORED_IN_GUI))); //First, we plan to do the animation if the level went up + + switch (newLevel) + { + case LEVEL_REGULAR: + clearWeaponSetFlag(WEAPONSET_VETERAN); + clearWeaponSetFlag(WEAPONSET_ELITE); + clearWeaponSetFlag(WEAPONSET_HERO); + clearWeaponBonusCondition(WEAPONBONUSCONDITION_VETERAN); + clearWeaponBonusCondition(WEAPONBONUSCONDITION_ELITE); + clearWeaponBonusCondition(WEAPONBONUSCONDITION_HERO); + doAnimation = FALSE;//... but not if somehow up to Regular + break; + case LEVEL_VETERAN: + setWeaponSetFlag(WEAPONSET_VETERAN); + clearWeaponSetFlag(WEAPONSET_ELITE); + clearWeaponSetFlag(WEAPONSET_HERO); + setWeaponBonusCondition(WEAPONBONUSCONDITION_VETERAN); + clearWeaponBonusCondition(WEAPONBONUSCONDITION_ELITE); + clearWeaponBonusCondition(WEAPONBONUSCONDITION_HERO); + break; + case LEVEL_ELITE: + clearWeaponSetFlag(WEAPONSET_VETERAN); + setWeaponSetFlag(WEAPONSET_ELITE); + clearWeaponSetFlag(WEAPONSET_HERO); + clearWeaponBonusCondition(WEAPONBONUSCONDITION_VETERAN); + setWeaponBonusCondition(WEAPONBONUSCONDITION_ELITE); + clearWeaponBonusCondition(WEAPONBONUSCONDITION_HERO); + break; + case LEVEL_HEROIC: + clearWeaponSetFlag(WEAPONSET_VETERAN); + clearWeaponSetFlag(WEAPONSET_ELITE); + setWeaponSetFlag(WEAPONSET_HERO); + clearWeaponBonusCondition(WEAPONBONUSCONDITION_VETERAN); + clearWeaponBonusCondition(WEAPONBONUSCONDITION_ELITE); + setWeaponBonusCondition(WEAPONBONUSCONDITION_HERO); + break; + } + + if( doAnimation && TheGameLogic->getDrawIconUI() && provideFeedback ) + { + if( TheAnim2DCollection && TheGlobalData->m_levelGainAnimationName.isEmpty() == FALSE ) + { + Anim2DTemplate *animTemplate = TheAnim2DCollection->findTemplate( TheGlobalData->m_levelGainAnimationName ); + + Coord3D pos = *getPosition(); + pos.add(&m_healthBoxOffset); + + TheInGameUI->addWorldAnimation( animTemplate, + &pos, + WORLD_ANIM_FADE_ON_EXPIRE, + TheGlobalData->m_levelGainAnimationDisplayTimeInSeconds, + TheGlobalData->m_levelGainAnimationZRisePerSecond); + } + + AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_unitPromoted; + soundToPlay.setObjectID( getID() ); + TheAudio->addAudioEvent( &soundToPlay ); + } + +} + +//------------------------------------------------------------------------------------------------- +/** + * Returns true if object currently has some kind of attack capability + */ +Bool Object::isAbleToAttack() const +{ + + //****************************************************** + //********* AUTOMATICALLY FALSE CONDITIONS ************* + //****************************************************** + + // For things that may or may not be able to normally attack, but are under a status condition + if( getStatusBits().test( OBJECT_STATUS_NO_ATTACK ) ) + return false; + + // if we're contained within a transport we cannot attack unless it specifically allows us + const Object *containedBy = getContainedBy(); + DEBUG_ASSERTCRASH( (containedBy == NULL) || (containedBy->getContain() != NULL), ("A %s thinks they are contained by something with no contain module!", getTemplate()->getName().str() ) ); + if( containedBy && containedBy->getContain() && !containedBy->getContain()->isPassengerAllowedToFire( getID() ) ) + return false; + + + // We can't fire if under construction + if( testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) ) + return false; + + // or being sold + if( testStatus(OBJECT_STATUS_SOLD) ) + return false; + + if ( isDisabledByType( DISABLED_SUBDUED ) ) + return FALSE; // A Microwave Tank is cooking me + + //We can't fire if we, as a portable structure, are aptly disabled + if ( isKindOf( KINDOF_PORTABLE_STRUCTURE ) || isKindOf( KINDOF_SPAWNS_ARE_THE_WEAPONS )) + { + if( isDisabledByType( DISABLED_HACKED ) || isDisabledByType( DISABLED_EMP ) ) + return false; + + if ( isKindOf( KINDOF_INFANTRY ) ) // I must be a stinger soldier or similar + { + for (BehaviorModule** update = getBehaviorModules(); *update; ++update)//expensive search, limited only to stinger soldiers + { + SlavedUpdateInterface* sdu = (*update)->getSlavedUpdateInterface(); + if ( sdu ) + { + ObjectID slaverID = sdu->getSlaverID(); + if ( slaverID != INVALID_ID ) + { + Object *slaver = TheGameLogic->findObjectByID( slaverID ); + if ( slaver && slaver->isDisabledByType( DISABLED_SUBDUED )) + return FALSE;// if my stinger site is subdued, so am I + } + + break;//only expect one slavedupdate, so stop searching + } + } + } + + + } + + + + //We can't fire if all our weapons are disabled! + //Currently, only turreted weapons can be disabled. + //ONLY DO THIS CHECK IF OUR UNIT DOESN'T HAVE THE + //KINDOF_CAN_ATTACK flag... nuke cannons have disabled + //turrets when not deployed, and need to be able to attack to deploy! + //Strategy centers can't attack when bombardment isn't active! + Bool anyEnabled = FALSE; + Bool anyWeapon = FALSE; + const AIUpdateInterface *ai = getAI(); + if( ai && !isKindOf( KINDOF_CAN_ATTACK ) ) + { + for( Int i = 0; i < WEAPONSLOT_COUNT; i++ ) + { + //Find the weapon in this slot. + Weapon* weapon = getWeaponInWeaponSlot( (WeaponSlotType)i ); + if( !weapon ) + continue; + + anyWeapon = TRUE; + + //We found a weapon, is it a turret? + Real dummy; + WhichTurretType tur = ai->getWhichTurretForWeaponSlot( (WeaponSlotType)i, &dummy ); + if( tur == TURRET_INVALID ) + { + //Currently impossible to disable a non-turreted weapon, so we + //have a non turreted weapon that is enabled. Quit. + anyEnabled = TRUE; + break; + } + + if( ai->isTurretEnabled( tur ) ) + { + //The turret is enable, meaning we have an enabled weapon. Quit. + anyEnabled = TRUE; + break;; + } + } + if( anyWeapon && !anyEnabled ) + { + //We failed to find any active weapons. + return FALSE; + } + } + + + //*************************************** + //********* TRUE CONDITIONS ************* + //*************************************** + + // for certain buildings + if (isKindOf(KINDOF_CAN_ATTACK)) + return true; + + // for garrisonned buildings that can attack sometimes + if( getStatusBits().test( OBJECT_STATUS_CAN_ATTACK ) ) + return true; + + // for weaponless transports. This will make me think I can, but I will check if I literally can by looking + // at passenger weapons in CanAttack. + const ContainModuleInterface* contain = getContain(); + if( contain && contain->isPassengerAllowedToFire( getID() ) && contain->getContainCount() > 0 ) + return true; + + // if we have AI and a weapon, assume we know how to use it + if (getAIUpdateInterface() != NULL && m_weaponSet.hasAnyWeapon()) + { + +// actually, we don't want to do this; we want the troop crawler to be considered "able to attack" +// even if empty, so sayeth Dustin. (srj) +// // special case: if the only damage we do is DEPLOY, we must have some guys contained. +// if (m_weaponSet.hasSingleDamageType(DAMAGE_DEPLOY)) +// { +// return contain->getContainCount() > 0; +// } +// else + { + return true; + } + } + + SpawnBehaviorInterface *spawnInterface = getSpawnBehaviorInterface(); + if( spawnInterface ) + { + if( spawnInterface->canAnySlavesAttack() ) + { + return TRUE; + } + } + + if (getTemplate()->isEnterGuard()) + return TRUE; + +//Default is no + return false; +} + +//------------------------------------------------------------------------------------------------- +/** + * Mask/Un-Mask an object + */ +void Object::maskObject( Bool mask ) +{ + + // set or clear the mask bit + setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ), mask ); + + // + // when masking objects they become unselected ... we do this in any situation for + // any player cause you aren't allowed to select masked objects, if the object is not + // selected (ie, belongs to another player) it's no big deal cause it won't be selected + // anyway + // + + if (mask) + TheGameLogic->deselectObject(this, ~getControllingPlayer()->getPlayerMask(), TRUE); + +} // end maskObject + +//------------------------------------------------------------------------------------------------- +/* + * returns true if the current locomotor is an airborne one + */ +Bool Object::isUsingAirborneLocomotor( void ) const +{ + return ( m_ai && m_ai->getCurLocomotor() && ((m_ai->getCurLocomotor()->getLegalSurfaces() & LOCOMOTORSURFACE_AIR) != 0) ); +} + +//------------------------------------------------------------------------------------------------- +//THIS FUNCTION BELONGS AT THE OBJECT LEVEL BECAUSE THERE IS AT LEAST ONE SPECIAL UNIT +//(ANGRY MOB) WHICH NEEDS LOGIC-SIDE POSITION CALC'S... +//IT WOULD PROBABLY BE WISE TO MOVE ALL THE HARD-CODED DEFAULTS BELOW +//INTO A NEW Drawable::getHealthBox..() WHICH USES GEOM0INFO, MODEL DATA, INI DATA, ETC. +void Object::getHealthBoxPosition(Coord3D& pos) const +{ + pos = *getPosition(); + pos.z += getGeometryInfo().getMaxHeightAbovePosition() + 10; + pos.add(&m_healthBoxOffset); + + // this needs to get moved to the mobspawnerupdate + if (isKindOf(KINDOF_MOB_NEXUS)) // quicker idiot test + { + pos.z += 20;// dear God, I confess my kluge, and repent. + } +} + +//------------------------------------------------------------------------------------------------- +//THIS FUNCTION BELONGS AT THE OBJECT LEVEL BECAUSE THERE IS AT LEAST ONE SPECIAL UNIT +//(ANGRY MOB) WHICH NEEDS LOGIC-SIDE POSITION CALC'S... +//IT WOULD PROBABLY BE WISE TO MOVE ALL THE HARD-CODED DEFAULTS BELOW +//INTO A NEW Drawable::getHealthBox..() WHICH USES GEOM0INFO, MODEL DATA, INI DATA, ETC. +Bool Object::getHealthBoxDimensions(Real &healthBoxHeight, Real &healthBoxWidth) const +{ + +#ifdef CALC_HEALTHBAR_FROM_HITPOINTS + Real maxHP = getBodyModule()->getMaxHealth(); + + if( isKindOf( KINDOF_STRUCTURE ) ) + { + //enforce healthBoxHeightMinimum/Maximum + healthBoxHeight = min(3.0f, max(5.0f, maxHP/50)); + //enforce healthBoxWidthMinimum/Maximum + healthBoxWidth = min(150.0f, max(100.0f, maxHP/10)); + return true; + } + else if ( isKindOf(KINDOF_MOB_NEXUS) ) + { + //enforce healthBoxHeightMinimum/Maximum + healthBoxHeight = min(3.0f, max(5.0f, maxHP/50)); + //enforce healthBoxWidthMinimum/Maximum + healthBoxWidth = min(100.0f, max(66.0f, maxHP/10)); + return true; + } + else if ( isKindOf( KINDOF_IGNORED_IN_GUI ) ) + { + healthBoxHeight = 0; + healthBoxWidth = 0; + return false; + } + else + { + //enforce healthBoxHeightMinimum/Maximum + healthBoxHeight = min(3.0f, max(5.0f, maxHP/50)); + //enforce healthBoxWidthMinimum/Maximum + healthBoxWidth = min(150.0f, max(35.0f, maxHP/10)); + return true; + } +#else + + if ( isKindOf( KINDOF_IGNORED_IN_GUI ) ) + { + healthBoxHeight = 0; + healthBoxWidth = 0; + return false; + } + + //just add the major and minor axes + Real size = MAX(20.0f, MIN(150.0f, (getGeometryInfo().getMajorRadius() + getGeometryInfo().getMinorRadius())) ); + healthBoxHeight = 3.0f; + healthBoxWidth = MAX(20.0f, size * 2.0f); + return TRUE; + +#endif + +} + + +//------------------------------------------------------------------------------------------------- +/** + * Update this object instance with properties from the map object + * + */ +void Object::updateObjValuesFromMapProperties(Dict* properties) +{ + Bool exists; + + AsciiString valStr; + Bool valBool = false; + Int valInt = 0; + Real valReal = 0.0f; + + valStr = properties->getAsciiString(TheKey_objectName, &exists); + if (exists) { + setName(valStr); + } + + valInt = properties->getInt(TheKey_objectMaxHPs, &exists); + if (exists && valInt >= 0) { + BodyModuleInterface* body = getBodyModule(); + if (body) { + body->setMaxHealth(valInt); + } + } + + valInt = properties->getInt(TheKey_objectInitialHealth, &exists); + if (exists) { + BodyModuleInterface* body = getBodyModule(); + if (body) { + body->setInitialHealth(valInt); + } + } + + // set the veterancy level + valInt = properties->getInt(TheKey_objectVeterancy, &exists); + if (exists) { + if (m_experienceTracker && m_experienceTracker->isTrainable()) + { + m_experienceTracker->setVeterancyLevel((VeterancyLevel)valInt); + } + } + + // set the aggressiveness/mood + valInt = properties->getInt(TheKey_objectAggressiveness, &exists); + if (exists) { + AIUpdateInterface *ai = getAIUpdateInterface(); + if (ai) + { + ai->setAttitude((AttitudeType)valInt); + } + } + + // set recruitable + valBool = properties->getBool(TheKey_objectRecruitableAI, &exists); + if (exists) { + if (getAIUpdateInterface()) + { + getAIUpdateInterface()->setIsRecruitable(valBool); + } + } + + // set selectable + valBool = properties->getBool(TheKey_objectSelectable, &exists); + if (exists) { + if (valBool != isSelectable()) { + setSelectable(valBool); + } + } + + // set the stopping distance + valReal = properties->getReal(TheKey_objectStoppingDistance, &exists); + if (exists && valReal >= 0.5f) + { + if (getAIUpdateInterface() && getAIUpdateInterface()->getCurLocomotor()) + { + Locomotor *loco = getAIUpdateInterface()->getCurLocomotor(); + loco->setCloseEnoughDist(valReal); + } + } + + // set the disabledness of this object + valBool = properties->getBool(TheKey_objectEnabled, &exists); + if (exists) { + setScriptStatus(OBJECT_STATUS_SCRIPT_DISABLED, !valBool); + } + + // set the disabledness of this object + valBool = properties->getBool(TheKey_objectPowered, &exists); + if (exists) { + setScriptStatus(OBJECT_STATUS_SCRIPT_UNPOWERED, !valBool); + } + + // set the invulnerability of the object + valBool = properties->getBool(TheKey_objectIndestructible, &exists); + if (exists) { + BodyModuleInterface* body = getBodyModule(); + if (body) { + body->setIndestructible(valBool); + } + } + + // set the sellability of the object + valBool = properties->getBool(TheKey_objectUnsellable, &exists); + if (exists) { + setScriptStatus(OBJECT_STATUS_SCRIPT_UNSELLABLE, valBool); + } + + //Set the player targetable setting of the object + valBool = properties->getBool( TheKey_objectTargetable, &exists ); + if( exists ) + { + setScriptStatus(OBJECT_STATUS_SCRIPT_TARGETABLE, valBool); + } + + // adjust the vision distance of this object, overriding its default vision distance + valInt = properties->getInt(TheKey_objectVisualRange, &exists); + if (exists) + { + if (valInt < 0) + valInt = 0; + m_visionRange = INT_TO_REAL(valInt); + } + + // adjust the shroud clearing distance of this object, overriding its default distance + valInt = properties->getInt(TheKey_objectShroudClearingDistance, &exists); + if (exists) + { + if (valInt < 0) + valInt = 0.0f; + m_shroudClearingRange = INT_TO_REAL(valInt); + } + + + Int upgradeNum = 0; + do + { + AsciiString keyName; + keyName.format("%s%d", TheNameKeyGenerator->keyToName(TheKey_objectGrantUpgrade).str(), upgradeNum); + valStr = properties->getAsciiString(NAMEKEY(keyName), &exists); + + if (exists) + { + const UpgradeTemplate *ut = TheUpgradeCenter->findUpgrade(valStr); + if (ut) + giveUpgrade(ut); + } + else + { + valStr.clear(); + } + + ++upgradeNum; + } while (!valStr.isEmpty()); + + Drawable *drawable = getDrawable(); + if ( drawable ) + { + valInt = properties->getInt(TheKey_objectTime, &exists); + if (exists) + { + switch (valInt) + { + case 1: + drawable->clearModelConditionState(MODELCONDITION_NIGHT); + break; + case 2: + drawable->setModelConditionState(MODELCONDITION_NIGHT); + break; + default: + break; + } + } + + valInt = properties->getInt(TheKey_objectWeather, &exists); + if (exists) + { + switch (valInt) + { + case 1: + drawable->clearModelConditionState(MODELCONDITION_SNOW); + break; + case 2: + drawable->setModelConditionState(MODELCONDITION_SNOW); + break; + default: + break; + } + } + + // See if we are supposed to playing the ambient sound + Bool soundEnabledExists; + Bool soundEnabled = properties->getBool( TheKey_objectSoundAmbientEnabled, &soundEnabledExists ); + + DynamicAudioEventInfo * audioToModify = NULL; + Bool infoModified = false; + valStr = properties->getAsciiString( TheKey_objectSoundAmbient, &exists ); + if ( exists ) + { + if ( valStr.isEmpty() ) + { + drawable->setCustomSoundAmbientOff(); + soundEnabledExists = true; + soundEnabled = false; // Don't bother trying to enable later + } + else + { + const AudioEventInfo * baseInfo = TheAudio->findAudioEventInfo( valStr ); + DEBUG_ASSERTCRASH( baseInfo != NULL, ("Cannot find customized ambient sound '%s'", valStr.str() ) ); + if ( baseInfo != NULL ) + { + audioToModify = newInstance( DynamicAudioEventInfo )( *baseInfo ); + infoModified = true; + } + } + } + + // Don't do anything more to audio if we forced the ambient sound off + if ( !( exists && valStr.isEmpty() ) ) + { + valBool = properties->getBool( TheKey_objectSoundAmbientCustomized, &exists ); + if ( exists && valBool ) + { + if ( audioToModify == NULL ) + { + const AudioEventInfo * baseInfo = drawable->getBaseSoundAmbientInfo( ); + DEBUG_ASSERTCRASH( baseInfo != NULL, ("getBaseSoundAmbientInfo() return NULL" ) ); + if ( baseInfo != NULL ) + { + audioToModify = newInstance( DynamicAudioEventInfo )( *baseInfo ); + } + } + + if ( audioToModify != NULL ) + { + valBool = properties->getBool( TheKey_objectSoundAmbientLooping, &exists ); + if ( exists ) + { + audioToModify->overrideLoopFlag( valBool ); + infoModified = true; + } + + valInt = properties->getInt( TheKey_objectSoundAmbientLoopCount, &exists ); + if ( exists && BitIsSet( audioToModify->m_control, AC_LOOP ) ) + { + audioToModify->overrideLoopCount( valInt ); + infoModified = true; + } + + valReal = properties->getReal( TheKey_objectSoundAmbientMinVolume, &exists ); + if ( exists ) + { + audioToModify->overrideMinVolume( valReal ); + infoModified = true; + } + + valReal = properties->getReal( TheKey_objectSoundAmbientVolume, &exists ); + if ( exists ) + { + audioToModify->overrideVolume( valReal ); + infoModified = true; + } + + valReal = properties->getReal( TheKey_objectSoundAmbientMinRange, &exists ); + if ( exists ) + { + audioToModify->overrideMinRange( valReal ); + infoModified = true; + } + + valReal = properties->getReal( TheKey_objectSoundAmbientMaxRange, &exists ); + if ( exists ) + { + audioToModify->overrideMaxRange( valReal ); + infoModified = true; + } + + valInt = properties->getInt( TheKey_objectSoundAmbientPriority, &exists ); + if ( exists ) + { + audioToModify->overridePriority ( (AudioPriority)valInt ); + infoModified = true; + } + } + } + } + + if ( !soundEnabledExists ) + { + // Decide if the sound should start enabled or not, since the map maker didn't record + // a preference. Enable permanently looping sounds, disable one-shot sounds by default + // NOTE: This test should match the tests done in MapObjectProps::mapObjectPageSound::dictToEnabled() + // when it decided whether or not to show a customized sound as enabled + if ( audioToModify != NULL ) + { + soundEnabled = audioToModify->isPermanentSound(); + soundEnabledExists = true; // To get into enableAmbientSoundFromScript() call. + } + else + { + // Use default audio + const AudioEventInfo * baseInfo = drawable->getBaseSoundAmbientInfo( ); + if ( baseInfo != NULL ) + { + soundEnabled = baseInfo->isPermanentSound(); + soundEnabledExists = true; // To get into enableAmbientSoundFromScript() call. + } + } + } + + if ( soundEnabledExists && !soundEnabled ) + { + // Make sure sound doesn't start playing when we set it + // ...FromScript because this is also controlled by the map designer not the game logic + drawable->enableAmbientSoundFromScript( false ); + } + + if ( infoModified && audioToModify != NULL ) + { + // Give a custom, level-specific name + drawable->mangleCustomAudioName( audioToModify ); + + // Pass to TheAudio + TheAudio->addAudioEventInfo( audioToModify ); + + drawable->setCustomSoundAmbientInfo( audioToModify ); + audioToModify = NULL; // Belongs to TheAudio now + } + + if ( audioToModify != NULL ) + { + audioToModify->deleteInstance(); + audioToModify = NULL; + } + + if ( soundEnabledExists && soundEnabled ) + { + // Play sound now that it is set up, if needed. Don't call if already enabled because that + // can cause sound to play twice + // ...FromScript because this is also controlled by the map designer not the game logic + if ( !drawable->getAmbientSoundEnabledFromScript() ) + { + drawable->enableAmbientSoundFromScript( true ); + } + } + } +} + +//------------------------------------------------------------------------------------------------- +void Object::friend_adjustPowerForPlayer( Bool incoming ) +{ + if (isDisabled() && getTemplate()->getEnergyProduction() > 0) + { + // Disabledness only affects Producers, not Consumers. + return; + } + + if (incoming) { + getControllingPlayer()->getEnergy()->objectEnteringInfluence(this); + } else { + getControllingPlayer()->getEnergy()->objectLeavingInfluence(this); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +void Object::onDisabledEdge(Bool becomingDisabled) +{ + // rip through the behavior modules and call the onDisabledEdge for any modules that care + for( BehaviorModule **module = m_behaviors; *module; ++module ) + (*module)->onDisabledEdge( becomingDisabled ); + + DozerAIInterface *dozerAI = getAI() ? getAI()->getDozerAIInterface() : NULL; + if( becomingDisabled && dozerAI ) + { + // Have to say goodbye to the thing we might be building or repairing so someone else can do it. + if( dozerAI->getCurrentTask() != DOZER_TASK_INVALID ) + dozerAI->cancelTask( dozerAI->getCurrentTask() ); + } + + Player* controller = getControllingPlayer(); + // can be called during game teardown, thus controller can be null + if (controller) + { + //@todo jkmcd - Colin suggested we rewrite this to use the interface stuff. I agree, but need + // to get some more bugs fixed today. + static NameKeyType radar = NAMEKEY("RadarUpgrade"); + Module *mod = mod = findModule(radar); + if (mod) { + RadarUpgrade *radarMod = (RadarUpgrade*) mod; + if (radarMod->isAlreadyUpgraded()) { + // Need to decrement the count here, because we own a radar upgrade + if (becomingDisabled) { + controller->removeRadar(radarMod->getIsDisableProof()); + } else { + controller->addRadar(radarMod->getIsDisableProof()); + } + } + } + } + + // We will need to adjust power ... somehow ... + Int powerToAdjust = getTemplate()->getEnergyProduction(); + + if( powerToAdjust > 0 ) + { + // We can't affect something that consumes, or else we go low power which removes the consumption + // which makes us not low power so we add the consumption so we go low power... + // This check also guaards the IsDisabled in friend_adjustPower above + static NameKeyType powerPlant = NAMEKEY("PowerPlantUpgrade"); + static NameKeyType overCharge = NAMEKEY("OverchargeBehavior"); + + Module* mod = findModule(powerPlant); + if (mod) { + PowerPlantUpgrade *powerPlantMod = (PowerPlantUpgrade*) mod; + if (powerPlantMod->isAlreadyUpgraded()) { + powerToAdjust += getTemplate()->getEnergyBonus(); + } + } + + mod = findModule(overCharge); + if (mod) { + OverchargeBehavior *overChargeMod = (OverchargeBehavior*) mod; + if (overChargeMod->isOverchargeActive()) { + powerToAdjust += getTemplate()->getEnergyBonus(); + } + } + + // Now, adjust the power for the player. + if (controller) + controller->getEnergy()->adjustPower(powerToAdjust, !becomingDisabled); + } +} + +//------------------------------------------------------------------------------------------------- +/** Object CRC implemtation */ +//------------------------------------------------------------------------------------------------- +void Object::crc( Xfer *xfer ) +{ +#ifdef DEBUG_CRC +// g_logObjectCRCs = TRUE; +// Bool g_logAllObjects = TRUE; + AsciiString logString; + AsciiString tmp; + Bool doLogging = g_logObjectCRCs /* && getControllingPlayer()->getPlayerType() == PLAYER_HUMAN */; + if (doLogging) + { + tmp.format("CRC of Object %d (%s), owned by player %d, team: %d, ", m_id, getTemplate()->getName().str(), getControllingPlayer()->getPlayerIndex(), this->getTeam() ? this->getTeam()->getID() : TEAM_ID_INVALID); + logString.concat(tmp); + } +#endif // DEBUG_CRC + + xfer->xferUnsignedByte(&m_privateStatus); +#ifdef DEBUG_CRC + if (doLogging) + { + tmp.format("m_privateStatus: %X, ", (UnsignedInt)m_privateStatus); + logString.concat(tmp); + } +#endif // DEBUG_CRC + + // This is evil - we cast the const Matrix3D * to a Matrix3D * because the XferCRC class must use + // the same interface as the XferLoad class for save game restore. This only works because + // XferCRC does not modify its data. + xfer->xferUser((Matrix3D *)getTransformMatrix(), sizeof(Matrix3D)); +#ifdef DEBUG_CRC + if (doLogging) + { + XferCRC tmpXfer; + tmpXfer.open("tmp"); + tmpXfer.xferUser((Matrix3D *)getTransformMatrix(), sizeof(Matrix3D)); + tmp.format("getTransformMatrix(): %8.8X, ", tmpXfer.getCRC()); + tmpXfer.close(); + logString.concat(tmp); + } +#endif // DEBUG_CRC + + + xfer->xferUser(&m_id, sizeof(m_id)); +#ifdef DEBUG_CRC + if (doLogging) + { + tmp.format("m_id: %d, ", m_id); + logString.concat(tmp); + } +#endif // DEBUG_CRC + xfer->xferUser(&m_objectUpgradesCompleted, sizeof(Int64)); +#ifdef DEBUG_CRC + if (doLogging) + { + tmp.format("m_objectUpgradesCompleted: %I64X, ", m_objectUpgradesCompleted); + logString.concat(tmp); + } +#endif // DEBUG_CRC + if (m_experienceTracker) + xfer->xferSnapshot( m_experienceTracker ); +#ifdef DEBUG_CRC + if (doLogging) + { + XferCRC tmpXfer; + tmpXfer.open("tmp"); + tmpXfer.xferSnapshot(m_experienceTracker); + tmp.format("m_experienceTracker: %8.8X, ", tmpXfer.getCRC()); + tmpXfer.close(); + logString.concat(tmp); + } +#endif // DEBUG_CRC + + Real health = getBodyModule()->getHealth(); + xfer->xferUser(&health, sizeof(health)); +#ifdef DEBUG_CRC + if (doLogging) + { + tmp.format("health: %g/%8.8X, ", health, AS_INT(health)); + logString.concat(tmp); + } +#endif // DEBUG_CRC + + xfer->xferUnsignedInt(&m_weaponBonusCondition); +#ifdef DEBUG_CRC + if (doLogging) + { + tmp.format("m_weaponBonusCondition: %8.8X, ", m_weaponBonusCondition); + logString.concat(tmp); + } +#endif // DEBUG_CRC + + Real scalar = getBodyModule()->getDamageScalar(); + xfer->xferUser(&scalar, sizeof(scalar)); +#ifdef DEBUG_CRC + if (doLogging) + { + tmp.format("damage scalar: %g/%8.8X\n", scalar, AS_INT(scalar)); + logString.concat(tmp); + + CRCDEBUG_LOG(("%s", logString.str())); + } +#endif // DEBUG_CRC + + for (Int i=0; ixferSnapshot( thisWeapon ); + } + } + +} // end crc + +//------------------------------------------------------------------------------------------------- +/** Object xfer implemtation + * Version Info: + * 1: Initial version + * 2: Xfers m_singleUseCommandUsed... determines if the single use command button has been used or not. + * 3: Xfers the solehealingbenefactor ID and expiration frame + * 4: misc stuff that got missed somehow + * 5: m_isReceivingDifficultyBonus + * 6: We do indeed need to save m_containedBy. The comment misrepresents what the contain module will do. + * 7: save full mtx, not pos+orient. + * 8: Kris: Conversion of object status bits from UnsignedInt to BitFlags<> + * 9: Extra sighting for reveal to all with different range units + */ +//------------------------------------------------------------------------------------------------- +void Object::xfer( Xfer *xfer ) +{ + + // version + const XferVersion currentVersion = 9; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // object ID + ObjectID id = getID(); + xfer->xferObjectID( &id ); + setID( id ); + + DEBUG_LOG(("Xfer Object %s id=%d\n",getTemplate()->getName().str(),id)); + + if (version >= 7) + { + Matrix3D mtx = *getTransformMatrix(); + xfer->xferMatrix3D(&mtx); + setTransformMatrix(&mtx); + } + else + { + // object position + Coord3D pos = *getPosition(); + xfer->xferCoord3D( &pos ); + setPosition( &pos ); + + // orientation + Real orientation = getOrientation(); + xfer->xferReal( &orientation ); + setOrientation( orientation ); + } + + // team + TeamID teamID = m_team ? m_team->getID() : TEAM_ID_INVALID; + xfer->xferUser( &teamID, sizeof( TeamID ) ); + // DON'T set the team yet; must wait till we read our status bits, + // since setTeam can affect the player's power usage, but that could + // be done incorrectly if our status bits aren't accurate yet... (srj) + + // producer id + xfer->xferObjectID( &m_producerID ); + + // builder id + xfer->xferObjectID( &m_builderID ); + + // drawable id + Drawable *draw = getDrawable(); + DrawableID drawableID = draw ? draw->getID() : INVALID_DRAWABLE_ID; + xfer->xferDrawableID( &drawableID ); + if( xfer->getXferMode() == XFER_LOAD ) + { + + // change the ID of the drawable attached to be the same ID as it was when it was saved + draw->setID( drawableID ); + + } // end if + + // internal name + xfer->xferAsciiString( &m_name ); + + // status + if( version >= 8 ) + { + m_status.xfer( xfer ); + } + else + { + //We are loading an old version, so we must convert it from a 32-bit int to a bitflag + UnsignedInt oldStatus; + xfer->xferUnsignedInt( &oldStatus ); + + //Clear our status + m_status.clear(); + + for( int i = 0; i < 32; i++ ) + { + UnsignedInt bit = 1<xferUnsignedByte( &m_scriptStatus ); + + // private status + xfer->xferUnsignedByte( &m_privateStatus ); + + // OK, now that we have xferred our status bits, it's safe to set the team... + if( xfer->getXferMode() == XFER_LOAD ) + { + Team *team = TheTeamFactory->findTeamByID( teamID ); + if( team == NULL ) + { + DEBUG_CRASH(( "Object::xfer - Unable to load team\n" )); + throw SC_INVALID_DATA; + } + const Bool restoring = true; + setOrRestoreTeam( team, restoring ); + } + + // geometry info + xfer->xferSnapshot( &m_geometryInfo ); + + // sighting info, last look - must be saved cause we save PartitionCell::m_shroudLevel + xfer->xferSnapshot( m_partitionLastLook ); + + if( version >= 9 ) + xfer->xferSnapshot( m_partitionRevealAllLastLook ); + + // sighting info, last shroud - must be saved cause we save PartitionCell::m_shroudLevel + xfer->xferSnapshot( m_partitionLastShroud ); + + // vision spied by + xfer->xferUser( m_visionSpiedBy, sizeof( Int ) * MAX_PLAYER_COUNT ); + + // vision spied by mask + xfer->xferUser( &m_visionSpiedMask, sizeof( PlayerMaskType ) ); + + // sighting info, last threat + // John M says we don't need to save this (CBD) +// xfer->xferSnapshot( &m_partitionLastThreat ); + + // sighting info, last value + // John M says we don't need to save this (CBD) +// xfer->xferSnapshot( &m_partitionLastValue ); + + // vision range + xfer->xferReal( &m_visionRange ); + + // shroud clearing range + xfer->xferReal( &m_shroudClearingRange ); + + // shroud range + xfer->xferReal( &m_shroudRange ); + + // disabled mask + m_disabledMask.xfer( xfer ); + + //New var added for version 2. Determines if the single use command button has been used or not. + if( xfer->getXferMode() == XFER_SAVE || version >= 2 ) + { + xfer->xferBool( &m_singleUseCommandUsed ); + } + else + { + m_singleUseCommandUsed = false; + } + + // disabled till frame + xfer->xferUser( m_disabledTillFrame, sizeof( UnsignedInt ) * DISABLED_COUNT ); + + // special model condition until + xfer->xferUnsignedInt( &m_smcUntil ); + + // + // radar data ... when loading, we will remove all objects from the radar and let + // the radar system load itself as a separate chunk of data from the save file + // + if( xfer->getXferMode() == XFER_LOAD && m_radarData ) + TheRadar->removeObject( this ); + + // experience tracker + xfer->xferSnapshot( m_experienceTracker ); + + // + // we do not need to do anything with our m_containedBy pointer, the post process + // of that objects contain module will actually re-do the contain process again + // + // m_containedBy <-- do nothing with this right now + if( version >= 6 ) + { + // No, the contain module is just going to friend_ reach in and set this for us. + // Containers more complicated than Open (like Tunnel) can't do that. Our variable, + // our responsibility. + if( xfer->getXferMode() == XFER_SAVE ) + { + if( m_containedBy != NULL ) + m_xferContainedByID = m_containedBy->getID(); + else + m_xferContainedByID = INVALID_ID; + } + + + xfer->xferObjectID( &m_xferContainedByID ); + } + + // contained by frame + xfer->xferUnsignedInt( &m_containedByFrame ); + + // construction percent + xfer->xferReal( &m_constructionPercent ); + + // upgrades completed + xfer->xferUpgradeMask( &m_objectUpgradesCompleted ); + + // original team name + xfer->xferAsciiString( &m_originalTeamName ); + + // indicator color + xfer->xferColor( &m_indicatorColor ); + + // health box offset + xfer->xferCoord3D( &m_healthBoxOffset ); + + // Entered & exited housekeeping. + Int i; + xfer->xferByte(&m_numTriggerAreasActive); + xfer->xferUnsignedInt(&m_enteredOrExitedFrame); + xfer->xferICoord3D(&m_iPos); + if (m_numTriggerAreasActive<0 || m_numTriggerAreasActive>MAX_TRIGGER_AREA_INFOS) { + DEBUG_CRASH(("Invalid m_numTriggerAreasActive = %d, max is %d", m_numTriggerAreasActive, + MAX_TRIGGER_AREA_INFOS)); + throw SC_INVALID_DATA; + } + for (i=0; igetTriggerName(); + } + xfer->xferAsciiString(&triggerName); + if (xfer->getXferMode() == XFER_LOAD) + { + // + // CBD (11-13-2002) I'm disabling this because it appears there might be some areas with + // empty names, see John A. for more info + // + //if (triggerName.isNotEmpty()) + m_triggerInfo[i].pTrigger = TheTerrainLogic->getTriggerAreaByName(triggerName); + } + xfer->xferByte(&m_triggerInfo[i].entered); + xfer->xferByte(&m_triggerInfo[i].exited); + xfer->xferByte(&m_triggerInfo[i].isInside); + } + // Layer object is pathing on. + xfer->xferUser(&m_layer, sizeof(m_layer)); + + // Layer of current path goal. + xfer->xferUser(&m_destinationLayer, sizeof(m_destinationLayer)); + + // Object selectability. + xfer->xferBool(&m_isSelectable); + + xfer->xferUnsignedInt(&m_safeOcclusionFrame); + + // User formations. + xfer->xferUser(&m_formationID, sizeof(m_formationID)); + if (m_formationID!=NO_FORMATION_ID) { + xfer->xferCoord2D(&m_formationOffset); + } + + // module count + UnsignedShort moduleCount = 0; + for (BehaviorModule** b = m_behaviors; *b; ++b) + ++moduleCount; + + xfer->xferUnsignedShort( &moduleCount ); + AsciiString moduleIdentifier; + BehaviorModule *module; + if( xfer->getXferMode() == XFER_SAVE ) + { + + // go through all modules + for (BehaviorModule** b = m_behaviors; *b; ++b) + { + + // get module + module = *b; + + // write module identifier + moduleIdentifier = TheNameKeyGenerator->keyToName( module->getModuleTagNameKey() ); + DEBUG_ASSERTCRASH( moduleIdentifier != AsciiString::TheEmptyString, + ("Object::xfer - Module tag key does not translate to a string!\n") ); + xfer->xferAsciiString( &moduleIdentifier ); + + // begin a data block + xfer->beginBlock(); + + // xfer data + xfer->xferSnapshot( module ); + + // end data block + xfer->endBlock(); + + } // end for, it + + } // end if, save + else + { + AsciiString otherModuleIdentifier; + + // read all module data + for( UnsignedShort i = 0; i < moduleCount; ++i ) + { + + // read module name + xfer->xferAsciiString( &moduleIdentifier ); + NameKeyType moduleIdentifierKey = TheNameKeyGenerator->nameToKey(moduleIdentifier); + + // find the module with this identifier in the module list + module = NULL; + for (BehaviorModule** b = m_behaviors; b && *b; ++b) + { + + if (moduleIdentifierKey == (*b)->getModuleTagNameKey()) + { + module = *b; + break; + } + + } // end for, moduleIt + + // start of a new block + Int dataSize = xfer->beginBlock(); + + // + // if we didn't find the module, it's quite possible that we have removed + // it from the object definition in a future patch, if that is so, we need to + // skip the module data in the file + // + if( module == NULL ) + { + + // for testing purposes, this module better be found +// DEBUG_CRASH(( "Object::xfer - Module '%s' was indicated in file, but not found on object '%s'(%d)\n", +// moduleIdentifier.str(), getTemplate()->getName().str(), getID() )); + + // skip this data in the file + xfer->skip( dataSize ); + + } // end if + else + { + + // xfer the data into this module + xfer->xferSnapshot( module ); + + } // end else + + // end block + xfer->endBlock(); + + } // end for, i module count recorded in file + + } // end else, load + + + if ( version >= 3 ) + { + xfer->xferObjectID( &m_soleHealingBenefactorID ); + xfer->xferUnsignedInt( &m_soleHealingBenefactorExpirationFrame ); + } + else if ( xfer->getXferMode() == XFER_LOAD ) + { + m_soleHealingBenefactorID = INVALID_ID; + m_soleHealingBenefactorExpirationFrame = 0; + } + + // Doesn't need to be saved. These are created as needed. jba. + //AIGroup* m_group; ///< if non-NULL, we are part of this group of agents + + // don't need to save m_partitionData. + DEBUG_ASSERTCRASH(!(xfer->getXferMode() == XFER_LOAD && m_partitionData == NULL), ("should not be in partitionmgr yet")); + + // don't need to be saved or loaded; are inited & cached for runtime only by our ctor (srj) + //m_repulsorHelper; + //m_smcHelper; + //m_wsHelper; + //m_defectionHelper; + //m_firingTracker; + //m_contain; + //m_body; + //m_ai; + //m_physics; +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + //m_hasDiedAlready; +#endif + + if (version >= 4) + { + // xfer the weaponSetFlags FIRST, since we need 'em to restore the weaponSet properly. (srj) + m_curWeaponSetFlags.xfer( xfer ); + xfer->xferUnsignedInt(&m_weaponBonusCondition); + xfer->xferUser(&m_lastWeaponCondition, sizeof(m_lastWeaponCondition)); + + // do the weaponSet itself after all the weapon-related stuff, just in case + xfer->xferSnapshot(&m_weaponSet); + + m_specialPowerBits.xfer( xfer ); + + xfer->xferAsciiString(&m_commandSetStringOverride); + + xfer->xferBool(&m_modulesReady); + } + + if (version >= 5) + { + xfer->xferBool(&m_isReceivingDifficultyBonus); + } + else + m_isReceivingDifficultyBonus = FALSE; + +} // end xfer + +//------------------------------------------------------------------------------------------------- +/** Object load game post process phase */ +//------------------------------------------------------------------------------------------------- +void Object::loadPostProcess() +{ + if( m_xferContainedByID != INVALID_ID ) + m_containedBy = TheGameLogic->findObjectByID(m_xferContainedByID); + else + m_containedBy = NULL; + +} // end loadPostProcess + +//------------------------------------------------------------------------------------------------- +/** Does this object have this upgrade */ +//------------------------------------------------------------------------------------------------- +Bool Object::hasUpgrade( const UpgradeTemplate *upgradeT ) const +{ + if( m_objectUpgradesCompleted.testForAll( upgradeT->getUpgradeMask() ) ) + { + return TRUE; + } + return FALSE; +} // end hasUpgrade + +//------------------------------------------------------------------------------------------------- +/** Is this object capable of having this upgrade */ +//------------------------------------------------------------------------------------------------- +Bool Object::affectedByUpgrade( const UpgradeTemplate *upgradeT ) const +{ + UpgradeMaskType objectMask = getObjectCompletedUpgradeMask(); + UpgradeMaskType playerMask = getControllingPlayer()->getCompletedUpgradeMask(); + UpgradeMaskType maskToCheck = playerMask; + maskToCheck.set( objectMask ); + maskToCheck.set( upgradeT->getUpgradeMask() ); + + // We need to add in all of the already owned upgrades to handle "AND" requiring upgrades. + // We combine all the masks in case someone has a Object AND Player combination + + for (BehaviorModule** module = m_behaviors; *module; ++module) + { + UpgradeModuleInterface* upgrade = (*module)->getUpgrade(); + if (!upgrade) + continue; + + if( upgrade->wouldUpgrade( maskToCheck ) ) + { + // if any of my many upgrade modules would execute in response to this flag, say yes. + return TRUE; + } + } + return FALSE; + +} // end affectedByUpgrade + +//------------------------------------------------------------------------------------------------- +/** Give this upgrade to this object */ +//------------------------------------------------------------------------------------------------- +void Object::giveUpgrade( const UpgradeTemplate *upgradeT ) +{ + if (upgradeT) + { + m_objectUpgradesCompleted.set( upgradeT->getUpgradeMask() ); + + // + // iterate through all the upgrade modules of this object and call the method to + // grant a new upgrade + // + updateUpgradeModules(); + } +} // end giveUpgrade + +//------------------------------------------------------------------------------------------------- +/** Remove this upgrade from this object */ +//------------------------------------------------------------------------------------------------- +void Object::removeUpgrade( const UpgradeTemplate *upgradeT ) +{ + m_objectUpgradesCompleted.clear( upgradeT->getUpgradeMask() ); + for (BehaviorModule** module = m_behaviors; *module; ++module) + { + UpgradeModuleInterface* upgrade = (*module)->getUpgrade(); + if (!upgrade) + continue; + + // Whoa, please note that while the function is called Object::RemoveUpgrade, it is not removing anything + // in the sense of undoing the effects. It is just resetting the upgrade so it may be run again. + upgrade->resetUpgrade( upgradeT->getUpgradeMask() ); + } +} + +//------------------------------------------------------------------------------------------------- +/** Central point for onCapture logic */ +//------------------------------------------------------------------------------------------------- +void Object::onCapture( Player *oldOwner, Player *newOwner ) +{ + // Everybody dhills when they captured so they don't keep doing something the new player might not want him to be doing + if( getAIUpdateInterface() && (oldOwner != newOwner) ) + getAIUpdateInterface()->aiIdle(CMD_FROM_AI); + + // this gets the new owner some points + newOwner->getScoreKeeper()->addObjectCaptured(this); + + // rip through the behavior modules and call the onCapture for any modules that care + for( BehaviorModule **module = m_behaviors; *module; ++module ) + (*module)->onCapture( oldOwner, newOwner ); + + // + // We have to undo our look for the old team and redo it for the new. + // onCapture is used now, so it better be called after ownership changes and not before. + // + handlePartitionCellMaintenance(); + + // Design needs the player to be able to sell buildings he steals from the AI's build list, and this is the + // easiest fix. The only snafu would be a key building build listed by the AI that the player can capture + // and the AI tries to capture back but needs to not sell. In that case, a Cinematic Unsellable version + // of the building needs to be made. This fix has been okayed as the most non-lethal in November. + clearScriptStatus(OBJECT_STATUS_SCRIPT_UNSELLABLE); + + // mark the command bar to redraw + TheControlBar->markUIDirty(); + + if (oldOwner!=newOwner && newOwner->isSkirmishAIPlayer()) { + // The skirmish ai doesn't know what to do with captured faction buildings except sell them. + if (isFactionStructure()) { + TheBuildAssistant->sellObject( this ); + } + } + +} // end onCapture + +//------------------------------------------------------------------------------------------------- +/// Object level events that need to happen upon game death +void Object::onDie( DamageInfo *damageInfo ) +{ + + checkAndDetonateBoobyTrap(NULL);// Already dying, so no need to handle death case of explosion + +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + DEBUG_ASSERTCRASH(m_hasDiedAlready == false, ("Object::onDie has been called multiple times. This is invalid. jkmcd")); + m_hasDiedAlready = true; +#endif + + Bool selfInflicted = (damageInfo->in.m_sourceID == getID()); + + // FIRST, call our die modules. + for (BehaviorModule** d = m_behaviors; *d; ++d) + { + DieModuleInterface* die = (*d)->getDie(); + if (die) + die->onDie(damageInfo); + } + + // When objects die we remove from the radar as they're really not interesting anymore + if( m_radarData ) + TheRadar->removeObject( this ); + + // Just in case I have been sporting one of thise fancy Terrain Decals, + //I naturally lose it now, because I'm dead. + Drawable *draw = getDrawable(); + if (draw) draw->setTerrainDecalFadeTarget(0.0f, -0.03f);//fade... + //if (draw) draw->setTerrainDecal(TERRAIN_DECAL_NONE);//pop! + + + // objects that were spawned from something, need to tell their spawner that they have died + Object* spawner = TheGameLogic->findObjectByID( getProducerID() ); + if( spawner ) + { + + // get the spawn behavior interface of the spawner + SpawnBehaviorInterface *spawnerBehavior = spawner->getSpawnBehaviorInterface(); + if( spawnerBehavior ) + spawnerBehavior->onSpawnDeath( getID(), damageInfo ); + + } + + handlePartitionCellMaintenance(); + if(m_team) + m_team->notifyTeamOfObjectDeath(); + + if (isLocallyControlled() && !selfInflicted) // wasLocallyControlled? :-) + { + if (isKindOf(KINDOF_STRUCTURE) && isKindOf(KINDOF_MP_COUNT_FOR_VICTORY)) + { + TheEva->setShouldPlay(EVA_BuldingLost); + } + else if (isKindOf(KINDOF_INFANTRY) || isKindOf(KINDOF_VEHICLE)) + { + TheEva->setShouldPlay(EVA_UnitLost); + //Create a fake radar event so the user can use the spacebar to quickly jump to this! + TheRadar->tryEvent( RADAR_EVENT_FAKE, getPosition() ); + } + } + + // This call won't do anything if we aren't actually in the list. + //Kris: Added NULL check to prevent crash with combat bikes & their riders getting deleted on exit. + if( getControllingPlayer() ) + { + TheInGameUI->removeIdleWorker( this, getControllingPlayer()->getPlayerIndex() ); + } + + //When a GLA hole is in the process of rebuilding, and that rebuild is lost, we need to + //tell anyone attacking it to transfer the attack to the hole that still exists. + if( testStatus( OBJECT_STATUS_RECONSTRUCTING ) ) + { + Object *hole = TheGameLogic->findObjectByID( getProducerID() ); + if( hole ) + { + // set the information in the hole about what to build + RebuildHoleBehaviorInterface *rhbi = RebuildHoleBehavior::getRebuildHoleBehaviorInterfaceFromObject( hole ); + + // sanity + DEBUG_ASSERTCRASH( rhbi, ("Object::onDie() - No Rebuild Hole Behavior interface on hole\n") ); + + // start the rebuild process + if( rhbi ) + { + rhbi->startRebuildProcess( getTemplate(), getID() ); + } + + //Transfer any attackers from the destroyed building to the hole. + for ( Object *obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() ) + { + AIUpdateInterface* ai = obj->getAI(); + if (!ai) + continue; + + ai->transferAttack( getID(), hole->getID() ); + } + } + } + +} + +//------------------------------------------------------------------------------------------------- +void Object::setWeaponBonusCondition(WeaponBonusConditionType wst) +{ + WeaponBonusConditionFlags oldCondition = m_weaponBonusCondition; + m_weaponBonusCondition |= (1 << wst); + + if( oldCondition != m_weaponBonusCondition ) + { + // Our weapon bonus just changed, so we need to immediately update our weapons + m_weaponSet.weaponSetOnWeaponBonusChange(this); + } +} + +//------------------------------------------------------------------------------------------------- +void Object::clearWeaponBonusCondition(WeaponBonusConditionType wst) +{ + WeaponBonusConditionFlags oldCondition = m_weaponBonusCondition; + m_weaponBonusCondition &= ~(1 << wst); + + if( oldCondition != m_weaponBonusCondition ) + { + // Our weapon bonus just changed, so we need to immediately update our weapons + m_weaponSet.weaponSetOnWeaponBonusChange(this); + } +} + +//------------------------------------------------------------------------------------------------- +/** + A weapon cannot be in charge of maintaining condition flags as it is all event driven. + I will maintain my ModelCondition myself if it should change. Firing is set by firing logic, + so I don't include it here. It is only the states that expire on timers that noone watches + that I am concerned with. +*/ +//------------------------------------------------------------------------------------------------- +void Object::adjustModelConditionForWeaponStatus() +{ + UnsignedInt now = TheGameLogic->getFrame(); + + for (int i = 0; i < WEAPONSLOT_COUNT; ++i) + { + const Weapon* w = m_weaponSet.getWeaponInWeaponSlot((WeaponSlotType)i); + if (!w) + { + m_lastWeaponCondition[i] = WSF_NONE; + continue; + } + + WeaponSetConditionType conditionToSet = WSF_INVALID; + if (i != m_weaponSet.getCurWeaponSlot()) + { + // if this isn't the current weapon, then we never set ANYTHING for it. + conditionToSet = WSF_NONE; + } + else if (w->getLastShotFrame() == now) + { + // yep, this overrides any weapon-status condition! + conditionToSet = WSF_FIRING; + } + else if (!testStatus( OBJECT_STATUS_IS_ATTACKING )) + { + // srj sez: not 100% sure about this one, but the problem is: say we were attacking, + // then issue a move command. if we didn't do this here, we might still have a 'firing' + // pose, because his weapon might be in 'reloading' mode. since we're not attacking, however, + // we really don't care, so we just force the issue here. (This might still need tweaking for the pursue state.) + conditionToSet = WSF_NONE; + } + else + { + WeaponStatus newStatus = w->getStatus(); + + const static WeaponSetConditionType s_wsfLookup[WEAPON_STATUS_COUNT] = + { + WSF_NONE, // READY_TO_FIRE, + WSF_NONE, // OUT_OF_AMMO, + WSF_BETWEEN, // BETWEEN_FIRING_SHOTS, + WSF_RELOADING, // RELOADING_CLIP, + WSF_PREATTACK // PRE_ATTACK, + }; + conditionToSet = s_wsfLookup[newStatus]; + + // special case this: say we are firing in bursts: pow-pow-pow-pause, etc. + // then we might have a frame where we have reloaded and are ready-to-fire, + // but haven't fired yet this frame. in that case, use 'between' so we still have + // a firing pose, 'cuz if we use 'none' we will 'pop' back to idle for a frame. (srj) + // additional note: only do if aiming or firing, since we could also be in this state if + // we are approaching or pursuing a target! (srj) + if (newStatus == READY_TO_FIRE && conditionToSet == WSF_NONE && testStatus( OBJECT_STATUS_IS_ATTACKING ) && + (testStatus( OBJECT_STATUS_IS_AIMING_WEAPON ) || testStatus( OBJECT_STATUS_IS_FIRING_WEAPON ))) + { + conditionToSet = WSF_BETWEEN; + } + + } + + if (m_drawable) + { + m_drawable->updateDrawableClipStatus( w->getRemainingAmmo(), w->getClipSize(), w->getWeaponSlot() ); + if (conditionToSet != WSF_INVALID && conditionToSet != m_lastWeaponCondition[i]) + { + m_lastWeaponCondition[i] = conditionToSet; + ModelConditionFlags c = m_weaponSet.getModelConditionForWeaponSlot((WeaponSlotType)i, conditionToSet); + m_drawable->clearAndSetModelConditionFlags(s_allWeaponFireFlags[i], c); + if (conditionToSet == WSF_PREATTACK) + { + // in the preattack state, adjust the speed of the preattack anim to match the actual time it will take + UnsignedInt preAttackDone = w->getPreAttackFinishedFrame(); + if (preAttackDone > now) + m_drawable->setAnimationLoopDuration(preAttackDone - now); + } + } + } + } +} + +//------------------------------------------------------------------------------------------------- +/// We have moved a 'significant' amount, so do maintenence that can be considered 'cell-based' +void Object::onPartitionCellChange() +{ + handlePartitionCellMaintenance(); +} + +//------------------------------------------------------------------------------------------------- +void Object::handlePartitionCellMaintenance() +{ + handleShroud(); + handleValueMap(); + handleThreatMap(); +} + +//------------------------------------------------------------------------------------------------- +void Object::handleShroud() +{ + // Undo last looking + unlook(); + // and shrouding + unshroud(); + + // redo shrouding + shroud(); + // Redo looking + look(); +} + +//------------------------------------------------------------------------------------------------- +void Object::handleValueMap() +{ + removeValue(); + addValue(); +} + +//------------------------------------------------------------------------------------------------- +void Object::handleThreatMap() +{ + removeThreat(); + addThreat(); +} + +//------------------------------------------------------------------------------------------------- +void Object::addValue() +{ + if( !m_partitionLastValue->isInvalid() ) + { + DEBUG_CRASH( ("An Object is adding value, but hasn't removed his previous value.") ); + return; + } + + if (!getControllingPlayer()) + return; + + if( getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) || isEffectivelyDead() || getShroudClearingRange() <= 0.0f ) + return; + + + m_partitionLastValue->m_where = *getPosition(); + m_partitionLastValue->m_data = getTemplate()->friend_getBuildCost(); + + m_partitionLastValue->m_forWhom = getControllingPlayer()->getPlayerMask(); + m_partitionLastValue->m_howFar = getVisionRange(); // we are valuable all the way to where we can target. + + ThePartitionManager->doValueAffect(m_partitionLastValue->m_where.x, + m_partitionLastValue->m_where.y, + m_partitionLastValue->m_howFar, + m_partitionLastValue->m_data, + m_partitionLastValue->m_forWhom + ); +} + +//------------------------------------------------------------------------------------------------- +void Object::removeValue() +{ + if( m_partitionLastValue->isInvalid() ) + { + // removing before adding is valid, cause we always remove before adding. (So the first remove + // will occur before the first add) + return; + } + + ThePartitionManager->undoValueAffect(m_partitionLastValue->m_where.x, + m_partitionLastValue->m_where.y, + m_partitionLastValue->m_howFar, + m_partitionLastValue->m_data, + m_partitionLastValue->m_forWhom + ); + + m_partitionLastValue->reset(); +} + +//------------------------------------------------------------------------------------------------- +void Object::addThreat() +{ + if( !m_partitionLastThreat->isInvalid() ) + { + DEBUG_CRASH( ("An Object is adding threat, but hasn't removed his previous threat. (He hasn't finished the threat?)") ); + return; + } + + if (!getControllingPlayer()) + return; + + if( getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) || isEffectivelyDead() || getShroudClearingRange() <= 0.0f ) + return; + + + m_partitionLastThreat->m_where = *getPosition(); + m_partitionLastThreat->m_data = getTemplate()->getThreatValue(); + + m_partitionLastThreat->m_forWhom = getControllingPlayer()->getPlayerMask(); + m_partitionLastThreat->m_howFar = getVisionRange(); // we are threatening all the way to where we can target. + + ThePartitionManager->doThreatAffect(m_partitionLastThreat->m_where.x, + m_partitionLastThreat->m_where.y, + m_partitionLastThreat->m_howFar, + m_partitionLastThreat->m_data, + m_partitionLastThreat->m_forWhom + ); +} + +//------------------------------------------------------------------------------------------------- +void Object::removeThreat() +{ + if( m_partitionLastThreat->isInvalid() ) + { + // removing before adding is valid, cause we always remove before adding. (So the first remove + // will occur before the first add) + return; + } + + ThePartitionManager->undoThreatAffect(m_partitionLastThreat->m_where.x, + m_partitionLastThreat->m_where.y, + m_partitionLastThreat->m_howFar, + m_partitionLastThreat->m_data, + m_partitionLastThreat->m_forWhom + ); + + m_partitionLastThreat->reset(); +} + + + +//------------------------------------------------------------------------------------------------- +void Object::look() +{ + if( ! m_partitionLastLook->isInvalid() ) + { + DEBUG_CRASH( ("An Object is looking, but hasn't unlooked the last one.") ); + return; + } + + Player* controller = getControllingPlayer(); + if ( controller ) + { + // I removed the check for objects under construction by request of designers since + // they want constructing objects to have a reduced sight range now. -MW + // dead or blind things don't reveal shroud + + + + // Some things get Destroyed directly without hitting Death. + if( !isDestroyed() && !isEffectivelyDead() ) + { + + ContainModuleInterface * contain = (getContainedBy() ? getContainedBy()->getContain() : NULL); + if ( contain && !contain->isGarrisonable() ) + return;// dont look, 'cause you are in a tunnel, now + // GS 10-20 Need to expand that exception to all transports or else you get a perma reveal where + // you entered the transport. Remember, this hackiness is caused by the fact that we never realized that + // garrisoned buildings weren't looking, we were just seeing the leftover last look of the guy inside. + // Otherwise we'd just have enclosingContainer control looking which is the 'correct' answer. + + Real shroudClearingRange = getShroudClearingRange(); + if( shroudClearingRange > 0.0f ) + { + PlayerMaskType lookingMask = 0; + + if ( isKindOf(KINDOF_REVEAL_TO_ALL) ) + { + lookingMask = PLAYERMASK_ALL; + } + else + { + for( Int currentIndex = ThePlayerList->getPlayerCount() - 1; currentIndex >=0; currentIndex-- ) + { + const Player *currentPlayer = ThePlayerList->getNthPlayer( currentIndex ); + + // Build mask of of allies who can see me. + // This is the Object-centric game level that cares + if( getControllingPlayer()->getRelationship( currentPlayer->getDefaultTeam() ) == ALLIES ) + { + lookingMask |= currentPlayer->getPlayerMask(); + } + } + + // Other players can also be looking through our eyes. + lookingMask |= m_visionSpiedMask; + } + + Coord3D pos = *getPosition(); + ThePartitionManager->doShroudReveal( pos.x, pos.y, shroudClearingRange, lookingMask ); + + m_partitionLastLook->m_where = pos; + m_partitionLastLook->m_forWhom = lookingMask; + m_partitionLastLook->m_howFar = getShroudClearingRange(); + + // DEBUG_LOG(( "A %s looks at %f, %f for %x at range %f\n", + // getTemplate()->getName().str(), + // pos.x, + // pos.y, + // lookingMask, + // getShroudClearingRange() + // )); + } + + //Now reveal to everyone if we're special. Note this works differently than KINDOF_REVEAL_TO_ALL because + //the kindof uses the same range as allies, spies, and owners would see. This template based shroud + //reveal to all range can specify a different value so we can get a much smaller reveal distance. + // And don't reveal while under construction. When finished, a refresh occurs, so don't worry. + Real shroudRevealToAllRange = getTemplate()->getShroudRevealToAllRange(); + if( shroudRevealToAllRange > 0.0f && !testStatus( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) + { + //Kris: August 20, 2003 + //Seeing I added this concept, I'm changing it now to only reveal to all when the unit is visible. If it's stealthed, + //we won't reveal it anymore (stealth general scudstorm). + Bool stealthedAndNotDetected = testStatus( OBJECT_STATUS_STEALTHED ) && !testStatus( OBJECT_STATUS_DETECTED ) && !testStatus( OBJECT_STATUS_DISGUISED ); + if( !stealthedAndNotDetected ) + { + Coord3D pos = *getPosition(); + PlayerMaskType thePlayersMask = ThePlayerList->getPlayersWithRelationship( getControllingPlayer()->getPlayerIndex(), ALLOW_ENEMIES | ALLOW_NEUTRAL ); + ThePartitionManager->doShroudReveal( pos.x, pos.y, shroudRevealToAllRange, thePlayersMask ); + m_partitionRevealAllLastLook->m_where = pos; + m_partitionRevealAllLastLook->m_forWhom = thePlayersMask; + m_partitionRevealAllLastLook->m_howFar = shroudRevealToAllRange; + } + } + } + } +} + +//------------------------------------------------------------------------------------------------- +void Object::unlook() +{ + if( m_partitionLastLook->isInvalid() ) + { + // Your very first action will be an unlook, so of course you haven't looked yet. This is not an error + // This early return prevents an extra unlook if you never looked. Like you have 0 vision. + return; + } + + ThePartitionManager->queueUndoShroudReveal(m_partitionLastLook->m_where.x, + m_partitionLastLook->m_where.y, + m_partitionLastLook->m_howFar, + m_partitionLastLook->m_forWhom + ); + +// DEBUG_LOG(( "A %s queues an unlook at %f, %f for %x at range %f\n", +// getTemplate()->getName().str(), +// m_partitionLastLook.m_where.x, +// m_partitionLastLook.m_where.y, +// m_partitionLastLook.m_forWhom, +// m_partitionLastLook.m_howFar +// )); + + m_partitionLastLook->reset(); + + if( !m_partitionRevealAllLastLook->isInvalid() ) + { + ThePartitionManager->queueUndoShroudReveal(m_partitionRevealAllLastLook->m_where.x, + m_partitionRevealAllLastLook->m_where.y, + m_partitionRevealAllLastLook->m_howFar, + m_partitionRevealAllLastLook->m_forWhom + ); + + m_partitionRevealAllLastLook->reset(); + } +} + +//------------------------------------------------------------------------------------------------- +void Object::shroud() +{ + if( ! m_partitionLastShroud->isInvalid() ) + { + DEBUG_CRASH( ("An Object is shrouding, but hasn't unshrouded the last one.") ); + return; + } + + Player* controller = getControllingPlayer(); + if ( controller ) + { + // things under construction don't shroud. (srj), nor do dead or blind things + if( !getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) && !isEffectivelyDead() && getShroudRange() > 0.0f ) + { + PlayerMaskType shroudingMask = 0; + for( Int currentIndex = ThePlayerList->getPlayerCount() - 1; currentIndex >=0; currentIndex-- ) + { + const Player *currentPlayer = ThePlayerList->getNthPlayer( currentIndex ); + //Build mask of NON-allies. This is the Object-centric game level that cares + if( getControllingPlayer()->getRelationship( currentPlayer->getDefaultTeam() ) != ALLIES ) + { + shroudingMask |= currentPlayer->getPlayerMask(); + } + } + + Coord3D pos = *getPosition(); + ThePartitionManager->doShroudCover(pos.x, pos.y, + getShroudRange(), + shroudingMask); + + m_partitionLastShroud->m_where = pos; + m_partitionLastShroud->m_forWhom = shroudingMask; + m_partitionLastShroud->m_howFar = getShroudRange(); + } + } +} + +//------------------------------------------------------------------------------------------------- +void Object::unshroud() +{ + if( m_partitionLastShroud->isInvalid() ) + { + // Your very first action will be an unlook, so of course you haven't looked yet. This is not an error + // This early return prevents an extra unlook if you never looked. Like you have 0 shroud generation. + return; + } + + ThePartitionManager->undoShroudCover(m_partitionLastShroud->m_where.x, + m_partitionLastShroud->m_where.y, + m_partitionLastShroud->m_howFar, + m_partitionLastShroud->m_forWhom); + + m_partitionLastShroud->reset(); +} + +//------------------------------------------------------------------------------------------------- +Real Object::getVisionRange() const +{ +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + if (TheGlobalData->m_debugVisibility) + { + Vector3 pos(m_visionRange, 0, 0); + for (int i = 0; i < TheGlobalData->m_debugVisibilityTileCount; ++i) + { + pos.Rotate_Z(1.0f * i / TheGlobalData->m_debugVisibilityTileCount * 2 * PI); + Coord3D coord = { pos.X + getPosition()->x, pos.Y + getPosition()->y, pos.Z + getPosition()->z }; + + addIcon(&coord, TheGlobalData->m_debugVisibilityTileWidth, + TheGlobalData->m_debugVisibilityTileDuration, + TheGlobalData->m_debugVisibilityTargettableColor); + } + } +#endif + return m_visionRange; +} + +//------------------------------------------------------------------------------------------------- +void Object::setVisionRange( Real newVisionRange ) +{ + m_visionRange = newVisionRange; +} + +//------------------------------------------------------------------------------------------------- +Real Object::getShroudClearingRange() const +{ + Real shroudClearingRange=m_shroudClearingRange; + + if( getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) + { + //structures under construction have limited vision range. For now, base it + //on the geometry extents so the structure can only see itself. + shroudClearingRange = getGeometryInfo().getBoundingCircleRadius(); + } + +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + if (TheGlobalData->m_debugVisibility) + { + Vector3 pos(shroudClearingRange, 0, 0); + for (int i = 0; i < TheGlobalData->m_debugVisibilityTileCount; ++i) + { + pos.Rotate_Z(1.0f * i / TheGlobalData->m_debugVisibilityTileCount * 2 * PI); + Coord3D coord = { pos.X + getPosition()->x, pos.Y + getPosition()->y, pos.Z + getPosition()->z }; + + addIcon(&coord, TheGlobalData->m_debugVisibilityTileWidth, + TheGlobalData->m_debugVisibilityTileDuration, + TheGlobalData->m_debugVisibilityDeshroudColor); + } + } +#endif + + return shroudClearingRange; +} + +//------------------------------------------------------------------------------------------------- +void Object::setShroudClearingRange( Real newShroudClearingRange ) +{ + if( newShroudClearingRange != m_shroudClearingRange ) + { + // The partition cell refresh is a slow operation, so only do it if you really have to. + // Range change is a valid reason to relook. + m_shroudClearingRange = newShroudClearingRange; + + /* + Complete and total monkey hack fix. + + The problem: newObject doesn't get an initial pos, so all objects start at 0,0,0. + Most code paths instantly move 'em to a good pos, but in some cases, that is too late: + If we have search-and-destroy battle plan, we will apply it at that point, and clear out + a vision range based on our current (wrong) location. Doh! + + So, this just sez: if you are at 0,0,0, don't call handlePartitionCellMaintenance()... since + you will either (1) be moved elsewhere immediately, thus forcing it to be called via + another route anyway, or (2) not be moved, which means you are a very naughty and worthless + object anyway and we should just ignore you. + + Proper fix for next version is to require initial pos to be passed in to newObject so that + all objects can start at their proper initial position from the start of the ctor. + + (srj) + */ + const Coord3D* pos = getPosition(); + if (pos->x || pos->y || pos->z) + { + handlePartitionCellMaintenance(); + } + } +} + +//------------------------------------------------------------------------------------------------- +Real Object::getShroudRange() const +{ +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + if (TheGlobalData->m_debugVisibility) + { + Vector3 pos(m_shroudRange, 0, 0); + for (int i = 0; i < TheGlobalData->m_debugVisibilityTileCount; ++i) + { + pos.Rotate_Z(1.0f * i / TheGlobalData->m_debugVisibilityTileCount * 2 * PI); + Coord3D coord = { pos.X + getPosition()->x, pos.Y + getPosition()->y, pos.Z + getPosition()->z }; + + addIcon(&coord, TheGlobalData->m_debugVisibilityTileWidth, + TheGlobalData->m_debugVisibilityTileDuration, + TheGlobalData->m_debugVisibilityGapColor); + } + } +#endif + + return m_shroudRange; +} + +//------------------------------------------------------------------------------------------------- +void Object::setShroudRange( Real newShroudRange ) +{ + m_shroudRange = newShroudRange; +} + +//------------------------------------------------------------------------------------------------- +void Object::setVisionSpied(Bool setting, Int byWhom) +{ + Bool needRefresh = FALSE; // If this setting is an edge trigger on the reference count, I need to refresh + + if( setting ) + { + m_visionSpiedBy[ byWhom ] = m_visionSpiedBy[ byWhom ] + 1; + if( m_visionSpiedBy[ byWhom ] == 1 ) + needRefresh = TRUE; + } + else + { + m_visionSpiedBy[ byWhom ] = m_visionSpiedBy[ byWhom ] - 1; + if( m_visionSpiedBy[ byWhom ] == 0 ) + needRefresh = TRUE; + } + + if( needRefresh ) + { + PlayerMaskType workingMask = 0; + for (Int i = 0; i < MAX_PLAYER_COUNT; ++i) + { + if( m_visionSpiedBy[i] > 0 ) + BitSet( workingMask, ( 1 << i ) ); + else + BitClear( workingMask, ( 1 << i ) ); + } + + m_visionSpiedMask = workingMask; + + handlePartitionCellMaintenance(); + } +} + +//------------------------------------------------------------------------------------------------- +void Object::doStatusDamage( ObjectStatusTypes status, Real duration ) +{ + if(m_statusDamageHelper) + m_statusDamageHelper->doStatusDamage(status, duration); +} + +//------------------------------------------------------------------------------------------------- +void Object::doTempWeaponBonus( WeaponBonusConditionType status, UnsignedInt duration, TintStatus tintStatus) +{ + if(m_tempWeaponBonusHelper) + m_tempWeaponBonusHelper->doTempWeaponBonus(status, duration, tintStatus); +} + +//------------------------------------------------------------------------------------------------- +void Object::notifySubdualDamage( Real amount ) +{ + if(m_subdualDamageHelper) + m_subdualDamageHelper->notifySubdualDamage( amount ); + + // If we are gaining subdual damage, we are slowly tinting + if( getDrawable() ) + { + if( amount > 0 ) + getDrawable()->setTintStatus(TINT_STATUS_GAINING_SUBDUAL_DAMAGE); + else + getDrawable()->clearTintStatus(TINT_STATUS_GAINING_SUBDUAL_DAMAGE); + } +} + +//------------------------------------------------------------------------------------------------- +void Object::notifyChronoDamage(Real amount) +{ + if (m_chronoDamageHelper) + m_chronoDamageHelper->notifyChronoDamage(amount); + + // TODO + // If we are gaining subdual damage, we are slowly tinting + //if (getDrawable()) + //{ + // if (amount > 0) + // getDrawable()->setTintStatus(TINT_STATUS_GAINING_SUBDUAL_DAMAGE); + // else + // getDrawable()->clearTintStatus(TINT_STATUS_GAINING_SUBDUAL_DAMAGE); + //} +} + +//------------------------------------------------------------------------------------------------- +/** Given a special power template, find the module in the object that can implement it. + * There can be at most one */ +//------------------------------------------------------------------------------------------------- +SpecialPowerModuleInterface *Object::getSpecialPowerModule( const SpecialPowerTemplate *specialPowerTemplate ) const +{ + + // sanity + if( specialPowerTemplate == NULL ) + return NULL; + + // search the modules for the one with the matching template + for( BehaviorModule** m = m_behaviors; *m; ++m ) + { + SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); + if (!sp) + continue; + + if( sp->isModuleForPower( specialPowerTemplate ) ) + return sp; + } + + return NULL; + +} + +//------------------------------------------------------------------------------------------------- +/** Execute special power */ +//------------------------------------------------------------------------------------------------- +void Object::doSpecialPower( const SpecialPowerTemplate *specialPowerTemplate, UnsignedInt commandOptions, Bool forced ) +{ + + if (isDisabled()) + return; + + // sanity + if( !forced && TheSpecialPowerStore->canUseSpecialPower( this, specialPowerTemplate ) == FALSE ) + return; + + // get the module and execute + SpecialPowerModuleInterface *mod = getSpecialPowerModule( specialPowerTemplate ); + if( mod ) + mod->doSpecialPower( commandOptions ); + +} + +//------------------------------------------------------------------------------------------------- +/** Execute special power */ +//------------------------------------------------------------------------------------------------- +void Object::doSpecialPowerAtObject( const SpecialPowerTemplate *specialPowerTemplate, Object *obj, UnsignedInt commandOptions, Bool forced ) +{ + + if (isDisabled()) + return; + + // sanity + if( !forced && TheSpecialPowerStore->canUseSpecialPower( this, specialPowerTemplate ) == FALSE ) + return; + + // get the module and execute + SpecialPowerModuleInterface *mod = getSpecialPowerModule( specialPowerTemplate ); + if( mod ) + mod->doSpecialPowerAtObject( obj, commandOptions ); +} + +//------------------------------------------------------------------------------------------------- +/** Execute special power */ +//------------------------------------------------------------------------------------------------- +void Object::doSpecialPowerAtLocation( const SpecialPowerTemplate *specialPowerTemplate, + const Coord3D *loc, Real angle, UnsignedInt commandOptions, Bool forced ) +{ + + if (isDisabled()) + return; + + // sanity + if( !forced && TheSpecialPowerStore->canUseSpecialPower( this, specialPowerTemplate ) == FALSE ) + return; + + // get the module and execute + SpecialPowerModuleInterface *mod = getSpecialPowerModule( specialPowerTemplate ); + if( mod ) + mod->doSpecialPowerAtLocation( loc, angle, commandOptions ); + +} + +//------------------------------------------------------------------------------------------------- +/** Execute special power */ +//------------------------------------------------------------------------------------------------- +void Object::doSpecialPowerUsingWaypoints( const SpecialPowerTemplate *specialPowerTemplate, const Waypoint *way, UnsignedInt commandOptions, Bool forced ) +{ + + if (isDisabled()) + return; + + // sanity + if( !forced && TheSpecialPowerStore->canUseSpecialPower( this, specialPowerTemplate ) == FALSE ) + return; + + // get the module and execute + SpecialPowerModuleInterface *mod = getSpecialPowerModule( specialPowerTemplate ); + if( mod ) + mod->doSpecialPowerUsingWaypoints( way, commandOptions ); + +} + +//------------------------------------------------------------------------------------------------- +/** Execute command button ability */ +//------------------------------------------------------------------------------------------------- +void Object::doCommandButton( const CommandButton *commandButton, CommandSourceType cmdSource ) +{ + if (isDisabled()) + return; + + AIUpdateInterface *ai = getAIUpdateInterface(); + if( commandButton ) + { + switch( commandButton->getCommandType() ) + { + case GUI_COMMAND_SPECIAL_POWER: + if( commandButton->getSpecialPowerTemplate() ) + { + CommandOption commandOptions = (CommandOption)(commandButton->getOptions() | COMMAND_FIRED_BY_SCRIPT); + doSpecialPower( commandButton->getSpecialPowerTemplate(), commandOptions, cmdSource == CMD_FROM_SCRIPT ); + return; + } + break; + case GUI_COMMAND_STOP: + if( ai ) + { + ai->aiIdle( cmdSource ); + return; + } + break; + + case GUI_COMMAND_SWITCH_WEAPON: + { + WeaponSlotType weaponSlot = commandButton->getWeaponSlot(); + // GUI_COMMAND_SWITCH_WEAPON switches until un-switched, or switched to something else. + setWeaponLock( weaponSlot, LOCKED_PERMANENTLY ); + return; + } + + case GUI_COMMAND_FIRE_WEAPON: + if( ai ) + { + if( !BitIsSet( commandButton->getOptions(), COMMAND_OPTION_NEED_OBJECT_TARGET ) && !BitIsSet( commandButton->getOptions(), NEED_TARGET_POS ) ) + { + setWeaponLock( commandButton->getWeaponSlot(), LOCKED_TEMPORARILY ); + //LOCATION BASED FIRE WEAPON + ai->aiAttackPosition( NULL, commandButton->getMaxShotsToFire(), cmdSource ); + } + else + { + DEBUG_CRASH( ("WARNING: Script doCommandButton for button %s cannot fire weapon with NO POSITION. Skipping.", commandButton->getName().str()) ); + } + return; + } + break; + + case GUI_COMMAND_OBJECT_UPGRADE: + case GUI_COMMAND_PLAYER_UPGRADE: + { + const UpgradeTemplate *upgradeT = commandButton->getUpgradeTemplate(); + DEBUG_ASSERTCRASH( upgradeT, ("Undefined upgrade '%s' in player upgrade command\n", "UNKNOWN") ); + // sanity + if( upgradeT == NULL ) + break; + if( upgradeT->getUpgradeType() == UPGRADE_TYPE_OBJECT ) + { + if( hasUpgrade( upgradeT ) || !affectedByUpgrade( upgradeT ) ) + break; + } + // producer must have a production update + ProductionUpdateInterface *pu = getProductionUpdateInterface(); + if( pu == NULL ) + break; + // queue the upgrade "research" + pu->queueUpgrade( upgradeT ); + } + return; + case GUI_COMMAND_UNIT_BUILD: + case GUI_COMMAND_DOZER_CONSTRUCT: { + const ThingTemplate *tt = commandButton->getThingTemplate(); + ProductionUpdateInterface *pu = this->getProductionUpdateInterface(); + if (pu && tt) { + pu->queueCreateUnit( tt, pu->requestUniqueUnitID()); + return; + } + break; + } + case GUI_COMMAND_HACK_INTERNET:{ + if( ai ) + { + ai->aiHackInternet( cmdSource ); + return; + } + break; + } + + case GUI_COMMAND_SELL: + TheBuildAssistant->sellObject( this ); + return; + + //Feel free to implement object based command buttons. + case GUI_COMMAND_COMBATDROP: + case GUI_COMMAND_DOZER_CONSTRUCT_CANCEL: + case GUI_COMMAND_CANCEL_UNIT_BUILD: + case GUI_COMMAND_CANCEL_UPGRADE: + case GUI_COMMAND_ATTACK_MOVE: + case GUI_COMMAND_GUARD: + case GUI_COMMAND_GUARD_WITHOUT_PURSUIT: + case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY: + case GUI_COMMAND_WAYPOINTS: + case GUI_COMMAND_EXIT_CONTAINER: + case GUI_COMMAND_EVACUATE: + case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT: + case GUI_COMMAND_BEACON_DELETE: + case GUI_COMMAND_SET_RALLY_POINT: + case GUI_COMMAND_TOGGLE_OVERCHARGE: +#ifdef ALLOW_SURRENDER + case GUI_COMMAND_POW_RETURN_TO_PRISON: +#endif + case GUICOMMANDMODE_HIJACK_VEHICLE: + case GUICOMMANDMODE_CONVERT_TO_CARBOMB: +#ifdef ALLOW_SURRENDER + case GUICOMMANDMODE_PICK_UP_PRISONER: +#endif + default: + break; + } + DEBUG_CRASH( ("WARNING: Script doCommandButton for button %s not implemented. Doing nothing.", commandButton->getName().str()) ); + } +} + +//------------------------------------------------------------------------------------------------- +/** Execute command button ability directed at an object target */ +//------------------------------------------------------------------------------------------------- +void Object::doCommandButtonAtObject( const CommandButton *commandButton, Object *obj, CommandSourceType cmdSource ) +{ + if (isDisabled()) + return; + + AIUpdateInterface *ai = getAIUpdateInterface(); + if( commandButton ) + { + switch( commandButton->getCommandType() ) + { + case GUI_COMMAND_COMBATDROP: + if( ai ) + { + ai->aiCombatDrop( obj, *(obj->getPosition()), cmdSource ); + } + return; + case GUI_COMMAND_SPECIAL_POWER: + { + if( commandButton->getSpecialPowerTemplate() ) + { + CommandOption commandOptions = (CommandOption)(commandButton->getOptions() | COMMAND_FIRED_BY_SCRIPT); + doSpecialPowerAtObject( commandButton->getSpecialPowerTemplate(), obj, commandOptions, cmdSource == CMD_FROM_SCRIPT ); + } + return; + } + + case GUI_COMMAND_STOP: + if( ai ) + { + ai->aiIdle( cmdSource ); + } + return; + + case GUI_COMMAND_FIRE_WEAPON: + if( ai ) + { + if( BitIsSet( commandButton->getOptions(), COMMAND_OPTION_NEED_OBJECT_TARGET ) ) + { + //OBJECT BASED FIRE WEAPON + if( !obj ) + { + break; + } + + if( !commandButton->isValidObjectTarget( this, obj ) ) + { + break; + } + + setWeaponLock( commandButton->getWeaponSlot(), LOCKED_TEMPORARILY ); + + if( BitIsSet( commandButton->getOptions(), ATTACK_OBJECTS_POSITION ) ) + { + //Actually, you know what.... we want to attack the object's location instead. + ai->aiAttackPosition( obj->getPosition(), commandButton->getMaxShotsToFire(), cmdSource ); + } + else + { + ai->aiAttackObject( obj, commandButton->getMaxShotsToFire(), cmdSource ); + } + } + else + { + DEBUG_CRASH( ("WARNING: Script doCommandButtonAtObject for button %s cannot fire weapon at AN OBJECT. Skipping.", commandButton->getName().str()) ); + } + return; + } + break; + + case GUICOMMANDMODE_HIJACK_VEHICLE: + case GUICOMMANDMODE_CONVERT_TO_CARBOMB: + case GUICOMMANDMODE_SABOTAGE_BUILDING: + if( ai ) + { + ai->aiEnter( obj, cmdSource ); + } + return; + + //Feel free to implement object based command buttons. + case GUI_COMMAND_DOZER_CONSTRUCT: + case GUI_COMMAND_DOZER_CONSTRUCT_CANCEL: + case GUI_COMMAND_UNIT_BUILD: + case GUI_COMMAND_CANCEL_UNIT_BUILD: + case GUI_COMMAND_PLAYER_UPGRADE: + case GUI_COMMAND_OBJECT_UPGRADE: + case GUI_COMMAND_CANCEL_UPGRADE: + case GUI_COMMAND_ATTACK_MOVE: + case GUI_COMMAND_GUARD: + case GUI_COMMAND_GUARD_WITHOUT_PURSUIT: + case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY: + case GUI_COMMAND_WAYPOINTS: + case GUI_COMMAND_EXIT_CONTAINER: + case GUI_COMMAND_EVACUATE: + case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT: + case GUI_COMMAND_BEACON_DELETE: + case GUI_COMMAND_SET_RALLY_POINT: + case GUI_COMMAND_SELL: + case GUI_COMMAND_HACK_INTERNET: + case GUI_COMMAND_TOGGLE_OVERCHARGE: + case GUI_COMMAND_SWITCH_WEAPON: + +#ifdef ALLOW_SURRENDER + case GUI_COMMAND_POW_RETURN_TO_PRISON: + case GUICOMMANDMODE_PICK_UP_PRISONER: +#endif + default: + break; + } + DEBUG_CRASH( ("WARNING: Script doCommandButtonAtObject for button %s not implemented. Doing nothing.", commandButton->getName().str()) ); + } +} + +//------------------------------------------------------------------------------------------------- +/** Execute command button ability directed at a location */ +//------------------------------------------------------------------------------------------------- +void Object::doCommandButtonAtPosition( const CommandButton *commandButton, const Coord3D *pos, CommandSourceType cmdSource ) +{ + if (isDisabled()) + return; + + AIUpdateInterface *ai = getAIUpdateInterface(); + if( commandButton ) + { + switch( commandButton->getCommandType() ) + { + case GUI_COMMAND_SPECIAL_POWER: + { + if( commandButton->getSpecialPowerTemplate() ) + { + CommandOption commandOptions = (CommandOption)(commandButton->getOptions() | COMMAND_FIRED_BY_SCRIPT); + doSpecialPowerAtLocation( commandButton->getSpecialPowerTemplate(), pos, INVALID_ANGLE, commandOptions, cmdSource == CMD_FROM_SCRIPT ); + return; + } + break; + } + case GUI_COMMAND_ATTACK_MOVE: + if( ai ) + { + ai->aiAttackMoveToPosition( pos, commandButton->getMaxShotsToFire(), cmdSource ); + return; + } + break; + case GUI_COMMAND_STOP: + if( ai ) + { + ai->aiIdle( cmdSource ); + return; + } + break; + + case GUI_COMMAND_DOZER_CONSTRUCT: + TheBuildAssistant->buildObjectNow( this, commandButton->getThingTemplate(), pos, 0.0f, getControllingPlayer() ); + return; + + case GUI_COMMAND_FIRE_WEAPON: + if( ai ) + { + if( BitIsSet( commandButton->getOptions(), NEED_TARGET_POS ) ) + { + //LOCATION BASED FIRE WEAPON + if( !pos ) + { + break; + } + setWeaponLock( commandButton->getWeaponSlot(), LOCKED_TEMPORARILY ); + ai->aiAttackPosition( pos, commandButton->getMaxShotsToFire(), cmdSource ); + } + else + { + DEBUG_CRASH( ("WARNING: Script doCommandButtonAtPosition for button %s cannot fire weapon at A POSITION. Skipping.", commandButton->getName().str()) ); + } + return; + } + break; + + case GUI_COMMAND_DOZER_CONSTRUCT_CANCEL: + case GUI_COMMAND_UNIT_BUILD: + case GUI_COMMAND_CANCEL_UNIT_BUILD: + case GUI_COMMAND_PLAYER_UPGRADE: + case GUI_COMMAND_OBJECT_UPGRADE: + case GUI_COMMAND_CANCEL_UPGRADE: + case GUI_COMMAND_GUARD: + case GUI_COMMAND_GUARD_WITHOUT_PURSUIT: + case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY: + case GUI_COMMAND_WAYPOINTS: + case GUI_COMMAND_EXIT_CONTAINER: + case GUI_COMMAND_EVACUATE: + case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT: + case GUI_COMMAND_BEACON_DELETE: + case GUI_COMMAND_SET_RALLY_POINT: + case GUI_COMMAND_SELL: + case GUI_COMMAND_HACK_INTERNET: + case GUI_COMMAND_TOGGLE_OVERCHARGE: +#ifdef ALLOW_SURRENDER + case GUI_COMMAND_POW_RETURN_TO_PRISON: +#endif + case GUI_COMMAND_COMBATDROP: + case GUI_COMMAND_SWITCH_WEAPON: + case GUICOMMANDMODE_HIJACK_VEHICLE: + case GUICOMMANDMODE_CONVERT_TO_CARBOMB: +#ifdef ALLOW_SURRENDER + case GUICOMMANDMODE_PICK_UP_PRISONER: +#endif + default: + break; + } + DEBUG_CRASH( ("WARNING: Script doCommandButtonAtPosition for button %s not implemented. Doing nothing.", commandButton->getName().str()) ); + } +} + +//------------------------------------------------------------------------------------------------- +/** Execute command button ability directed at a location */ +//------------------------------------------------------------------------------------------------- +void Object::doCommandButtonUsingWaypoints( const CommandButton *commandButton, const Waypoint *way, CommandSourceType cmdSource ) +{ + if (isDisabled()) + return; + + if( commandButton ) + { + if( !BitIsSet( commandButton->getOptions(), CAN_USE_WAYPOINTS ) ) + { + //Our button doesn't support waypoints. + DEBUG_CRASH( ("WARNING: Script doCommandButtonUsingWaypoints for button %s lacks CAN_USE_WAYPOINTS option. Doing nothing.", commandButton->getName().str()) ); + return; + } + switch( commandButton->getCommandType() ) + { + case GUI_COMMAND_SPECIAL_POWER: + { + if( commandButton->getSpecialPowerTemplate() ) + { + CommandOption commandOptions = (CommandOption)(commandButton->getOptions() | COMMAND_FIRED_BY_SCRIPT); + doSpecialPowerUsingWaypoints( commandButton->getSpecialPowerTemplate(), way, commandOptions, cmdSource == CMD_FROM_SCRIPT ); + return; + } + break; + } + case GUI_COMMAND_ATTACK_MOVE: + case GUI_COMMAND_STOP: + case GUI_COMMAND_DOZER_CONSTRUCT: + case GUI_COMMAND_DOZER_CONSTRUCT_CANCEL: + case GUI_COMMAND_UNIT_BUILD: + case GUI_COMMAND_CANCEL_UNIT_BUILD: + case GUI_COMMAND_PLAYER_UPGRADE: + case GUI_COMMAND_OBJECT_UPGRADE: + case GUI_COMMAND_CANCEL_UPGRADE: + case GUI_COMMAND_GUARD: + case GUI_COMMAND_GUARD_WITHOUT_PURSUIT: + case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY: + case GUI_COMMAND_WAYPOINTS: + case GUI_COMMAND_EXIT_CONTAINER: + case GUI_COMMAND_EVACUATE: + case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT: + case GUI_COMMAND_BEACON_DELETE: + case GUI_COMMAND_SET_RALLY_POINT: + case GUI_COMMAND_SELL: + case GUI_COMMAND_FIRE_WEAPON: + case GUI_COMMAND_HACK_INTERNET: + case GUI_COMMAND_TOGGLE_OVERCHARGE: +#ifdef ALLOW_SURRENDER + case GUI_COMMAND_POW_RETURN_TO_PRISON: +#endif + case GUI_COMMAND_COMBATDROP: + case GUI_COMMAND_SWITCH_WEAPON: + case GUICOMMANDMODE_HIJACK_VEHICLE: + case GUICOMMANDMODE_CONVERT_TO_CARBOMB: +#ifdef ALLOW_SURRENDER + case GUICOMMANDMODE_PICK_UP_PRISONER: +#endif + default: + break; + } + DEBUG_CRASH( ("WARNING: Script doCommandButtonUsingWaypoints for button %s not implemented. Doing nothing.", commandButton->getName().str()) ); + } +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void Object::clearLeechRangeModeForAllWeapons() +{ + m_weaponSet.clearLeechRangeModeForAllWeapons(); +} + +// ------------------------------------------------------------------------------------------------ +/** Search our update modules for a production update interface and return it if one is found */ +// ------------------------------------------------------------------------------------------------ +ProductionUpdateInterface* Object::getProductionUpdateInterface( void ) +{ + ProductionUpdateInterface *pui; + + // tell our update modules that we intend to do this special power. + for( BehaviorModule** u = m_behaviors; *u; ++u ) + { + + pui = (*u)->getProductionUpdateInterface(); + if( pui ) + return pui; + + } // end for + + return NULL; + +} // end getProductionUpdateInterface + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +DockUpdateInterface *Object::getDockUpdateInterface( void ) +{ + DockUpdateInterface *dock = NULL; + + for( BehaviorModule **u = m_behaviors; *u; ++u ) + { + if( (dock = (*u)->getDockUpdateInterface()) != NULL ) + return dock; + } + + return NULL; + +} // end getDockUpdateInterface + +// ------------------------------------------------------------------------------------------------ +// Search our special power modules for a specific one. +// ------------------------------------------------------------------------------------------------ +SpecialPowerModuleInterface* Object::findSpecialPowerModuleInterface( SpecialPowerType type ) const +{ + for (BehaviorModule** m = m_behaviors; *m; ++m) + { + SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); + if (!sp) + continue; + + const SpecialPowerTemplate *spTemplate = sp->getSpecialPowerTemplate(); + if (spTemplate && spTemplate->getSpecialPowerType() == type || type == SPECIAL_INVALID ) + { + return sp; + } + } + return NULL; +} + +// ------------------------------------------------------------------------------------------------ +// Search our special power modules for the first occurrence of a shortcut special. +// ------------------------------------------------------------------------------------------------ +SpecialPowerModuleInterface* Object::findAnyShortcutSpecialPowerModuleInterface() const +{ + for( BehaviorModule** m = m_behaviors; *m; ++m ) + { + SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); + if (!sp) + continue; + + const SpecialPowerTemplate *spTemplate = sp->getSpecialPowerTemplate(); + if( spTemplate && spTemplate->isShortcutPower() ) + { + return sp; + } + } + return NULL; +} + +// ------------------------------------------------------------------------------------------------ +/** Get spawn behavior interface from object */ +// ------------------------------------------------------------------------------------------------ +SpawnBehaviorInterface* Object::getSpawnBehaviorInterface() const +{ + for (BehaviorModule** m = m_behaviors; *m; ++m) + { + SpawnBehaviorInterface *sbi = (*m)->getSpawnBehaviorInterface(); + if( sbi ) + { + return sbi; + } + } + return NULL; +} // end getSpawnBehaviorInterfaceFromObject + +// ------------------------------------------------------------------------------------------------ +ProjectileUpdateInterface* Object::getProjectileUpdateInterface() const +{ + for (BehaviorModule** m = m_behaviors; *m; ++m) + { + ProjectileUpdateInterface *pui = (*m)->getProjectileUpdateInterface(); + if( pui ) + { + return pui; + } + } + return NULL; +} + +// ------------------------------------------------------------------------------------------------ +// Simply find the special power module that is currently allowing plotting of positions to target. +// ------------------------------------------------------------------------------------------------ +SpecialPowerUpdateInterface* Object::findSpecialPowerWithOverridableDestinationActive( SpecialPowerType type ) const +{ + for( BehaviorModule** u = m_behaviors; *u; ++u ) + { + SpecialPowerUpdateInterface *spInterface = (*u)->getSpecialPowerUpdateInterface(); + if( spInterface ) + { + if( spInterface->doesSpecialPowerHaveOverridableDestinationActive() ) + { + return spInterface; + } + } + } // end for + return NULL; +} + +// ------------------------------------------------------------------------------------------------ +// Simply find the special power module that is potentially allowed to plot positions to target. +// ------------------------------------------------------------------------------------------------ +SpecialPowerUpdateInterface* Object::findSpecialPowerWithOverridableDestination( SpecialPowerType type ) const +{ + for( BehaviorModule** u = m_behaviors; *u; ++u ) + { + SpecialPowerUpdateInterface *spInterface = (*u)->getSpecialPowerUpdateInterface(); + if( spInterface ) + { + if( spInterface->doesSpecialPowerHaveOverridableDestination() ) + { + return spInterface; + } + } + } // end for + return NULL; +} + + +// ------------------------------------------------------------------------------------------------ +// Search our special ability updates for a specific one. +// ------------------------------------------------------------------------------------------------ +SpecialAbilityUpdate* Object::findSpecialAbilityUpdate( SpecialPowerType type ) const +{ + for( BehaviorModule** u = m_behaviors; *u; ++u ) + { + SpecialPowerUpdateInterface *spInterface = (*u)->getSpecialPowerUpdateInterface(); + if( spInterface && spInterface->isSpecialAbility() ) + { + SpecialAbilityUpdate *spUpdate = (SpecialAbilityUpdate*)spInterface; + if( spUpdate->getSpecialPowerType() == type ) + { + return spUpdate; + } + } + } // end for + + return NULL; +} + +// ------------------------------------------------------------------------------------------------ +SpecialPowerCompletionDie* Object::findSpecialPowerCompletionDie() const +{ + static NameKeyType key_SpecialPowerCompletionDie = NAMEKEY("SpecialPowerCompletionDie"); + return (SpecialPowerCompletionDie*)findModule(key_SpecialPowerCompletionDie); +} + +// ------------------------------------------------------------------------------------------------ +Int Object::getNumConsecutiveShotsFiredAtTarget( const Object *victim ) const +{ + return m_firingTracker ? m_firingTracker->getNumConsecutiveShotsAtVictim( victim ) : 0; +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +Bool Object::getSingleLogicalBonePosition(const char* boneName, Coord3D* position, Matrix3D* transform) const +{ + if (m_drawable && m_drawable->getPristineBonePositions( boneName, 0, position, transform, 1 ) == 1 ) + { + m_drawable->convertBonePosToWorldPos( position, transform, position, transform ); + return true; + } + else + { + if (position) + *position = *getPosition(); + if (transform) + *transform = *getTransformMatrix(); + return false; + } +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +Bool Object::getSingleLogicalBonePositionOnTurret( WhichTurretType whichTurret, const char* boneName, Coord3D* position, Matrix3D* transform ) const +{ + Coord3D turretPosition; + Coord3D bonePosition; + if( getDrawable() == NULL || getAI() == NULL ) + return FALSE; + + // We need to find the TurretBone's pristine position. + getDrawable()->getProjectileLaunchOffset( PRIMARY_WEAPON, 1, NULL, whichTurret, &turretPosition, NULL ); + // And the required bone's pristine position + if( getDrawable()->getPristineBonePositions(boneName, 0, &bonePosition, NULL, 1) != 1 ) + return FALSE; + //Then we mojo the Logic position of the required bone like Missile firing does. Using the logic twist of the turret + Real turretRotation; + getAI()->getTurretRotAndPitch( whichTurret, &turretRotation, NULL ); + + Matrix3D boneOffset(TRUE);// This will be from the turret to the requested bone + +// Vector3 bonePositionVector( bonePosition.x - turretPosition.x, +// bonePosition.y - turretPosition.y, +// bonePosition.z - turretPosition.z ); + Vector3 bonePositionVector( bonePosition.x, + bonePosition.y, + bonePosition.z ); + boneOffset.Translate(bonePositionVector); + + Matrix3D turnAdjustment(TRUE);// this is the turret twist to be applied to the final answer + + turnAdjustment.Translate( turretPosition.x, turretPosition.y, turretPosition.z ); + turnAdjustment.In_Place_Pre_Rotate_Z(turretRotation); + turnAdjustment.Translate( -turretPosition.x, -turretPosition.y, -turretPosition.z ); + + Matrix3D boneLogicTransform; + boneLogicTransform.mul( turnAdjustment, boneOffset ); + + Matrix3D worldTransform; + convertBonePosToWorldPos(NULL, &boneLogicTransform, NULL, &worldTransform); + + Vector3 tmp = worldTransform.Get_Translation(); + Coord3D worldPos; + worldPos.x = tmp.X; + worldPos.y = tmp.Y; + worldPos.z = tmp.Z; + + if( position ) + *position = worldPos; + if( transform ) + *transform = worldTransform; + + return TRUE; +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +Int Object::getMultiLogicalBonePosition(const char* boneNamePrefix, Int maxBones, + Coord3D* positions, Matrix3D* transforms, + Bool convertToWorld ) const +{ + Int count; + if (m_drawable && (count = m_drawable->getPristineBonePositions( boneNamePrefix, 1, positions, transforms, maxBones )) > 0 ) + { + if( convertToWorld ) + { + for (Int i = 0; i < count; ++i) + m_drawable->convertBonePosToWorldPos( positions ? &positions[i] : NULL, transforms ? &transforms[i] : NULL, positions ? &positions[i] : NULL, transforms ? &transforms[i] : NULL ); + } + return count; + } + else + { + return 0; + } +} + +//============================================================================= +const AsciiString& Object::getCommandSetString() const +{ + if (m_commandSetStringOverride.isNotEmpty()) + return m_commandSetStringOverride; + + return getTemplate()->friend_getCommandSetString(); +} + +//============================================================================= +Bool Object::canProduceUpgrade( const UpgradeTemplate *upgrade ) +{ + // We need to have the button to make the upgrade. CommandSets are a weird Logic/Client hybrid. + const CommandSet *set = TheControlBar->findCommandSet(getCommandSetString()); + + for( Int buttonIndex = 0; buttonIndex < MAX_COMMANDS_PER_SET; buttonIndex++ ) + { + const CommandButton *button = set->getCommandButton(buttonIndex); + if( button && button->getUpgradeTemplate() && (button->getUpgradeTemplate() == upgrade) ) + return TRUE; // getUpgradeTemplate only returns something if it is actually an upgrade + } + + return FALSE;// Cheatin' punk. +} + +//============================================================================= +// Object::defect, and related methods = +//============================================================================= +void Object::defect( Team* newTeam, UnsignedInt detectionTime ) +{ + if ( isContained() ) //@todo (KRIS?) make contained units unselectable, until then... lorenzen + { + return; + } + + Player *player = getControllingPlayer(); + if ( !player ) + return; + + Team* myTeam = player->getDefaultTeam(); + if ( myTeam == newTeam ) // can't defect from my own team, that would be silly + return; + + // things that are under construction, or sold, cannot defect. + if (testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) || + testStatus(OBJECT_STATUS_SOLD)) + { + return; + } + + // Before switch //////////////////////////////////////// + + //Design says: + ProductionUpdateInterface *production = getProductionUpdateInterface(); + if ( production ) + { + production->cancelAndRefundAllProduction(); + } + + // pop it up on the radar, so as to warn those who care + // do this first, since after setTeam() the infiltrator + // becomes the controllingplayer, not me + + // But don't do this is if the new team is not a real team. "'Enemy' infiltration" wouldn't make + // sense, and we are probably just reverting a cave or something. + if( friend_getRadarData() && newTeam->getControllingPlayer()->isPlayableSide() && myTeam->getControllingPlayer()->isPlayableSide()) + { + TheRadar->tryInfiltrationEvent( this ); + } + + friend_setUndetectedDefector( detectionTime > 0 ); + + if (m_defectionHelper) + m_defectionHelper->startDefectionTimer(detectionTime); + + // Switch //////////////////////////////////////// + setTeam( newTeam ); + + // After switch //////////////////////////////////////// + + AIUpdateInterface *ai = getAI(); + + handlePartitionCellMaintenance();// to clear the shoud for my new master + + if ( ai ) + { + ai->aiIdle( CMD_FROM_AI ); + } + + // Play our sound indicating we've been defected. (weird verbage, but true.) + AudioEventRTS voiceDefect = *getTemplate()->getVoiceDefect(); + voiceDefect.setObjectID(getID()); + TheAudio->addAudioEvent(&voiceDefect); + + //make the new recruit the only selected thing, awaiting new command to move, attack, etc... + Drawable *dr = getDrawable(); + if (dr) + { + dr->flashAsSelected(); //This is the first of several flashes which get cue'd by doDefectorUpdateStuff() + AudioEventRTS defectorTimerSound = TheAudio->getMiscAudio()->m_defectorTimerTickSound; + defectorTimerSound.setObjectID( getID() ); + TheAudio->addAudioEvent(&defectorTimerSound); + } + + ContainModuleInterface *ct = getContain(); + if( ct && ct->isKickOutOnCapture() ) + { + // Caves really really don't want to do this. + ct->removeAllContained( TRUE ); + } + + // if it has parking places, defect anything parked there. + for (BehaviorModule** i = getBehaviorModules(); *i; ++i) + { + ParkingPlaceBehaviorInterface* pp = (*i)->getParkingPlaceBehaviorInterface(); + if (pp) + { + pp->defectAllParkedUnits(newTeam, detectionTime); + break; + } + } + + // defect any mines that are owned by this structure, right now. + // unfortunately, structures don't keep list of mines they own, so we must do + // this the hard way :-( [fortunately, this doens't happen very often, so this + // is probably an acceptable, if icky, solution.] (srj) + for (Object* mine = TheGameLogic->getFirstObject(); mine; mine = mine->getNextObject()) + { + if (mine->isKindOf(KINDOF_MINE)) + { + if (mine->getProducerID() == this->getID()) + { + mine->setTeam(newTeam); + } + } + } + +} + +//============================================================================= +// Object::goInvulnerable +//============================================================================= +void Object::goInvulnerable( UnsignedInt time ) +{ + const Bool WITHOUT_DEFECTOR_FX = FALSE; + + + friend_setUndetectedDefector( time > 0 ); + + if (m_defectionHelper) + m_defectionHelper->startDefectionTimer(time, WITHOUT_DEFECTOR_FX); + +} + +// ------------------------------------------------------------------------------------------------ +/** Return the radar priority for this object type */ +// ------------------------------------------------------------------------------------------------ +RadarPriorityType Object::getRadarPriority( void ) const +{ + RadarPriorityType priority = RADAR_PRIORITY_INVALID; + + // first, get the priority at the thing template level + priority = getTemplate()->getDefaultRadarPriority(); + + // + // there are some objects that we want to show up on the radar when they have + // certain properties ... here we will check for those properties unless the INI + // setting of "not on radar" has been manually entered which explicitly forbids an + // object from being on the radar ... by default objects get an "invalid" priority + // on the radar and this means that we are free to decide one here if we want + // + if( priority == RADAR_PRIORITY_INVALID ) + { + + // objects that are "garrisonable" show up on the radar + ContainModuleInterface *cmi = getContain(); + if( cmi && cmi->isGarrisonable() ) + priority = RADAR_PRIORITY_STRUCTURE; + + // objects that are "capturable" show up on the radar + if( isKindOf( KINDOF_CAPTURABLE ) ) + priority = RADAR_PRIORITY_STRUCTURE; + + + } // end if + + // Carbombs will show up as units regardless of their default priority + if ( testStatus( OBJECT_STATUS_IS_CARBOMB ) ) + priority = RADAR_PRIORITY_UNIT; + + + // return the priority we're going to use + return priority; + +} // end getRadarPriority + +// ------------------------------------------------------------------------------------------------ +AIGroup *Object::getGroup(void) +{ + return m_group; +} + +//------------------------------------------------------------------------------------------------- +void Object::enterGroup( AIGroup *group ) +{ +// DEBUG_LOG(("***AIGROUP %x involved in enterGroup on %x\n", group, this)); + // if we are in another group, remove ourselves from it first + leaveGroup(); + + m_group = group; +} + +//------------------------------------------------------------------------------------------------- +void Object::leaveGroup( void ) +{ +// DEBUG_LOG(("***AIGROUP %x involved in leaveGroup on %x\n", m_group, this)); + // if we are in a group, remove ourselves from it + if (m_group) + { + // to avoid recursion, set m_group to NULL before removing + AIGroup *group = m_group; + m_group = NULL; + group->remove( this ); + } +} + +//------------------------------------------------------------------------------------------------- +Real Object::getCarrierDeckHeight() const +{ + Object *producer = TheGameLogic->findObjectByID( getProducerID() ); + if( producer ) + { + // Find a parking place behavior. + for( BehaviorModule** i = producer->getBehaviorModules(); *i; ++i ) + { + ParkingPlaceBehaviorInterface* pp = (*i)->getParkingPlaceBehaviorInterface(); + if( pp ) + { + return pp->getLandingDeckHeightOffset(); + } + } + } + return 0.0f; +} + +//------------------------------------------------------------------------------------------------- +CountermeasuresBehaviorInterface* Object::getCountermeasuresBehaviorInterface() +{ + for( BehaviorModule** i = getBehaviorModules(); *i; ++i ) + { + CountermeasuresBehaviorInterface* cbi = (*i)->getCountermeasuresBehaviorInterface(); + if( cbi ) + { + return cbi; + } + } + return NULL; +} + +//------------------------------------------------------------------------------------------------- +const CountermeasuresBehaviorInterface* Object::getCountermeasuresBehaviorInterface() const +{ + for( BehaviorModule** i = getBehaviorModules(); *i; ++i ) + { + const CountermeasuresBehaviorInterface* cbi = (*i)->getCountermeasuresBehaviorInterface(); + if( cbi ) + { + return cbi; + } + } + return NULL; +} + +//------------------------------------------------------------------------------------------------- +Bool Object::hasCountermeasures() const +{ + const CountermeasuresBehaviorInterface* cbi = getCountermeasuresBehaviorInterface(); + if( cbi && cbi->isActive() ) + { + return TRUE; + } + return FALSE; +} + +//------------------------------------------------------------------------------------------------- +void Object::reportMissileForCountermeasures( Object *missile ) +{ + for( BehaviorModule** i = getBehaviorModules(); *i; ++i ) + { + CountermeasuresBehaviorInterface* cbi = (*i)->getCountermeasuresBehaviorInterface(); + if( cbi ) + { + cbi->reportMissileForCountermeasures( missile ); + } + } +} + +//------------------------------------------------------------------------------------------------- +ObjectID Object::calculateCountermeasureToDivertTo( const Object& victim ) +{ + AIUpdateInterface *ai = getAI(); + if( ai ) + { + for( BehaviorModule** i = victim.getBehaviorModules(); *i; ++i ) + { + CountermeasuresBehaviorInterface* cbi = (*i)->getCountermeasuresBehaviorInterface(); + if( cbi ) + { + ObjectID decoyID = cbi->calculateCountermeasureToDivertTo( victim ); + return decoyID; + } + } + } + return INVALID_ID; +} diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/UpgradeSpecialPower.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/UpgradeSpecialPower.cpp index 7f839c16def..c8e76cda709 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/UpgradeSpecialPower.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/UpgradeSpecialPower.cpp @@ -1,175 +1,175 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: UpgradeSpecialPower.cpp ///////////////////////////////////////////////////////////////// -// Author: Andreas W, July 25 -// Desc: Special Power will grant an upgrade to the object -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -#include "Common/Xfer.h" -#include "Common/Player.h" -#include "Common/Upgrade.h" -#include "GameLogic/Object.h" -#include "GameLogic/Module/UpgradeSpecialPower.h" - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -UpgradeSpecialPowerModuleData::UpgradeSpecialPowerModuleData(void) -{ - m_upgradeName = ""; -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void UpgradeSpecialPowerModuleData::buildFieldParse(MultiIniFieldParse& p) -{ - SpecialPowerModuleData::buildFieldParse(p); - - static const FieldParse dataFieldParse[] = - { - { "UpgradeToGrant", INI::parseAsciiString, NULL, offsetof(UpgradeSpecialPowerModuleData, m_upgradeName) }, - { 0, 0, 0, 0 } - }; - p.add(dataFieldParse); - -} // end buildFieldParse - -/////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -UpgradeSpecialPower::UpgradeSpecialPower(Thing* thing, const ModuleData* moduleData) - : SpecialPowerModule(thing, moduleData) -{ - -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -UpgradeSpecialPower::~UpgradeSpecialPower(void) -{ - -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void UpgradeSpecialPower::grantUpgrade(Object* object) { - - // get module data - const UpgradeSpecialPowerModuleData* modData = getUpgradeSpecialPowerModuleData(); - - const UpgradeTemplate* upgradeTemplate = TheUpgradeCenter->findUpgrade(modData->m_upgradeName); - if (!upgradeTemplate) - { - DEBUG_ASSERTCRASH(0, ("UpgradeSpecialPower for %s can't find upgrade template %s.", getObject()->getName(), modData->m_upgradeName)); - return; - } - - Player* player = object->getControllingPlayer(); - if (upgradeTemplate->getUpgradeType() == UPGRADE_TYPE_PLAYER) - { - // get the player - player->addUpgrade(upgradeTemplate, UPGRADE_STATUS_COMPLETE); - } - else - { - object->giveUpgrade(upgradeTemplate); - } - - player->getAcademyStats()->recordUpgrade(upgradeTemplate, TRUE); -} - - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void UpgradeSpecialPower::doSpecialPower(UnsignedInt commandOptions) -{ - if (getObject()->isDisabled()) - return; - - // call the base class action cause we are *EXTENDING* functionality - SpecialPowerModule::doSpecialPower(commandOptions); - - // Grant the upgrade - grantUpgrade(getObject()); -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void UpgradeSpecialPower::doSpecialPowerAtObject(Object* obj, UnsignedInt commandOptions) -{ - if (getObject()->isDisabled()) - return; - - // call the base class action cause we are *EXTENDING* functionality - SpecialPowerModule::doSpecialPowerAtObject(obj, commandOptions); - - // Grant the upgrade - grantUpgrade(obj); -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void UpgradeSpecialPower::crc(Xfer* xfer) -{ - - // extend base class - SpecialPowerModule::crc(xfer); - -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ - // ------------------------------------------------------------------------------------------------ -void UpgradeSpecialPower::xfer(Xfer* xfer) -{ - - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion(&version, currentVersion); - - // extend base class - SpecialPowerModule::xfer(xfer); - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void UpgradeSpecialPower::loadPostProcess(void) -{ - - // extend base class - SpecialPowerModule::loadPostProcess(); - -} // end loadPostProcess +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: UpgradeSpecialPower.cpp ///////////////////////////////////////////////////////////////// +// Author: Andreas W, July 25 +// Desc: Special Power will grant an upgrade to the object +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#include "Common/Xfer.h" +#include "Common/Player.h" +#include "Common/Upgrade.h" +#include "GameLogic/Object.h" +#include "GameLogic/Module/UpgradeSpecialPower.h" + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +UpgradeSpecialPowerModuleData::UpgradeSpecialPowerModuleData(void) +{ + m_upgradeName = ""; +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPowerModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + SpecialPowerModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + { "UpgradeToGrant", INI::parseAsciiString, NULL, offsetof(UpgradeSpecialPowerModuleData, m_upgradeName) }, + { 0, 0, 0, 0 } + }; + p.add(dataFieldParse); + +} // end buildFieldParse + +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +UpgradeSpecialPower::UpgradeSpecialPower(Thing* thing, const ModuleData* moduleData) + : SpecialPowerModule(thing, moduleData) +{ + +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +UpgradeSpecialPower::~UpgradeSpecialPower(void) +{ + +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPower::grantUpgrade(Object* object) { + + // get module data + const UpgradeSpecialPowerModuleData* modData = getUpgradeSpecialPowerModuleData(); + + const UpgradeTemplate* upgradeTemplate = TheUpgradeCenter->findUpgrade(modData->m_upgradeName); + if (!upgradeTemplate) + { + DEBUG_ASSERTCRASH(0, ("UpgradeSpecialPower for %s can't find upgrade template %s.", getObject()->getName(), modData->m_upgradeName)); + return; + } + + Player* player = object->getControllingPlayer(); + if (upgradeTemplate->getUpgradeType() == UPGRADE_TYPE_PLAYER) + { + // get the player + player->addUpgrade(upgradeTemplate, UPGRADE_STATUS_COMPLETE); + } + else + { + object->giveUpgrade(upgradeTemplate); + } + + player->getAcademyStats()->recordUpgrade(upgradeTemplate, TRUE); +} + + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPower::doSpecialPower(UnsignedInt commandOptions) +{ + if (getObject()->isDisabled()) + return; + + // call the base class action cause we are *EXTENDING* functionality + SpecialPowerModule::doSpecialPower(commandOptions); + + // Grant the upgrade + grantUpgrade(getObject()); +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPower::doSpecialPowerAtObject(Object* obj, UnsignedInt commandOptions) +{ + if (getObject()->isDisabled()) + return; + + // call the base class action cause we are *EXTENDING* functionality + SpecialPowerModule::doSpecialPowerAtObject(obj, commandOptions); + + // Grant the upgrade + grantUpgrade(obj); +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPower::crc(Xfer* xfer) +{ + + // extend base class + SpecialPowerModule::crc(xfer); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ + // ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPower::xfer(Xfer* xfer) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion(&version, currentVersion); + + // extend base class + SpecialPowerModule::xfer(xfer); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPower::loadPostProcess(void) +{ + + // extend base class + SpecialPowerModule::loadPostProcess(); + +} // end loadPostProcess diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp index 7b3fbd13a56..aa5c036c8b6 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp @@ -1,198 +1,198 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: RadiusDecalBehavior.cpp /////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -#include "Common/RandomValue.h" -#include "Common/Xfer.h" -#include "GameLogic/GameLogic.h" -#include "GameLogic/Module/RadiusDecalBehavior.h" -#include "GameLogic/Object.h" - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- - -RadiusDecalBehaviorModuleData::RadiusDecalBehaviorModuleData() -{ - m_initiallyActive = false; - m_decalRadius = 0.0f; -} -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -/*static*/ void RadiusDecalBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p) -{ - UpdateModuleData::buildFieldParse(p); - static const FieldParse dataFieldParse[] = - { - { "StartsActive", INI::parseBool, NULL, offsetof(RadiusDecalBehaviorModuleData, m_initiallyActive) }, - { "RadiusDecal", RadiusDecalTemplate::parseRadiusDecalTemplate, NULL, offsetof( RadiusDecalBehaviorModuleData, m_decalTemplate) }, - { "Radius", INI::parseReal, NULL, offsetof( RadiusDecalBehaviorModuleData, m_decalRadius) }, - { 0, 0, 0, 0 } - }; - - BehaviorModuleData::buildFieldParse(p); - p.add(dataFieldParse); - p.add(UpgradeMuxData::getFieldParse(), offsetof(RadiusDecalBehaviorModuleData, m_upgradeMuxData)); -} - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -RadiusDecalBehavior::RadiusDecalBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData ) -{ - if (getRadiusDecalBehaviorModuleData()->m_initiallyActive) - { - giveSelfUpgrade(); - } - else { - clearDecal(); - setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -RadiusDecalBehavior::~RadiusDecalBehavior( void ) -{ - clearDecal(); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void RadiusDecalBehavior::createRadiusDecal( void ) -{ - const RadiusDecalBehaviorModuleData* data = getRadiusDecalBehaviorModuleData(); - const RadiusDecalTemplate& tmpl = data->m_decalTemplate; - m_radiusDecal.clear(); - if (tmpl.valid()) { - // DEBUG_LOG(("RadiusDecalBehavior::createRadiusDecal: \n")); - // tmpl.debugPrint(); - tmpl.createRadiusDecal(*(getObject()->getPosition()), data->m_decalRadius, getObject()->getControllingPlayer(), m_radiusDecal); - setWakeFrame(getObject(), m_radiusDecal.isEmpty() ? UPDATE_SLEEP_FOREVER : UPDATE_SLEEP_NONE); - } - else { - // We don't have a decal defined. Do we need this? - setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void RadiusDecalBehavior::killRadiusDecal() -{ - clearDecal(); - setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); -} - -// ----------------------------------------------------------------------------------------------- -void RadiusDecalBehavior::clearDecal() -{ - m_radiusDecal.clear(); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -UpdateSleepTime RadiusDecalBehavior::update( void ) -{ - if (getObject()->isDisabledByType(DISABLED_HELD)) { - if (!m_radiusDecal.isEmpty()) - clearDecal(); - return UPDATE_SLEEP_NONE; // We wait to be re-enabled - } - - // Upgrade has not been triggered, or it might have been removed. - if (!isUpgradeActive()) { - clearDecal(); - return UPDATE_SLEEP_FOREVER; - } - - // The object is dead - if (getObject()->isEffectivelyDead()) { - clearDecal(); - return UPDATE_SLEEP_FOREVER; - } - - // This should be our usual case - if (!m_radiusDecal.isEmpty()) { - m_radiusDecal.update(); - m_radiusDecal.setPosition(*(getObject()->getPosition())); - return UPDATE_SLEEP_NONE; - } - - // We get here if we were disabled - createRadiusDecal(); - return UPDATE_SLEEP_NONE; - - // Something probably went wrong if we reach this point - //return UPDATE_SLEEP_FOREVER; -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void RadiusDecalBehavior::crc( Xfer *xfer ) -{ - - // extend base class - UpdateModule::crc( xfer ); - -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void RadiusDecalBehavior::xfer( Xfer *xfer ) -{ - - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // extend base class - UpdateModule::xfer( xfer ); - - // decal, if any - m_radiusDecal.xferRadiusDecal(xfer); - - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void RadiusDecalBehavior::loadPostProcess( void ) -{ - - // extend base class - UpdateModule::loadPostProcess(); - -} // end loadPostProcess +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: RadiusDecalBehavior.cpp /////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#include "Common/RandomValue.h" +#include "Common/Xfer.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/Module/RadiusDecalBehavior.h" +#include "GameLogic/Object.h" + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +RadiusDecalBehaviorModuleData::RadiusDecalBehaviorModuleData() +{ + m_initiallyActive = false; + m_decalRadius = 0.0f; +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +/*static*/ void RadiusDecalBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + UpdateModuleData::buildFieldParse(p); + static const FieldParse dataFieldParse[] = + { + { "StartsActive", INI::parseBool, NULL, offsetof(RadiusDecalBehaviorModuleData, m_initiallyActive) }, + { "RadiusDecal", RadiusDecalTemplate::parseRadiusDecalTemplate, NULL, offsetof( RadiusDecalBehaviorModuleData, m_decalTemplate) }, + { "Radius", INI::parseReal, NULL, offsetof( RadiusDecalBehaviorModuleData, m_decalRadius) }, + { 0, 0, 0, 0 } + }; + + BehaviorModuleData::buildFieldParse(p); + p.add(dataFieldParse); + p.add(UpgradeMuxData::getFieldParse(), offsetof(RadiusDecalBehaviorModuleData, m_upgradeMuxData)); +} + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +RadiusDecalBehavior::RadiusDecalBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData ) +{ + if (getRadiusDecalBehaviorModuleData()->m_initiallyActive) + { + giveSelfUpgrade(); + } + else { + clearDecal(); + setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +RadiusDecalBehavior::~RadiusDecalBehavior( void ) +{ + clearDecal(); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void RadiusDecalBehavior::createRadiusDecal( void ) +{ + const RadiusDecalBehaviorModuleData* data = getRadiusDecalBehaviorModuleData(); + const RadiusDecalTemplate& tmpl = data->m_decalTemplate; + m_radiusDecal.clear(); + if (tmpl.valid()) { + // DEBUG_LOG(("RadiusDecalBehavior::createRadiusDecal: \n")); + // tmpl.debugPrint(); + tmpl.createRadiusDecal(*(getObject()->getPosition()), data->m_decalRadius, getObject()->getControllingPlayer(), m_radiusDecal); + setWakeFrame(getObject(), m_radiusDecal.isEmpty() ? UPDATE_SLEEP_FOREVER : UPDATE_SLEEP_NONE); + } + else { + // We don't have a decal defined. Do we need this? + setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void RadiusDecalBehavior::killRadiusDecal() +{ + clearDecal(); + setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); +} + +// ----------------------------------------------------------------------------------------------- +void RadiusDecalBehavior::clearDecal() +{ + m_radiusDecal.clear(); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +UpdateSleepTime RadiusDecalBehavior::update( void ) +{ + if (getObject()->isDisabledByType(DISABLED_HELD)) { + if (!m_radiusDecal.isEmpty()) + clearDecal(); + return UPDATE_SLEEP_NONE; // We wait to be re-enabled + } + + // Upgrade has not been triggered, or it might have been removed. + if (!isUpgradeActive()) { + clearDecal(); + return UPDATE_SLEEP_FOREVER; + } + + // The object is dead + if (getObject()->isEffectivelyDead()) { + clearDecal(); + return UPDATE_SLEEP_FOREVER; + } + + // This should be our usual case + if (!m_radiusDecal.isEmpty()) { + m_radiusDecal.update(); + m_radiusDecal.setPosition(*(getObject()->getPosition())); + return UPDATE_SLEEP_NONE; + } + + // We get here if we were disabled + createRadiusDecal(); + return UPDATE_SLEEP_NONE; + + // Something probably went wrong if we reach this point + //return UPDATE_SLEEP_FOREVER; +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void RadiusDecalBehavior::crc( Xfer *xfer ) +{ + + // extend base class + UpdateModule::crc( xfer ); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void RadiusDecalBehavior::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // extend base class + UpdateModule::xfer( xfer ); + + // decal, if any + m_radiusDecal.xferRadiusDecal(xfer); + + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void RadiusDecalBehavior::loadPostProcess( void ) +{ + + // extend base class + UpdateModule::loadPostProcess(); + +} // end loadPostProcess diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/LocomotorSetUpgrade.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/LocomotorSetUpgrade.cpp index c5550ff49ef..8899f65ab6e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/LocomotorSetUpgrade.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/LocomotorSetUpgrade.cpp @@ -1,146 +1,146 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: LocomotorSetUpgrade.cpp ///////////////////////////////////////////////////////////////////////////// -// Author: Graham Smallwood, March 2002 -// Desc: UpgradeModule that sets a weapon set bit for the Best Fit weapon set chooser to discover -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -#define DEFINE_LOCOMOTORSET_NAMES //Gain access to TheLocomotorSetNames[] - -#include "Common/Xfer.h" -#include "GameLogic/Object.h" -#include "GameLogic/Module/LocomotorSetUpgrade.h" -#include "GameLogic/Module/AIUpdate.h" - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -LocomotorSetUpgradeModuleData::LocomotorSetUpgradeModuleData(void) -{ - m_setUpgraded = TRUE; - m_useLocomotorType = FALSE; - m_LocomotorType = LOCOMOTORSET_INVALID; - // m_needsParkedAircraft = FALSE; -} -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -/*static*/ void LocomotorSetUpgradeModuleData::parseLocomotorType(INI* ini, void* instance, void* store, const void* /*userData*/) -{ - const char* token = ini->getNextToken(); - if (stricmp(token, "None") != 0) { - LocomotorSetUpgradeModuleData* self = (LocomotorSetUpgradeModuleData*)instance; - self->m_useLocomotorType = true; - *(LocomotorSetType*)store = (LocomotorSetType)INI::scanIndexList(token, TheLocomotorSetNames); - } -} -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void LocomotorSetUpgradeModuleData::buildFieldParse(MultiIniFieldParse& p) -{ - - UpgradeModuleData::buildFieldParse(p); - - static const FieldParse dataFieldParse[] = - { - { "EnableUpgrade", INI::parseBool, NULL, offsetof(LocomotorSetUpgradeModuleData, m_setUpgraded) }, - { "ExplicitLocomotorType", LocomotorSetUpgradeModuleData::parseLocomotorType, NULL, offsetof(LocomotorSetUpgradeModuleData, m_LocomotorType)}, - //{ "NeedsParkedAircraft", INI::parseBool, NULL, offsetof(WeaponSetUpgradeModuleData, m_needsParkedAircraft) }, - { 0, 0, 0, 0 } - }; - - p.add(dataFieldParse); - -} // end buildFieldParse - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -LocomotorSetUpgrade::LocomotorSetUpgrade( Thing *thing, const ModuleData* moduleData ) : UpgradeModule( thing, moduleData ) -{ -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -LocomotorSetUpgrade::~LocomotorSetUpgrade( void ) -{ -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void LocomotorSetUpgrade::upgradeImplementation( ) -{ - const LocomotorSetUpgradeModuleData* data = getLocomotorSetUpgradeModuleData(); - AIUpdateInterface* ai = getObject()->getAIUpdateInterface(); - if (ai) { - if (data->m_useLocomotorType && data->m_LocomotorType != LOCOMOTORSET_NORMAL_UPGRADED) { - ai->chooseLocomotorSet(data->m_LocomotorType); - } - else { - ai->setLocomotorUpgrade(data->m_setUpgraded); - } - } - -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void LocomotorSetUpgrade::crc( Xfer *xfer ) -{ - - // extend base class - UpgradeModule::crc( xfer ); - -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void LocomotorSetUpgrade::xfer( Xfer *xfer ) -{ - - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // extend base class - UpgradeModule::xfer( xfer ); - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void LocomotorSetUpgrade::loadPostProcess( void ) -{ - - // extend base class - UpgradeModule::loadPostProcess(); - -} // end loadPostProcess +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: LocomotorSetUpgrade.cpp ///////////////////////////////////////////////////////////////////////////// +// Author: Graham Smallwood, March 2002 +// Desc: UpgradeModule that sets a weapon set bit for the Best Fit weapon set chooser to discover +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#define DEFINE_LOCOMOTORSET_NAMES //Gain access to TheLocomotorSetNames[] + +#include "Common/Xfer.h" +#include "GameLogic/Object.h" +#include "GameLogic/Module/LocomotorSetUpgrade.h" +#include "GameLogic/Module/AIUpdate.h" + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +LocomotorSetUpgradeModuleData::LocomotorSetUpgradeModuleData(void) +{ + m_setUpgraded = TRUE; + m_useLocomotorType = FALSE; + m_LocomotorType = LOCOMOTORSET_INVALID; + // m_needsParkedAircraft = FALSE; +} +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +/*static*/ void LocomotorSetUpgradeModuleData::parseLocomotorType(INI* ini, void* instance, void* store, const void* /*userData*/) +{ + const char* token = ini->getNextToken(); + if (stricmp(token, "None") != 0) { + LocomotorSetUpgradeModuleData* self = (LocomotorSetUpgradeModuleData*)instance; + self->m_useLocomotorType = true; + *(LocomotorSetType*)store = (LocomotorSetType)INI::scanIndexList(token, TheLocomotorSetNames); + } +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void LocomotorSetUpgradeModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + + UpgradeModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + { "EnableUpgrade", INI::parseBool, NULL, offsetof(LocomotorSetUpgradeModuleData, m_setUpgraded) }, + { "ExplicitLocomotorType", LocomotorSetUpgradeModuleData::parseLocomotorType, NULL, offsetof(LocomotorSetUpgradeModuleData, m_LocomotorType)}, + //{ "NeedsParkedAircraft", INI::parseBool, NULL, offsetof(WeaponSetUpgradeModuleData, m_needsParkedAircraft) }, + { 0, 0, 0, 0 } + }; + + p.add(dataFieldParse); + +} // end buildFieldParse + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +LocomotorSetUpgrade::LocomotorSetUpgrade( Thing *thing, const ModuleData* moduleData ) : UpgradeModule( thing, moduleData ) +{ +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +LocomotorSetUpgrade::~LocomotorSetUpgrade( void ) +{ +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void LocomotorSetUpgrade::upgradeImplementation( ) +{ + const LocomotorSetUpgradeModuleData* data = getLocomotorSetUpgradeModuleData(); + AIUpdateInterface* ai = getObject()->getAIUpdateInterface(); + if (ai) { + if (data->m_useLocomotorType && data->m_LocomotorType != LOCOMOTORSET_NORMAL_UPGRADED) { + ai->chooseLocomotorSet(data->m_LocomotorType); + } + else { + ai->setLocomotorUpgrade(data->m_setUpgraded); + } + } + +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void LocomotorSetUpgrade::crc( Xfer *xfer ) +{ + + // extend base class + UpgradeModule::crc( xfer ); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void LocomotorSetUpgrade::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // extend base class + UpgradeModule::xfer( xfer ); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void LocomotorSetUpgrade::loadPostProcess( void ) +{ + + // extend base class + UpgradeModule::loadPostProcess(); + +} // end loadPostProcess diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp index 6012a3361e6..66d9fb090c1 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp @@ -1,197 +1,212 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: Damage.cpp /////////////////////////////////////////////////////////////////////////////// -// Author: Colin Day, September 2002 -// Desc: Basic structures for the damage process -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" -#include "Common/Xfer.h" -#include "GameLogic/Damage.h" -#include "Common/BitFlagsIO.h" -#include "Common/ThingFactory.h" -#include "Common/ThingTemplate.h" - -/////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -const char* DamageTypeFlags::s_bitNameList[] = -{ - "EXPLOSION", - "CRUSH", - "ARMOR_PIERCING", - "SMALL_ARMS", - "GATTLING", - "RADIATION", - "FLAME", - "LASER", - "SNIPER", - "POISON", - "HEALING", - "UNRESISTABLE", - "WATER", - "DEPLOY", - "SURRENDER", - "HACK", - "KILL_PILOT", - "PENALTY", - "FALLING", - "MELEE", - "DISARM", - "HAZARD_CLEANUP", - "PARTICLE_BEAM", - "TOPPLING", - "INFANTRY_MISSILE", - "AURORA_BOMB", - "LAND_MINE", - "JET_MISSILES", - "STEALTHJET_MISSILES", - "MOLOTOV_COCKTAIL", - "COMANCHE_VULCAN", - "SUBDUAL_MISSILE", - "SUBDUAL_VEHICLE", - "SUBDUAL_BUILDING", - "SUBDUAL_UNRESISTABLE", - "MICROWAVE", - "KILL_GARRISONED", - "STATUS", - - NULL -}; - -DamageTypeFlags DAMAGE_TYPE_FLAGS_NONE; // inits to all zeroes -DamageTypeFlags DAMAGE_TYPE_FLAGS_ALL; - -void initDamageTypeFlags() -{ - SET_ALL_DAMAGE_TYPE_BITS( DAMAGE_TYPE_FLAGS_ALL ); -} - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void DamageInfo::xfer( Xfer *xfer ) -{ - - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // xfer input - xfer->xferSnapshot( &in ); - - // xfer output - xfer->xferSnapshot( &out ); - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version - * 2: Damage FX override -*/ -// ------------------------------------------------------------------------------------------------ -void DamageInfoInput::xfer( Xfer *xfer ) -{ - - // version - XferVersion currentVersion = 3; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // source id - xfer->xferObjectID( &m_sourceID ); - - // source player mask - xfer->xferUser( &m_sourcePlayerMask, sizeof( PlayerMaskType ) ); - - // damage type - xfer->xferUser( &m_damageType, sizeof( DamageType ) ); - - // damage FX Override - if( version >= 2 ) - xfer->xferUser( &m_damageFXOverride, sizeof( DamageType ) ); - - // death type - xfer->xferUser( &m_deathType, sizeof( DeathType ) ); - - // amount - xfer->xferReal( &m_amount ); - - // kill no matter what (old versions default to FALSE). - if( currentVersion >= 2 ) - { - xfer->xferBool( &m_kill ); - } - - xfer->xferUser( &m_damageStatusType, sizeof(ObjectStatusTypes) );//It's an enum - - xfer->xferCoord3D(&m_shockWaveVector); - xfer->xferReal( &m_shockWaveAmount ); - xfer->xferReal( &m_shockWaveRadius ); - xfer->xferReal( &m_shockWaveTaperOff ); - - if( version >= 3 ) - { - AsciiString thingString = m_sourceTemplate ? m_sourceTemplate->getName() : AsciiString::TheEmptyString; - xfer->xferAsciiString( &thingString ); - if( xfer->getXferMode() == XFER_LOAD ) - { - m_sourceTemplate = TheThingFactory->findTemplate( thingString ); - } - } - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void DamageInfoOutput::xfer( Xfer *xfer ) -{ - - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // actual damage - xfer->xferReal( &m_actualDamageDealt ); - - // damage clipped - xfer->xferReal( &m_actualDamageClipped ); - - // no effect - xfer->xferBool( &m_noEffect ); - -} // end xfer - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: Damage.cpp /////////////////////////////////////////////////////////////////////////////// +// Author: Colin Day, September 2002 +// Desc: Basic structures for the damage process +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" +#include "Common/Xfer.h" +#include "GameLogic/Damage.h" +#include "Common/BitFlagsIO.h" +#include "Common/ThingFactory.h" +#include "Common/ThingTemplate.h" + +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +const char* DamageTypeFlags::s_bitNameList[] = +{ + "EXPLOSION", + "CRUSH", + "ARMOR_PIERCING", + "SMALL_ARMS", + "GATTLING", + "RADIATION", + "FLAME", + "LASER", + "SNIPER", + "POISON", + "HEALING", + "UNRESISTABLE", + "WATER", + "DEPLOY", + "SURRENDER", + "HACK", + "KILL_PILOT", + "PENALTY", + "FALLING", + "MELEE", + "DISARM", + "HAZARD_CLEANUP", + "PARTICLE_BEAM", + "TOPPLING", + "INFANTRY_MISSILE", + "AURORA_BOMB", + "LAND_MINE", + "JET_MISSILES", + "STEALTHJET_MISSILES", + "MOLOTOV_COCKTAIL", + "COMANCHE_VULCAN", + "SUBDUAL_MISSILE", + "SUBDUAL_VEHICLE", + "SUBDUAL_BUILDING", + "SUBDUAL_UNRESISTABLE", + "MICROWAVE", + "KILL_GARRISONED", + "STATUS", + // Generic additional damage types (no special logic) + "SONIC", + "ACID", + "JET_BOMB", + "ANTI_TANK_GUN", + "ANTI_TANK_MISSILE", + "ANTI_AIR_GUN", + "ANTI_AIR_MISSILE", + "ARTILLERY", + "SEISMIC", + "RAD_BEAM", + "TESLA", + // Specific damage types with special logic attached + "CHRONO_GUN", + //"ZOMBIE_VIRUS", // TODO + //"MIND_CONTROL", // TODO + NULL +}; + +DamageTypeFlags DAMAGE_TYPE_FLAGS_NONE; // inits to all zeroes +DamageTypeFlags DAMAGE_TYPE_FLAGS_ALL; + +void initDamageTypeFlags() +{ + SET_ALL_DAMAGE_TYPE_BITS( DAMAGE_TYPE_FLAGS_ALL ); +} + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void DamageInfo::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // xfer input + xfer->xferSnapshot( &in ); + + // xfer output + xfer->xferSnapshot( &out ); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version + * 2: Damage FX override +*/ +// ------------------------------------------------------------------------------------------------ +void DamageInfoInput::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 3; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // source id + xfer->xferObjectID( &m_sourceID ); + + // source player mask + xfer->xferUser( &m_sourcePlayerMask, sizeof( PlayerMaskType ) ); + + // damage type + xfer->xferUser( &m_damageType, sizeof( DamageType ) ); + + // damage FX Override + if( version >= 2 ) + xfer->xferUser( &m_damageFXOverride, sizeof( DamageType ) ); + + // death type + xfer->xferUser( &m_deathType, sizeof( DeathType ) ); + + // amount + xfer->xferReal( &m_amount ); + + // kill no matter what (old versions default to FALSE). + if( currentVersion >= 2 ) + { + xfer->xferBool( &m_kill ); + } + + xfer->xferUser( &m_damageStatusType, sizeof(ObjectStatusTypes) );//It's an enum + + xfer->xferCoord3D(&m_shockWaveVector); + xfer->xferReal( &m_shockWaveAmount ); + xfer->xferReal( &m_shockWaveRadius ); + xfer->xferReal( &m_shockWaveTaperOff ); + + if( version >= 3 ) + { + AsciiString thingString = m_sourceTemplate ? m_sourceTemplate->getName() : AsciiString::TheEmptyString; + xfer->xferAsciiString( &thingString ); + if( xfer->getXferMode() == XFER_LOAD ) + { + m_sourceTemplate = TheThingFactory->findTemplate( thingString ); + } + } + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void DamageInfoOutput::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // actual damage + xfer->xferReal( &m_actualDamageDealt ); + + // damage clipped + xfer->xferReal( &m_actualDamageClipped ); + + // no effect + xfer->xferBool( &m_noEffect ); + +} // end xfer + From 4a6e779a3bfc47fbede3f9419fa7cccc8b07ceca Mon Sep 17 00:00:00 2001 From: Andi Date: Mon, 28 Jul 2025 18:05:22 +0200 Subject: [PATCH 40/42] ChronoDeath Behavior and various Chrono Gun effects --- GeneralsMD/Code/GameEngine/CMakeLists.txt | 2 + .../GameEngine/Include/Common/GlobalData.h | 8 + .../Include/GameClient/TintStatus.h | 102 +- .../Include/GameLogic/Module/ActiveBody.h | 4 +- .../Include/GameLogic/Module/BodyModule.h | 4 +- .../GameLogic/Module/ChronoDeathBehavior.h | 106 ++ .../Include/GameLogic/Module/ImmortalBody.h | 2 +- .../Include/GameLogic/Module/InactiveBody.h | 2 +- .../GameEngine/Source/Common/GlobalData.cpp | 16 + .../Source/Common/RTS/ActionManager.cpp | 69 +- .../Source/Common/System/MemoryInit.cpp | 1 + .../Source/Common/Thing/ModuleFactory.cpp | 2 + .../GameEngine/Source/GameClient/Drawable.cpp | 2 + .../GUI/ControlBar/ControlBarCommand.cpp | 4 + .../Object/Behavior/ChronoDeathBehavior.cpp | 240 ++++ .../GameLogic/Object/Body/ActiveBody.cpp | 129 +- .../GameLogic/Object/Body/ImmortalBody.cpp | 4 +- .../GameLogic/Object/Body/InactiveBody.cpp | 2 +- .../Source/GameLogic/Object/Object.cpp | 49 +- .../Update/AIUpdate/TeleporterAIUpdate.cpp | 1246 +++++++++-------- .../Source/GameLogic/Object/Weapon.cpp | 9 +- 21 files changed, 1282 insertions(+), 721 deletions(-) create mode 100644 GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ChronoDeathBehavior.h create mode 100644 GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/ChronoDeathBehavior.cpp diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index 5ad610481ea..6fbd280bfb3 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -348,6 +348,7 @@ set(GAMEENGINE_SRC Include/GameLogic/Module/ImmortalBody.h Include/GameLogic/Module/InactiveBody.h Include/GameLogic/Module/InstantDeathBehavior.h + Include/GameLogic/Module/ChronoDeathBehavior.h Include/GameLogic/Module/InternetHackContain.h Include/GameLogic/Module/JetAIUpdate.h Include/GameLogic/Module/JetSlowDeathBehavior.h @@ -867,6 +868,7 @@ set(GAMEENGINE_SRC Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp Source/GameLogic/Object/Behavior/GrantStealthBehavior.cpp Source/GameLogic/Object/Behavior/InstantDeathBehavior.cpp + Source/GameLogic/Object/Behavior/ChronoDeathBehavior.cpp Source/GameLogic/Object/Behavior/JetSlowDeathBehavior.cpp Source/GameLogic/Object/Behavior/MinefieldBehavior.cpp Source/GameLogic/Object/Behavior/NeutonBlastBehavior.cpp diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h index d420af04731..4f0fda90d31 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h @@ -532,6 +532,14 @@ class GlobalData : public SubsystemInterface UnsignedInt m_chronoDamageHealRate; Real m_chronoDamageHealAmount; + Real m_chronoDisableAlphaStart; + Real m_chronoDisableAlphaEnd; + + // TintStatus m_chronoTintStatusType; + AsciiString m_chronoDisableParticleSystemLarge; + AsciiString m_chronoDisableParticleSystemMedium; + AsciiString m_chronoDisableParticleSystemSmall; + DeathTypeFlags m_defaultExcludedDeathTypes; diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/TintStatus.h b/GeneralsMD/Code/GameEngine/Include/GameClient/TintStatus.h index e4bb7bf1f8e..60a100e5733 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/TintStatus.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/TintStatus.h @@ -1,51 +1,53 @@ - -#pragma once -#ifndef __TINTSTATUS_H__ -#define __TINTSTATUS_H__ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "Lib/BaseType.h" -#include "Common/BitFlags.h" -#include "Common/BitFlagsIO.h" - -// Tint status types can now be used via ini; -// Sync with TintStatusFlags::s_bitNameList[] in Drawable.cpp -enum TintStatus CPP_11(: Int) -{ - TINT_STATUS_INVALID = 0, - - TINT_STATUS_DISABLED = 1,///< drawable tint color is deathly dark grey - TINT_STATUS_IRRADIATED, ///< drawable tint color is sickly green - TINT_STATUS_POISONED, ///< drawable tint color is open-sore red - TINT_STATUS_GAINING_SUBDUAL_DAMAGE, ///< When gaining subdual damage, we tint SUBDUAL_DAMAGE_COLOR - TINT_STATUS_FRENZY, ///< When frenzied, we tint FRENZY_COLOR - // New generic entries: - TINT_STATUS_SHIELDED, ///< When shielded, we tint SHIELDED_COLOR - TINT_STATUS_DEMORALIZED, - TINT_STATUS_BOOST, - TINT_STATUS_TELEPORT_RECOVER, ///< (Chrono Legionnaire -> recover from teleport) - TINT_STATUS_EXTRA1, - TINT_STATUS_EXTRA2, - TINT_STATUS_EXTRA3, - TINT_STATUS_EXTRA4, - TINT_STATUS_EXTRA5, - TINT_STATUS_EXTRA6, - TINT_STATUS_EXTRA7, - TINT_STATUS_EXTRA8, - - TINT_STATUS_COUNT // Keep this last -}; - -//------------------- -struct DrawableColorTint -{ - RGBColor color; - RGBColor colorInfantry; - UnsignedInt attackFrames; - UnsignedInt decayFrames; -}; - -typedef BitFlags TintStatusFlags; - -// -------- + +#pragma once +#ifndef __TINTSTATUS_H__ +#define __TINTSTATUS_H__ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Lib/BaseType.h" +#include "Common/BitFlags.h" +#include "Common/BitFlagsIO.h" + +// Tint status types can now be used via ini; +// Sync with TintStatusFlags::s_bitNameList[] in Drawable.cpp +enum TintStatus CPP_11(: Int) +{ + TINT_STATUS_INVALID = 0, + + TINT_STATUS_DISABLED = 1,///< drawable tint color is deathly dark grey + TINT_STATUS_IRRADIATED, ///< drawable tint color is sickly green + TINT_STATUS_POISONED, ///< drawable tint color is open-sore red + TINT_STATUS_GAINING_SUBDUAL_DAMAGE, ///< When gaining subdual damage, we tint SUBDUAL_DAMAGE_COLOR + TINT_STATUS_FRENZY, ///< When frenzied, we tint FRENZY_COLOR + // New generic entries: + TINT_STATUS_SHIELDED, ///< When shielded, we tint SHIELDED_COLOR + TINT_STATUS_DEMORALIZED, + TINT_STATUS_BOOST, + TINT_STATUS_TELEPORT_RECOVER, ///< (Chrono Legionnaire -> recover from teleport) + TINT_STATUS_DISABLED_CHRONO, ///< Unit disabled by chrono gun + TINT_STATUS_GAINING_CHRONO_DAMAGE, ///< Unit getting damaged from chrono gun + TINT_STATUS_EXTRA1, + TINT_STATUS_EXTRA2, + TINT_STATUS_EXTRA3, + TINT_STATUS_EXTRA4, + TINT_STATUS_EXTRA5, + TINT_STATUS_EXTRA6, + TINT_STATUS_EXTRA7, + TINT_STATUS_EXTRA8, + + TINT_STATUS_COUNT // Keep this last +}; + +//------------------- +struct DrawableColorTint +{ + RGBColor color; + RGBColor colorInfantry; + UnsignedInt attackFrames; + UnsignedInt decayFrames; +}; + +typedef BitFlags TintStatusFlags; + +// -------- #endif /* __TINTSTATUS_H__ */ \ No newline at end of file diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h index 25deafe1168..b7d8575c5a4 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h @@ -123,7 +123,7 @@ class ActiveBody : public BodyModule virtual void setIndestructible( Bool indestructible ); virtual Bool isIndestructible( void ) const { return m_indestructible; } - virtual void internalChangeHealth( Real delta ); ///< change health + virtual void internalChangeHealth( Real delta, Bool changeModelCondition = TRUE); ///< change health virtual void evaluateVisualCondition(); virtual void updateBodyParticleSystems( void );// made public for topple anf building collapse updates -ML @@ -157,6 +157,8 @@ class ActiveBody : public BodyModule virtual void internalAddSubdualDamage( Real delta ); ///< change health virtual void internalAddChronoDamage( Real delta ); ///< change health + virtual void applyChronoParticleSystems(void); + private: Real m_currentHealth; ///< health of the object diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BodyModule.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BodyModule.h index d841fb8e6c0..b4c568d61c8 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BodyModule.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BodyModule.h @@ -194,7 +194,7 @@ class BodyModuleInterface call this directly (especially when when decreasing health, since you probably want "attemptDamage" or "attemptHealing") */ - virtual void internalChangeHealth( Real delta ) = 0; + virtual void internalChangeHealth(Real delta, Bool changeModelCondition = TRUE ) = 0; virtual void setIndestructible( Bool indestructible ) = 0; virtual Bool isIndestructible( void ) const = 0; @@ -299,7 +299,7 @@ class BodyModule : public BehaviorModule, public BodyModuleInterface call this directly (especially when when decreasing health, since you probably want "attemptDamage" or "attemptHealing") */ - virtual void internalChangeHealth( Real delta ) = 0; + virtual void internalChangeHealth( Real delta, Bool changeModelCondition = TRUE) = 0; virtual void evaluateVisualCondition() { } virtual void updateBodyParticleSystems() { };// made public for topple anf building collapse updates -ML diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ChronoDeathBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ChronoDeathBehavior.h new file mode 100644 index 00000000000..45a0af952c9 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ChronoDeathBehavior.h @@ -0,0 +1,106 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: ChronoDeathBehavior.h ///////////////////////////////////////////////////////////////////////// +// Author: Steven Johnson, Sep 2002 +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __ChronoDeathBehavior_H_ +#define __ChronoDeathBehavior_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "GameLogic/Module/DieModule.h" + +class FXList; +class ObjectCreationList; +class WeaponTemplate; +class DamageInfo; + +//------------------------------------------------------------------------------------------------- +class ChronoDeathBehaviorModuleData : public UpdateModuleData +{ +public: + DieMuxData m_dieMuxData; + + const ObjectCreationList* m_ocl; + const FXList* m_startFX; + const FXList* m_endFX; + + Real m_startScale; + Real m_endScale; + Real m_startAlpha; + Real m_endAlpha; + + UnsignedInt m_destructionDelay; + + ChronoDeathBehaviorModuleData(); + static void buildFieldParse(MultiIniFieldParse& p); + +private: + +}; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class ChronoDeathBehavior : public UpdateModule, public DieModuleInterface +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( ChronoDeathBehavior, "ChronoDeathBehavior" ) + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( ChronoDeathBehavior, ChronoDeathBehaviorModuleData ) + +public: + + ChronoDeathBehavior( Thing *thing, const ModuleData* moduleData ); + // virtual destructor prototype provided by memory pool declaration + + static Int getInterfaceMask() { return UpdateModule::getInterfaceMask() | (MODULEINTERFACE_DIE); } + + // BehaviorModule + virtual DieModuleInterface* getDie() { return this; } + + // UpdateModuleInterface + virtual UpdateSleepTime update(); + virtual DisabledMaskType getDisabledTypesToProcess() const { return DISABLEDMASK_ALL; } + + // DieModuleInterface + virtual void onDie( const DamageInfo *damageInfo ); + virtual Bool isDieApplicable(const DamageInfo* damageInfo) const { return getChronoDeathBehaviorModuleData()->m_dieMuxData.isDieApplicable(getObject(), damageInfo); } + + +protected: + + virtual void beginChronoDeath(const DamageInfo* damageInfo); + +private: + Bool m_deathTriggered; + + UnsignedInt m_destructionFrame; + UnsignedInt m_dieFrame; + +}; + +#endif // __ChronoDeathBehavior_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ImmortalBody.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ImmortalBody.h index a7a290a3813..bcc5b555f4b 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ImmortalBody.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ImmortalBody.h @@ -52,7 +52,7 @@ class ImmortalBody : public ActiveBody ImmortalBody( Thing *thing, const ModuleData* moduleData ); // virtual destructor prototype provided by memory pool declaration - virtual void internalChangeHealth( Real delta ); ///< change health + virtual void internalChangeHealth( Real delta, Bool changeModelCondition = TRUE); ///< change health protected: diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/InactiveBody.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/InactiveBody.h index 9f567262c1b..9807e2b78a2 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/InactiveBody.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/InactiveBody.h @@ -64,7 +64,7 @@ class InactiveBody : public BodyModule virtual void clearArmorSetFlag(ArmorSetType ast) { /* nothing */ } virtual Bool testArmorSetFlag(ArmorSetType ast){ return FALSE; } - virtual void internalChangeHealth( Real delta ); + virtual void internalChangeHealth( Real delta, Bool changeModelCondition = TRUE); private: Bool m_dieCalled; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp index 0973dd35fdc..e9c1bf3eadd 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp @@ -547,6 +547,14 @@ GlobalData* GlobalData::m_theOriginal = NULL; {"ChronoDamageDisableThreshold", INI::parsePercentToReal, NULL, offsetof(GlobalData, m_chronoDamageDisableThreshold)}, {"ChronoDamageHealRate", INI::parseDurationUnsignedInt, NULL, offsetof(GlobalData, m_chronoDamageHealRate)}, {"ChronoDamageHealAmountPercent", INI::parsePercentToReal, NULL, offsetof(GlobalData, m_chronoDamageHealAmount) }, + {"ChronoDamageOpacityStart", INI::parsePercentToReal, NULL, offsetof(GlobalData, m_chronoDisableAlphaStart) }, + {"ChronoDamageOpacityEnd", INI::parsePercentToReal, NULL, offsetof(GlobalData, m_chronoDisableAlphaEnd) }, + + // {"ChronoDamageTintStatusType", TintStatusFlags::parseSingleBitFromINI, NULL, offsetof(GlobalData, m_chronoTintStatusType) }, + {"ChronoDamageParticleSystemLarge", INI::parseAsciiString, NULL, offsetof(GlobalData, m_chronoDisableParticleSystemLarge) }, + {"ChronoDamageParticleSystemMedium", INI::parseAsciiString, NULL, offsetof(GlobalData, m_chronoDisableParticleSystemMedium) }, + {"ChronoDamageParticleSystemSmall", INI::parseAsciiString, NULL, offsetof(GlobalData, m_chronoDisableParticleSystemSmall) }, + {"DefaultExcludedDeathTypes", INI::parseDeathTypeFlagsList, NULL, offsetof(GlobalData, m_defaultExcludedDeathTypes) }, { NULL, NULL, NULL, 0 } // keep this last @@ -1160,8 +1168,16 @@ GlobalData::GlobalData() m_chronoDamageHealRate = 15; m_chronoDamageHealAmount = 0.1; + m_chronoDisableAlphaStart = 1.0; + m_chronoDisableAlphaEnd = 1.0; + m_defaultExcludedDeathTypes = DEATH_TYPE_FLAGS_NONE; + m_chronoDisableParticleSystemLarge.clear(); + m_chronoDisableParticleSystemMedium.clear(); + m_chronoDisableParticleSystemSmall.clear(); + // m_chronoTintStatusType = TINT_STATUS_INVALID; + } // end GlobalData diff --git a/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp index db8f2d3189d..f81649a95a2 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp @@ -150,6 +150,9 @@ Bool ActionManager::canGetRepairedAt( const Object *obj, const Object *repairDes if( obj == NULL || repairDest == NULL ) return FALSE; + if (repairDest->isDisabledByType( DISABLED_CHRONO )) + return FALSE; + Relationship r = obj->getRelationship(repairDest); // only available by our allies @@ -220,6 +223,9 @@ Bool ActionManager::canTransferSuppliesAt( const Object *obj, const Object *tran return FALSE; } + if (transferDest->isDisabledByType( DISABLED_CHRONO )) + return FALSE; + // nothing can be done with things that are under construction if( obj->getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) || transferDest->getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) @@ -290,6 +296,9 @@ Bool ActionManager::canTransferSuppliesAt( const Object *obj, const Object *tran Bool ActionManager::canDockAt( const Object *obj, const Object *dockDest, CommandSourceType commandSource ) { + if (dockDest->isDisabledByType( DISABLED_CHRONO )) + return FALSE; + // look for a dock interface DockUpdateInterface *di = NULL; for (BehaviorModule **u = dockDest->getBehaviorModules(); *u; ++u) @@ -335,6 +344,9 @@ Bool ActionManager::canGetHealedAt( const Object *obj, const Object *healDest, C if( obj == NULL || healDest == NULL ) return FALSE; + if (healDest->isDisabledByType( DISABLED_CHRONO )) + return FALSE; + Relationship r = obj->getRelationship(healDest); // only available by our allies @@ -387,6 +399,9 @@ Bool ActionManager::canRepairObject( const Object *obj, const Object *objectToRe if( obj == NULL || objectToRepair == NULL ) return FALSE; + if (objectToRepair->isDisabledByType( DISABLED_CHRONO )) + return FALSE; + Relationship r = obj->getRelationship(objectToRepair); // you can only repair allies, we ignore this restriction for bridges @@ -459,6 +474,9 @@ Bool ActionManager::canResumeConstructionOf( const Object *obj, if( obj == NULL || objectBeingConstructed == NULL ) return FALSE; + if (objectBeingConstructed->isDisabledByType( DISABLED_CHRONO )) + return FALSE; + // only dozers or workers can resume construction of things if( obj->isKindOf( KINDOF_DOZER ) == FALSE ) return FALSE; @@ -529,6 +547,9 @@ Bool ActionManager::canEnterObject( const Object *obj, const Object *objectToEnt // sanity if( obj == NULL || objectToEnter == NULL ) return FALSE; + + if (objectToEnter->isDisabledByType( DISABLED_CHRONO )) + return FALSE; if( obj == objectToEnter ) { @@ -722,6 +743,11 @@ Bool ActionManager::canEnterObject( const Object *obj, const Object *objectToEnt // ------------------------------------------------------------------------------------------------ CanAttackResult ActionManager::getCanAttackObject( const Object *obj, const Object *objectToAttack, CommandSourceType commandSource, AbleToAttackType attackType ) { + + // We still need to attack with chrono damage + // if (objectToEnter->isDisabledByType( DISABLED_CHRONO ) + // return FALSE; + // sanity if( !obj || !objectToAttack || obj->isEffectivelyDead() || objectToAttack->isEffectivelyDead() || objectToAttack == obj ) { @@ -829,6 +855,10 @@ Bool ActionManager::canConvertObjectToCarBomb( const Object *obj, const Object * return FALSE; } + if (objectToConvert->isDisabledByType( DISABLED_CHRONO )) + return FALSE; + + // if the target is in the shroud, we can't do anything if (isObjectShroudedForAction(obj, objectToConvert, commandSource)) return FALSE; @@ -852,6 +882,7 @@ Bool ActionManager::canConvertObjectToCarBomb( const Object *obj, const Object * // ------------------------------------------------------------------------------------------------ Bool ActionManager::canHijackVehicle( const Object *obj, const Object *objectToHijack, CommandSourceType commandSource ) //LORENZEN { + // sanity if( obj == NULL || objectToHijack == NULL ) { @@ -864,6 +895,9 @@ Bool ActionManager::canHijackVehicle( const Object *obj, const Object *objectToH return FALSE; } + if (objectToHijack->isDisabledByType( DISABLED_CHRONO )) + return FALSE; + // if the target is in the shroud, we can't do anything if (isObjectShroudedForAction(obj, objectToHijack, commandSource)) { @@ -926,6 +960,10 @@ Bool ActionManager::canSabotageBuilding( const Object *obj, const Object *object return FALSE; } + if (objectToSabotage->isDisabledByType( DISABLED_CHRONO )) + return FALSE; + + // if the target is in the shroud, we can't do anything if (isObjectShroudedForAction(obj, objectToSabotage, commandSource)) { @@ -979,6 +1017,9 @@ Bool ActionManager::canMakeObjectDefector( const Object *obj, const Object *obje return FALSE; } + if (objectToMakeDefector->isDisabledByType( DISABLED_CHRONO )) + return FALSE; + // if the target is in the shroud, we can't do anything if (isObjectShroudedForAction(obj, objectToMakeDefector, commandSource)) { @@ -999,6 +1040,9 @@ Bool ActionManager::canCaptureBuilding( const Object *obj, const Object *objectT if( obj == NULL || objectToCapture == NULL ) return FALSE; + if (objectToCapture->isDisabledByType( DISABLED_CHRONO )) + return FALSE; + //Make sure our object has the capability of performing this special ability. Bool isOwnerBlackLotus = obj->hasSpecialPower( SPECIAL_BLACKLOTUS_CAPTURE_BUILDING ); @@ -1103,6 +1147,9 @@ Bool ActionManager::canDisableVehicleViaHacking( const Object *obj, const Object if( obj == NULL || objectToHack == NULL ) return FALSE; + if (objectToHack->isDisabledByType( DISABLED_CHRONO )) + return FALSE; + if (checkSourceRequirements) { //Make sure our object has the capability of performing this special ability. @@ -1227,6 +1274,9 @@ Bool ActionManager::canStealCashViaHacking( const Object *obj, const Object *obj if( obj == NULL || objectToHack == NULL ) return FALSE; + if (objectToHack->isDisabledByType( DISABLED_CHRONO )) + return FALSE; + //Make sure our object has the capability of performing this special ability. if( !obj->hasSpecialPower( SPECIAL_BLACKLOTUS_STEAL_CASH_HACK ) ) { @@ -1313,6 +1363,9 @@ Bool ActionManager::canDisableBuildingViaHacking( const Object *obj, const Objec if( obj == NULL || objectToHack == NULL ) return FALSE; + if (objectToHack->isDisabledByType( DISABLED_CHRONO )) + return FALSE; + //Make sure our object has the capability of performing this special ability. if( !obj->hasSpecialPower( SPECIAL_HACKER_DISABLE_BUILDING ) ) { @@ -1404,6 +1457,9 @@ Bool ActionManager::canSnipeVehicle( const Object *obj, const Object *objectToSn return FALSE; } + if (objectToSnipe->isDisabledByType( DISABLED_CHRONO )) + return FALSE; + // if the target is in the shroud, we can't do anything if (isObjectShroudedForAction(obj, objectToSnipe, commandSource)) return FALSE; @@ -1477,7 +1533,7 @@ Bool ActionManager::canDoSpecialPowerAtLocation( const Object *obj, const Coord3 if (behaviorType >= SPECIAL_ION_CANNON) { //first custom SP behaviorType = spTemplate->getSpecialPowerBehaviorType(); if (behaviorType == SPECIAL_INVALID) { - behaviorType == SPECIAL_NEUTRON_MISSILE; // Default to behave like neutron missile, common behavior + behaviorType = SPECIAL_NEUTRON_MISSILE; // Default to behave like neutron missile, common behavior } } @@ -1605,6 +1661,9 @@ Bool ActionManager::canDoSpecialPowerAtObject( const Object *obj, const Object * return FALSE; } + if (target->isDisabledByType( DISABLED_CHRONO )) + return FALSE; + Relationship r = obj->getRelationship(target); SpecialPowerModuleInterface *mod = obj->getSpecialPowerModule( spTemplate ); @@ -1889,7 +1948,7 @@ Bool ActionManager::canDoSpecialPower( const Object *obj, const SpecialPowerTemp if (behaviorType >= SPECIAL_ION_CANNON) { //first custom SP behaviorType = spTemplate->getSpecialPowerBehaviorType(); if (behaviorType == SPECIAL_INVALID) { - behaviorType == SPECIAL_NEUTRON_MISSILE; // Default to behave like neutron missile, common behavior + behaviorType = SPECIAL_NEUTRON_MISSILE; // Default to behave like neutron missile, common behavior } } @@ -2043,6 +2102,9 @@ Bool ActionManager::canGarrison( const Object *obj, const Object *target, Comman if (!(obj && target)) return false; + if (target->isDisabledByType( DISABLED_CHRONO )) + return FALSE; + // The object was not an infantry, or is disallowed from being allowed to garrison stuff. if (obj->isKindOf(KINDOF_INFANTRY) == false || obj->isKindOf(KINDOF_NO_GARRISON)) return false; @@ -2080,6 +2142,9 @@ Bool ActionManager::canPlayerGarrison( const Player *player, const Object *targe { if (!(player && target)) return false; + + if (target->isDisabledByType( DISABLED_CHRONO )) + return FALSE; if (target->isEffectivelyDead()) { return false; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp index 17136466723..12e3f14670f 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp @@ -240,6 +240,7 @@ static PoolSizeRec sizes[] = { "ImmortalBody", 128, 256 }, { "InactiveBody", 2048, 32 }, { "InstantDeathBehavior", 512, 32 }, + { "ChronoDeathBehavior", 512, 32 }, { "LaserUpdate", 32, 32 }, { "PointDefenseLaserUpdate", 32, 32 }, { "CleanupHazardUpdate", 32, 32 }, diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index e7dfe842d35..5b8ebc274a5 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -53,6 +53,7 @@ #include "GameLogic/Module/DumbProjectileBehavior.h" #include "GameLogic/Module/FreeFallProjectileBehavior.h" #include "GameLogic/Module/InstantDeathBehavior.h" +#include "GameLogic/Module/ChronoDeathBehavior.h" #include "GameLogic/Module/SlowDeathBehavior.h" #include "GameLogic/Module/HelicopterSlowDeathUpdate.h" #include "GameLogic/Module/NeutronMissileSlowDeathUpdate.h" @@ -345,6 +346,7 @@ void ModuleFactory::init( void ) addModule( FreeFallProjectileBehavior ); addModule( PhysicsBehavior ); addModule( InstantDeathBehavior ); + addModule( ChronoDeathBehavior ); addModule( SlowDeathBehavior ); addModule( HelicopterSlowDeathBehavior ); addModule( NeutronMissileSlowDeathBehavior ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 14b1426a580..330e306ae43 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -129,6 +129,8 @@ const char* TintStatusFlags::s_bitNameList[] = "DEMORALIZED", "BOOST", "TELEPORT_RECOVER", + "DISABLED_CHRONO", + "GAINING_CHRONO_DAMAGE", "EXTRA1", "EXTRA2", "EXTRA3", diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommand.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommand.cpp index b5759c34bc0..cfa851bb333 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommand.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommand.cpp @@ -1074,6 +1074,10 @@ CommandAvailability ControlBar::getCommandAvailability( const CommandButton *com } } } + + //Disabled Chrono does not allow *any* commands + if ( obj->isDisabledByType( DISABLED_CHRONO ) ) + return COMMAND_RESTRICTED; //Other disabled objects are unable to use buttons -- so gray them out. Bool disabled = obj->isDisabled(); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/ChronoDeathBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/ChronoDeathBehavior.cpp new file mode 100644 index 00000000000..32c5f930b3c --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/ChronoDeathBehavior.cpp @@ -0,0 +1,240 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: ChronoDeathBehavior.cpp /////////////////////////////////////////////////////////////////////// +// Author: +// Desc: +/////////////////////////////////////////////////////////////////////////////////////////////////// + + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine +#define DEFINE_SLOWDEATHPHASE_NAMES + +#include "Common/Thing.h" +#include "Common/ThingTemplate.h" +#include "Common/INI.h" +#include "Common/Xfer.h" +#include "GameClient/Drawable.h" +#include "GameClient/FXList.h" +#include "GameClient/InGameUI.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/Module/BodyModule.h" +#include "GameLogic/Module/ChronoDeathBehavior.h" +#include "GameLogic/Module/AIUpdate.h" +#include "GameLogic/Object.h" +#include "GameLogic/ObjectCreationList.h" +#include "GameClient/Drawable.h" + +//------------------------------------------------------------------------------------------------- +ChronoDeathBehaviorModuleData::ChronoDeathBehaviorModuleData() +{ + m_ocl = NULL; + m_startFX = NULL; + m_endFX = NULL; + m_startScale = 1.0; + m_endScale = 1.0; + m_startAlpha = 1.0; + m_endAlpha = 1.0; + m_destructionDelay = 1; +} + +//------------------------------------------------------------------------------------------------- +/*static*/ void ChronoDeathBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + UpdateModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + { "StartFX", INI::parseFXList, NULL, offsetof(ChronoDeathBehaviorModuleData, m_startFX) }, + { "EndFX", INI::parseFXList, NULL, offsetof(ChronoDeathBehaviorModuleData, m_endFX) }, + { "OCL", INI::parseObjectCreationList, NULL, offsetof(ChronoDeathBehaviorModuleData, m_ocl) }, + { "StartScale", INI::parseReal, NULL, offsetof(ChronoDeathBehaviorModuleData, m_startScale) }, + { "EndScale", INI::parseReal, NULL, offsetof(ChronoDeathBehaviorModuleData, m_endScale) }, + { "StartOpacity", INI::parseReal, NULL, offsetof(ChronoDeathBehaviorModuleData, m_startAlpha) }, + { "EndOpacity", INI::parseReal, NULL, offsetof(ChronoDeathBehaviorModuleData, m_endAlpha) }, + { "DestructionDelay", INI::parseDurationUnsignedInt, NULL, offsetof(ChronoDeathBehaviorModuleData, m_destructionDelay) }, + { 0, 0, 0, 0 } + }; + p.add(dataFieldParse); + p.add(DieMuxData::getFieldParse(), offsetof(ChronoDeathBehaviorModuleData, m_dieMuxData)); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ChronoDeathBehavior::ChronoDeathBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule(thing, moduleData) +{ + m_destructionFrame = 0; + m_dieFrame = 0; + m_deathTriggered = FALSE; + setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ChronoDeathBehavior::~ChronoDeathBehavior( void ) +{ +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +UpdateSleepTime ChronoDeathBehavior::update() +{ + DEBUG_ASSERTCRASH(m_deathTriggered, ("hmm, this should not be possible")); + + const ChronoDeathBehaviorModuleData* d = getChronoDeathBehaviorModuleData(); + Object* obj = getObject(); + UnsignedInt now = TheGameLogic->getFrame(); + + // Calculate current Alpha and Scale + Real progress = INT_TO_REAL(now - m_dieFrame) / INT_TO_REAL(m_destructionFrame - m_dieFrame); + + Real scale = (1 - progress) * d->m_startScale + progress * d->m_endScale; + Real opacity = (1 - progress) * d->m_startAlpha + progress * d->m_endAlpha; + + Drawable* draw = obj->getDrawable(); + + // Make sure to include template scale + draw->setInstanceScale(obj->getTemplate()->getAssetScale() * scale); + draw->setDrawableOpacity(opacity); + //draw->setEffectiveOpacity(opacity); + //draw->setSecondMaterialPassOpacity(opacity); + + if (now >= m_destructionFrame) + { + if (d->m_endFX) + { + FXList::doFXObj(d->m_startFX, obj); + } + TheGameLogic->destroyObject(obj); + return UPDATE_SLEEP_FOREVER; + } + + return UPDATE_SLEEP_NONE; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ChronoDeathBehavior::beginChronoDeath(const DamageInfo* damageInfo) +{ + if (m_deathTriggered) + return; + + const ChronoDeathBehaviorModuleData* d = getChronoDeathBehaviorModuleData(); + Object* obj = getObject(); + + // deselect this unit for all players. + TheGameLogic->deselectObject(obj, PLAYERMASK_ALL, TRUE); + + Drawable* draw = obj->getDrawable(); + if (draw) { + draw->setShadowsEnabled(false); + draw->setTerrainDecalFadeTarget(0.0f, -0.2f); + } + + if (d->m_startFX) + { + FXList::doFXObj(d->m_startFX, obj); + } + + if (d->m_ocl) + { + // TODO: Create Dynamic Scale module and pass geometry size of parent object; + /* Object* newObject = */ ObjectCreationList::create(d->m_ocl, obj, NULL); + } + + UnsignedInt now = TheGameLogic->getFrame(); + m_dieFrame = now; + m_destructionFrame = now + d->m_destructionDelay; + + m_deathTriggered = TRUE; + + setWakeFrame(obj, UPDATE_SLEEP_NONE); +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ChronoDeathBehavior::onDie( const DamageInfo *damageInfo ) +{ + if (!isDieApplicable(damageInfo)) + return; + + AIUpdateInterface* ai = getObject()->getAIUpdateInterface(); + if (ai) + { + // has another AI already handled us. + if (ai->isAiInDeadState()) + return; + ai->markAsDead(); + } + beginChronoDeath(damageInfo); +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void ChronoDeathBehavior::crc( Xfer *xfer ) +{ + + // extend base class + UpdateModule::crc( xfer ); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void ChronoDeathBehavior::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // extend base class + UpdateModule::xfer( xfer ); + + // is triggered + xfer->xferBool(&m_deathTriggered); + + // destruction frame + xfer->xferUnsignedInt(&m_destructionFrame); + + // die frame + xfer->xferUnsignedInt(&m_dieFrame); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void ChronoDeathBehavior::loadPostProcess( void ) +{ + + // extend base class + UpdateModule::loadPostProcess(); + +} // end loadPostProcess diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp index 47631498a54..fbab52ccbea 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp @@ -373,8 +373,15 @@ void ActiveBody::attemptDamage( DamageInfo *damageInfo ) Bool alreadyHandled = FALSE; Bool allowModifier = TRUE; + Bool doDamageModules = TRUE; + Bool adjustConditions = TRUE; Real amount = m_curArmor.adjustDamage(damageInfo->in.m_damageType, damageInfo->in.m_amount); + // Units that get disabled by Chrono damage cannot take damage: + if (obj->isDisabledByType(DISABLED_CHRONO) && + !(damageInfo->in.m_damageType == DAMAGE_CHRONO_GUN || damageInfo->in.m_damageType == DAMAGE_CHRONO_UNRESISTABLE)) + return; + switch( damageInfo->in.m_damageType ) { case DAMAGE_HEALING: @@ -506,7 +513,7 @@ void ActiveBody::attemptDamage( DamageInfo *damageInfo ) // Increase damage counter internalAddChronoDamage(amount); - DEBUG_LOG(("ActiveBody::attemptDamage - amount = %f, chronoDmg = %f\n", amount, getCurrentChronoDamageAmount())); + // DEBUG_LOG(("ActiveBody::attemptDamage - amount = %f, chronoDmg = %f\n", amount, getCurrentChronoDamageAmount())); // Check for disabling threshold Bool nowSubdued = isSubduedChrono(); @@ -523,6 +530,8 @@ void ActiveBody::attemptDamage( DamageInfo *damageInfo ) // Check kill state: if (getCurrentChronoDamageAmount() > getMaxHealth()) { damageInfo->in.m_kill = TRUE; + doDamageModules = FALSE; + adjustConditions = FALSE; } else { alreadyHandled = TRUE; @@ -574,7 +583,7 @@ void ActiveBody::attemptDamage( DamageInfo *damageInfo ) if (!alreadyHandled) { // do the damage simplistic damage subtraction - internalChangeHealth( -amount ); + internalChangeHealth( -amount, adjustConditions); } #ifdef ALLOW_SURRENDER @@ -646,7 +655,7 @@ void ActiveBody::attemptDamage( DamageInfo *damageInfo ) } // if our health has gone down then do run the damage module callback - if( m_currentHealth < m_prevHealth ) + if( m_currentHealth < m_prevHealth && doDamageModules) { for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m) { @@ -658,7 +667,7 @@ void ActiveBody::attemptDamage( DamageInfo *damageInfo ) } } - if (m_curDamageState != oldState) + if (m_curDamageState != oldState && adjustConditions) { for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m) { @@ -1249,7 +1258,7 @@ void ActiveBody::updateBodyParticleSystems( void ) * Game stuff goes in attemptDamage and attemptHealing. */ //------------------------------------------------------------------------------------------------- -void ActiveBody::internalChangeHealth( Real delta ) +void ActiveBody::internalChangeHealth( Real delta, Bool changeModelCondition) { // save the current health as the previous health m_prevHealth = m_currentHealth; @@ -1267,23 +1276,25 @@ void ActiveBody::internalChangeHealth( Real delta ) if( m_currentHealth < lowEndCap ) m_currentHealth = lowEndCap; - // recalc the damage state - BodyDamageType oldState = m_curDamageState; - setCorrectDamageState(); + if (changeModelCondition) { + // recalc the damage state + BodyDamageType oldState = m_curDamageState; + setCorrectDamageState(); - // if our state has changed - if( m_curDamageState != oldState ) - { + // if our state has changed + if (m_curDamageState != oldState) + { - // - // show a visual change in the model for the damage state, we do not show visual changes - // for damage states when things are under construction because we just don't have - // all the art states for that during buildup animation - // - if( !getObject()->getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) - evaluateVisualCondition(); + // + // show a visual change in the model for the damage state, we do not show visual changes + // for damage states when things are under construction because we just don't have + // all the art states for that during buildup animation + // + if (!getObject()->getStatusBits().test(OBJECT_STATUS_UNDER_CONSTRUCTION)) + evaluateVisualCondition(); - } // end if + } // end if + } // mark the bit according to our health. (if our AI is dead but our health improves, it will // still re-flag this bit in the AIDeadState every frame.) @@ -1365,22 +1376,26 @@ void ActiveBody::onSubdualChange( Bool isNowSubdued ) //------------------------------------------------------------------------------------------------- void ActiveBody::onSubdualChronoChange( Bool isNowSubdued ) { - // TODO: Apply/Remove visual effects - Object *me = getObject(); if( isNowSubdued ) { me->setDisabled(DISABLED_CHRONO); - ContainModuleInterface *contain = me->getContain(); - if ( contain ) - contain->orderAllPassengersToIdle( CMD_FROM_AI ); - } + // Apply Chrono Particles + applyChronoParticleSystems(); + + ContainModuleInterface *contain = me->getContain(); + if ( contain ) + contain->orderAllPassengersToIdle( CMD_FROM_AI ); + } else { me->clearDisabled(DISABLED_CHRONO); + // Remove Chrono Particles, i.e. restore default particles + updateBodyParticleSystems(); + if (me->isKindOf(KINDOF_FS_INTERNET_CENTER)) { //Kris: October 20, 2003 - Patch 1.01 @@ -1393,6 +1408,64 @@ void ActiveBody::onSubdualChronoChange( Bool isNowSubdued ) } } +// ------------------------------------------------------------------------------------------------ +/* This function is called on state changes only. Body Type or Aflameness. */ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::applyChronoParticleSystems(void) +{ + deleteAllParticleSystems(); + + static const ParticleSystemTemplate* chronoEffectsLargeTemplate = TheParticleSystemManager->findTemplate(TheGlobalData->m_chronoDisableParticleSystemLarge); + static const ParticleSystemTemplate* chronoEffectsMediumTemplate = TheParticleSystemManager->findTemplate(TheGlobalData->m_chronoDisableParticleSystemMedium); + static const ParticleSystemTemplate* chronoEffectsSmallTemplate = TheParticleSystemManager->findTemplate(TheGlobalData->m_chronoDisableParticleSystemSmall); + + const ParticleSystemTemplate* chronoEffects; + + // TODO: select particles + Object* obj = getObject(); + + if (obj->isKindOf(KINDOF_INFANTRY)) { + chronoEffects = chronoEffectsSmallTemplate; + } + else if (obj->isKindOf(KINDOF_STRUCTURE)) { + chronoEffects = chronoEffectsLargeTemplate; + } + // Use Medium as default + else { + chronoEffects = chronoEffectsMediumTemplate; + } + + ParticleSystem* particleSystem = TheParticleSystemManager->createParticleSystem(chronoEffects); + if (particleSystem) + { + // set the position of the particle system in local object space + // particleSystem->setPosition(obj->getPosition()); + + // attach particle system to object + particleSystem->attachToObject(obj); + + // Scale particle count based on size + Real x = obj->getGeometryInfo().getMajorRadius(); + Real y = obj->getGeometryInfo().getMinorRadius(); + Real z = obj->getGeometryInfo().getMaxHeightAbovePosition() * 0.5; + particleSystem->setEmissionBoxHalfSize(x, y, z); + //Real size = x * y; + //particleSystem->setBurstCountMultiplier(MAX(1.0, sqrt(size * 0.02f))); // these are somewhat tweaked right now + //particleSystem->setBurstDelayMultiplier(MIN(5.0, sqrt(500.0f / size))); + + // create a new body particle system entry and keep this particle system in it + BodyParticleSystem* newEntry = newInstance(BodyParticleSystem); + newEntry->m_particleSystemID = particleSystem->getSystemID(); + newEntry->m_next = m_particleSystems; + m_particleSystems = newEntry; + + // DEBUG_LOG(("ActiveBody::applyChronoParticleSystems - created particleSystem.\n")); + } + else { + // DEBUG_LOG(("ActiveBody::applyChronoParticleSystems - Failed to create particleSystem?!\n")); + } +} + //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- Bool ActiveBody::isSubduedChrono() const @@ -1460,7 +1533,7 @@ UnsignedInt ActiveBody::getChronoDamageHealRate() const //------------------------------------------------------------------------------------------------- Real ActiveBody::getChronoDamageHealAmount() const { - DEBUG_LOG(("ActiveBody::getChronoDamageHealAmount() - maxHealth = %f\n", m_maxHealth)); + // DEBUG_LOG(("ActiveBody::getChronoDamageHealAmount() - maxHealth = %f\n", m_maxHealth)); return m_maxHealth * TheGlobalData->m_chronoDamageHealAmount; } @@ -1643,8 +1716,8 @@ void ActiveBody::overrideDamageFX(DamageFX* damageFX) m_curDamageFX = set->getDamageFX(); } } - DEBUG_LOG((">>>ActiveBody: overrideDamageFX - new m_curDamageFX = %d, m_damageFXOverride = %d\n", - m_curDamageFX, m_damageFXOverride)); + //DEBUG_LOG((">>>ActiveBody: overrideDamageFX - new m_curDamageFX = %d, m_damageFXOverride = %d\n", + // m_curDamageFX, m_damageFXOverride)); } // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ImmortalBody.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ImmortalBody.cpp index d7970249732..85f46c6c039 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ImmortalBody.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ImmortalBody.cpp @@ -52,13 +52,13 @@ ImmortalBody::~ImmortalBody( void ) // ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------ -void ImmortalBody::internalChangeHealth( Real delta ) +void ImmortalBody::internalChangeHealth( Real delta, Bool changeModelCondition) { // Don't let anything changes us to below one hit point delta = max( delta, -getHealth() + 1 ); // extend functionality, but I go first because I can't let you die and then fix it, I must prevent - ActiveBody::internalChangeHealth( delta ); + ActiveBody::internalChangeHealth( delta, changeModelCondition ); // nothing -- never mark it as dead. DEBUG_ASSERTCRASH( (getHealth() > 0 && !getObject()->isEffectivelyDead() ), ("Immortal objects should never get marked as dead!")); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/InactiveBody.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/InactiveBody.cpp index cb17a1b0572..88095e86589 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/InactiveBody.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/InactiveBody.cpp @@ -131,7 +131,7 @@ void InactiveBody::attemptHealing( DamageInfo *damageInfo ) //------------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------------- -void InactiveBody::internalChangeHealth( Real delta ) +void InactiveBody::internalChangeHealth( Real delta, Bool changeModelCondition) { // Inactive bodies have no health to increase or decrease diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index e83712e972d..1bf736457ad 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -2218,7 +2218,7 @@ void Object::setDisabledUntil( DisabledType type, UnsignedInt frame ) // Doh. Also shouldn't be tinting when disabled by scripting. // Doh^2. Also shouldn't be CLEARING tinting if we're disabling by held or script disabledness // Doh^3. Unmanned is no tint too - if( type != DISABLED_HELD && type != DISABLED_SCRIPT_DISABLED && type != DISABLED_UNMANNED && type != DISABLED_TELEPORT) + if( type != DISABLED_HELD && type != DISABLED_SCRIPT_DISABLED && type != DISABLED_UNMANNED && type != DISABLED_TELEPORT && type != DISABLED_CHRONO) { m_drawable->setTintStatus( TINT_STATUS_DISABLED ); } @@ -2395,6 +2395,7 @@ Bool Object::clearDisabled( DisabledType type ) exceptions.set(DISABLED_SCRIPT_DISABLED); exceptions.set(DISABLED_UNMANNED); exceptions.set(DISABLED_TELEPORT); + exceptions.set(DISABLED_CHRONO); DisabledMaskType myFlagsMinusExceptions = getDisabledFlags(); myFlagsMinusExceptions.clearAndSet(exceptions, DISABLEDMASK_NONE); @@ -5380,15 +5381,43 @@ void Object::notifyChronoDamage(Real amount) if (m_chronoDamageHelper) m_chronoDamageHelper->notifyChronoDamage(amount); - // TODO - // If we are gaining subdual damage, we are slowly tinting - //if (getDrawable()) - //{ - // if (amount > 0) - // getDrawable()->setTintStatus(TINT_STATUS_GAINING_SUBDUAL_DAMAGE); - // else - // getDrawable()->clearTintStatus(TINT_STATUS_GAINING_SUBDUAL_DAMAGE); - //} + //Real progress = INT_TO_REAL(now - m_dieFrame) / INT_TO_REAL(m_destructionFrame - m_dieFrame); + + BodyModuleInterface* body = getBodyModule(); + Drawable* draw = getDrawable(); + if (body != NULL && draw != NULL) { + + Real chronoTh = TheGlobalData->m_chronoDamageDisableThreshold * body->getMaxHealth(); + Real chronoDmg = body->getCurrentChronoDamageAmount(); + if (chronoDmg > chronoTh) { + Real progress = (chronoDmg - chronoTh) / (body->getMaxHealth() - chronoTh); + progress = min(1.0f, max(0.0f, progress)); + + Real alpha0 = TheGlobalData->m_chronoDisableAlphaStart; + Real alpha1 = TheGlobalData->m_chronoDisableAlphaEnd; + Real opacity = (1.0 - progress) * alpha0 + progress * alpha1; + + // DEBUG_LOG(("Object::notifyChronoDamage - progress = %f, alpha = %f\n", progress, opacity)); + + draw->setDrawableOpacity(opacity); + //draw->setEffectiveOpacity(opacity); + //draw->setSecondMaterialPassOpacity(opacity); + + } + else if (amount < 0) { + draw->setDrawableOpacity(1.0); + // DEBUG_LOG(("Object::notifyChronoDamage - reset opacity\n")); + } + } + + //If we are gaining chrono damage, we are slowly tinting + if (getDrawable()) + { + if (amount > 0) + getDrawable()->setTintStatus(TINT_STATUS_GAINING_CHRONO_DAMAGE); + else + getDrawable()->clearTintStatus(TINT_STATUS_GAINING_CHRONO_DAMAGE); + } } //------------------------------------------------------------------------------------------------- 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 76737f7c0e0..22cf98b94da 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp @@ -1,621 +1,625 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// TeleporterAIUpdate.cpp ////////// -// Will give self random move commands -// Author: Graham Smallwood, April 2002 - -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -#include "Common/GameAudio.h" -#include "Common/RandomValue.h" -#include "GameLogic/Module/TeleporterAIUpdate.h" -#include "GameLogic/Object.h" -#include "Common/Xfer.h" -#include "Common/DisabledTypes.h" -#include "Common/ModelState.h" -#include "GameClient/Drawable.h" -#include "GameClient/FXList.h" -#include "GameLogic/AI.h" -#include "GameLogic/AIGuard.h" -#include "GameLogic/AIPathfind.h" -#include "GameLogic/Module/AIUpdate.h" -#include "GameLogic/Damage.h" -#include "GameLogic/GameLogic.h" -#include "GameLogic/Weapon.h" -#include "GameLogic/PartitionManager.h" -#include "GameLogic/TerrainLogic.h" -#include "GameClient/TintStatus.h" - - -//------------------------------------------------------------------------------------------------- -TeleporterAIUpdateModuleData::TeleporterAIUpdateModuleData( void ) -{ - m_sourceFX = NULL; - m_targetFX = NULL; - m_recoverEndFX = NULL; - m_tintStatus = TINT_STATUS_INVALID; - m_opacityStart = 1.0; - m_opacityEnd = 1.0; -} - -//------------------------------------------------------------------------------------------------- -/*static*/ void TeleporterAIUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) -{ - AIUpdateModuleData::buildFieldParse(p); - - static const FieldParse dataFieldParse[] = - { - { "MinDistanceForTeleport", INI::parseReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_minDistance) }, - { "DisabledDurationPerDistance", INI::parseDurationReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_disabledDuration) }, - { "TeleportStartFX", INI::parseFXList, NULL, offsetof(TeleporterAIUpdateModuleData, m_sourceFX) }, - { "TeleportTargetFX", INI::parseFXList, NULL, offsetof(TeleporterAIUpdateModuleData, m_targetFX) }, - { "TeleportRecoverEndFX", INI::parseFXList, NULL, offsetof(TeleporterAIUpdateModuleData, m_recoverEndFX) }, - { "TeleportRecoverSoundAmbient", INI::parseAudioEventRTS, NULL, offsetof(TeleporterAIUpdateModuleData, m_recoverSoundLoop) }, - { "TeleportRecoverTint", TintStatusFlags::parseSingleBitFromINI, NULL, offsetof(TeleporterAIUpdateModuleData, m_tintStatus) }, - { "TeleportRecoverOpacityStart", INI::parseReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_opacityStart) }, - { "TeleportRecoverOpacityEnd", INI::parseReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_opacityEnd) }, - { 0, 0, 0, 0 } - }; - p.add(dataFieldParse); -} - - -//------------------------------------------------------------------------------------------------- -AIStateMachine* TeleporterAIUpdate::makeStateMachine() -{ - return newInstance(AIStateMachine)( getObject(), "TeleporterAIUpdateMachine"); -} - -//------------------------------------------------------------------------------------------------- -TeleporterAIUpdate::TeleporterAIUpdate( Thing *thing, const ModuleData* moduleData ) : AIUpdateInterface( thing, moduleData ) -{ - m_disabledUntil = 0; - m_disabledStart = 0; - m_isDisabled = false; -} - -//------------------------------------------------------------------------------------------------- -TeleporterAIUpdate::~TeleporterAIUpdate( void ) -{ - -} - -//------------------------------------------------------------------------------------------------- -UpdateSleepTime TeleporterAIUpdate::update(void) -{ - const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); - Object* obj = getObject(); - - //UpdateSleepTime ret = UPDATE_SLEEP_FOREVER; - - UnsignedInt now = TheGameLogic->getFrame(); - - if (m_isDisabled) { - if (m_disabledUntil > now) { - // We are currently disabled - Real progress = __max(__min(INT_TO_REAL(now - m_disabledStart) / INT_TO_REAL(m_disabledUntil - m_disabledStart), 1.0), 0.0); - - Drawable* drw = obj->getDrawable(); - if (drw) - { - // - set opacity - if (d->m_opacityStart < 1.0f || d->m_opacityEnd < 1.0f) { - Real curOpacity = (1.0 - progress) * d->m_opacityStart + progress * d->m_opacityEnd; - // DEBUG_LOG((">>> TPAI Update: opacity = %f\n", curOpacity)); - drw->setDrawableOpacity(curOpacity); - } - } - // We actually need to stop here, because the default update would allow us to attack while disabled - return UPDATE_SLEEP_NONE; - //ret = UPDATE_SLEEP_NONE; - } - else { - // We are done - removeRecoverEffects(); - m_isDisabled = false; - } - } - - // extend - // UpdateSleepTime ret2 = AIUpdateInterface::update(); - // return (ret < ret2) ? ret : ret2; - - return AIUpdateInterface::update(); - -} // end update - - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void TeleporterAIUpdate::applyRecoverEffects(Real dist) -{ - const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); - Object* obj = getObject(); - - // - set conditionstate - obj->setModelConditionState(MODELCONDITION_TELEPORT_RECOVER); - - // - add ambient sound - m_recoverSoundLoop = d->m_recoverSoundLoop; - m_recoverSoundLoop.setObjectID(obj->getID()); - m_recoverSoundLoop.setPlayingHandle(TheAudio->addAudioEvent(&m_recoverSoundLoop)); - - Drawable* drw = obj->getDrawable(); - if (drw) - { - // - set color tint - if (d->m_tintStatus > TINT_STATUS_INVALID && d->m_tintStatus < TINT_STATUS_COUNT) - { - drw->setTintStatus(d->m_tintStatus); - } - - // - set opacity - if (d->m_opacityStart < 1.0 || d->m_opacityEnd < 1.0) { - drw->setEffectiveOpacity(1.0); - } - } - -} - -// ------------------------------------------------------------------------------------------------ -void TeleporterAIUpdate::removeRecoverEffects() -{ - const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); - Object* obj = getObject(); - - obj->clearModelConditionState(MODELCONDITION_TELEPORT_RECOVER); - - TheAudio->removeAudioEvent(m_recoverSoundLoop.getPlayingHandle()); - - Drawable* drw = obj->getDrawable(); - if (drw) - { - // - clear color tint - if (d->m_tintStatus > TINT_STATUS_INVALID && d->m_tintStatus < TINT_STATUS_COUNT) - { - drw->clearTintStatus(d->m_tintStatus); - } - } - - FXList::doFXObj(d->m_recoverEndFX, getObject()); -} -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ - -UpdateSleepTime TeleporterAIUpdate::doTeleport(Coord3D targetPos, Real angle, Real dist) -{ - const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); - Object* obj = getObject(); - - FXList::doFXObj(d->m_sourceFX, getObject()); - - obj->setPosition(&targetPos); - obj->setOrientation(angle); - - FXList::doFXObj(d->m_targetFX, getObject()); - - destroyPath(); - - TheAI->pathfinder()->updateGoal(obj, &targetPos, TheTerrainLogic->getLayerForDestination(&targetPos)); - setLocomotorGoalOrientation(angle); - - UnsignedInt disabledFrames = REAL_TO_UNSIGNEDINT(dist * d->m_disabledDuration); - - m_disabledStart = TheGameLogic->getFrame(); - m_disabledUntil = m_disabledStart + disabledFrames; - - m_isDisabled = true; - obj->setDisabledUntil(DISABLED_TELEPORT, m_disabledUntil); - - applyRecoverEffects(dist); - - // return UPDATE_SLEEP(disabledFrames); - return UPDATE_SLEEP_NONE; // We can't actually sleep since we need to adjust some things dynamically - -} - -//------------------------------------------------------------------------------------------------- -Bool TeleporterAIUpdate::isLocationValid(Object* obj, const Coord3D* targetPos, Object* victim, const Coord3D* victimPos, Weapon* weap) -{ - 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); - - return !viewBlocked && inRange && posValid; -} - -//------------------------------------------------------------------------------------------------- -Bool TeleporterAIUpdate::findAttackLocation(Object* victim, const Coord3D* victimPos, Coord3D* targetPos, Real* targetAngle) -{ - Object* obj = getObject(); - Weapon* weap = obj->getCurrentWeapon(); - if (!weap) - return false; - - Coord3D newPos; - newPos.x = targetPos->x; - newPos.y = targetPos->y; - newPos.z = targetPos->z; - - // Check if the current location is valid. - // This needs to be rechecked after the disabled timer. - if (isLocationValid(obj, targetPos, victim, victimPos, weap)) { - if (!TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &newPos)) { - DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination failed!\n")); - } - //else { - // DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); - //} - - if (isLocationValid(obj, &newPos, victim, victimPos, weap)) { - // DEBUG_LOG((">>> TPAI - findAttackLocation: done with initial pos\n")); - *targetPos = newPos; - return true; - } - } - - newPos.x = targetPos->x; - newPos.y = targetPos->y; - newPos.z = targetPos->z; - - - Real RANGE_MARGIN = 10.0f; - - // If the unit's current distance is lower than the attack range, we try to keep this distance - - - Real maxRange = weap->getAttackRange(obj) - RANGE_MARGIN; - Real range = maxRange - weap->getTemplate()->getMinimumAttackRange(); - - - // Calculate direction vector from victim to candidate position - Coord3D dir; - Real distSq = ThePartitionManager->getGoalDistanceSquared(obj, targetPos, victimPos, FROM_CENTER_2D, &dir); - Real dist = sqrt(distSq); - if (dist < maxRange) { - maxRange = dist; - } - - Coord2D direction; - direction.x = -dir.x; - direction.y = -dir.y; - Real initAngle = atan2(direction.y, direction.x); // angle from victim to target - - direction.normalize(); - - const Real maxAngle = deg2rad(180.0f); - const Real step_size_angle = deg2rad(10.0f); - const Real step_size_length = 15.0f; - // const int max_steps = 500; - - const int max_rings = REAL_TO_INT(range / step_size_length); - const int max_steps = REAL_TO_INT(maxAngle / step_size_angle); - // DEBUG_LOG((">>> TPAI - findAttackLocation: range = %f, max_rings = %d\n", range, max_rings)); - for (int ring = 0; ring < max_rings; ++ring) { - - Real radius = maxRange - (ring * step_size_length); - - for (int step = 0; step < max_steps; ++step) { - int sign = (step % 2) ? 1 : -1; - Real angle = initAngle + (step * sign * step_size_angle); - - //polar offset - newPos.x = victimPos->x + radius * cos(angle); - newPos.y = victimPos->y + radius * sin(angle); - newPos.z = TheTerrainLogic->getGroundHeight(newPos.x, newPos.y); - - //viewBlocked = TheAI->pathfinder()->isAttackViewBlockedByObstacle(obj, newPos, victim, *victimPos); - //inRange = weap->isSourceObjectWithGoalPositionWithinAttackRange(obj, &newPos, victim, victimPos); - //destinationLayer = TheTerrainLogic->getLayerForDestination(&newPos); - //posValid = TheAI->pathfinder()->validMovementPosition(getObject()->getCrusherLevel() > 0, destinationLayer, getLocomotorSet(), &newPos); - - // DEBUG_LOG((">>> TPAI - findAttackLocation: candidate Pos: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); - - // TheAI->pathfinder()->adjustTargetDestination(obj, victim, victimPos, weap, &newPos); - if (!TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &newPos)) { - DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination failed!\n")); - } - //else { - // DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); - //} - - /*if (sign == 1) - FXList::doFXPos(debug_fx1, &newPos); - else - FXList::doFXPos(debug_fx2, &newPos);*/ - - if (isLocationValid(obj, &newPos, victim, victimPos, weap)) { - *targetPos = newPos; - *targetAngle = angle + PI; - //DEBUG_LOG((">>> TPAI - findAttackLocation: done after ring=%d, step=%d\n", ring, step)); - - return true; - } - } - } - - DEBUG_LOG((">>> TPAI - findAttackLocation: failed to find attack position\n")); - - return false; -} - -//------------------------------------------------------------------------------------------------- -UpdateSleepTime TeleporterAIUpdate::doLocomotor(void) -{ - if (!isMoving()) { - return AIUpdateInterface::doLocomotor(); - } - - const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); - - Object* obj = getObject(); - - Object* goalObj = getGoalObject(); - const Coord3D* goalPos = getGoalPosition(); - - Real requiredRange = 0; - - Coord3D targetPos; - Coord3D dir; - Real distSq; - - // TODO: Check states - // - (generic) Moving - // - Attacking - // - Guard - // -- GuardAttack - // -- Move to Object - // - Enter - - //Path* path = getPath(); - - // Get TargetPos - - if (goalObj != NULL) { - targetPos = *goalObj->getPosition(); - //goalPos = targetPos; //This should be the same anyways - //DEBUG_LOG((">>> TPAI - doLoc: goalOBJPos (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); - //if (isAttacking()) - distSq = ThePartitionManager->getDistanceSquared(obj, goalObj, FROM_BOUNDINGSPHERE_2D, &dir); - //else - // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - } - else if (goalPos != NULL && !(goalPos->x == 0 && goalPos->y == 0 && goalPos->z == 0)) { - targetPos = *goalPos; - //DEBUG_LOG((">>> TPAI - doLoc: goalPOS (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); - distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - } - else if (getStateMachine()->getCurrentStateID() == AI_GUARD) { - if (isAttacking()) { - AIGuardMachine* guardMachine = getStateMachine()->getGuardMachine(); - if (guardMachine != NULL) { - ObjectID nemID = guardMachine->getNemesisID(); - if (nemID != INVALID_ID) { - Object* nemesis = TheGameLogic->findObjectByID(nemID); - if (nemesis != NULL) { - goalObj = nemesis; - goalPos = goalObj->getPosition(); - targetPos = *goalPos; - - //DEBUG_LOG((">>> TPAI - doLoc: goalPos GUARD NEMESIS (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); - //distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_BOUNDINGSPHERE_2D, &dir); - distSq = ThePartitionManager->getDistanceSquared(obj, goalObj, FROM_BOUNDINGSPHERE_2D, &dir); - } - } - } - } - else if (getGuardLocation() != NULL && !(getGuardLocation()->x == 0 && getGuardLocation()->y == 0 && getGuardLocation()->z == 0)) { // getStateMachine()->isInGuardIdleState() - targetPos = *getGuardLocation(); - //TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &targetPos); - distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - //DEBUG_LOG((">>> TPAI - doLoc: goalPos GUARD (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); - if (getStateMachine()->isInGuardIdleState()) { - requiredRange = 25.0f; // Allow extra range to give some room for large groups guarding - } - } - } - else { - DEBUG_LOG((">>> TPAI - doLoc: GOAL POS AND OBJ ARE NULL??\n")); - return UPDATE_SLEEP_FOREVER; - } - - if (getStateMachine()->getCurrentStateID() == AI_ENTER) { - // If we want to enter and got this close, we just move normally - requiredRange = 15.0f; - //} else if (getStateMachine()->getCurrentStateID() == AI_DOCK) { - // // Get the dock's approach position. - // // If we are at least X distance away, teleport, otherwise, do normal movement - // DockUpdateInterface* dock = goalObj->getDockUpdateInterface(); - // if (dock != NULL) { - // int dockIndex; // we don't really need this - // Bool reserved = dock->reserveApproachPosition(obj, &targetPos, &dockIndex); - // if (reserved) { - // // Get dist to goal obj center - // Real distSqObj = ThePartitionManager->getDistanceSquared(obj, goalObj, FROM_CENTER_2D, &dir); - // // Get dist to approach pos - // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - - // DEBUG_LOG((">>> TPAI: DOCK distSq = %f, distSqObj = %f\n", distSq, distSqObj)); - - // // If we are close to both the approach pos and the center pos, move normally - // Real minDistSq = 25.0f * 25.0f; - // if (distSqObj < minDistSq && distSqObj < minDistSq) { - // return AIUpdateInterface::doLocomotor(); - // } - // // otherwise teleport - // } - // } - } - - DEBUG_LOG((">>> TPAI - doLoc: LocomotorGoalType = %d, AI STATE = %s (%d)\n", getLocomotorGoalType(), getStateMachine()->getCurrentStateName(), getStateMachine()->getCurrentStateID())); - - Real RANGE_MARGIN = 5.0f; // We calculate distance this much shorter than weapon range - Real TELEPORT_DIST_MARGIN = 5.0f; // We teleport this much closer than needed - - // Get initial dist and dir - // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - Real dist = sqrt(distSq); - Real targetAngle = atan2(dir.y, dir.x); - dir.normalize(); - - // We are within min range - if (dist <= d->m_minDistance || dist <= requiredRange) { - return AIUpdateInterface::doLocomotor(); - } - - //When we attack, we attempt to teleport into range - if (isAttacking()) { - // requiredRange = obj->getLargestWeaponRange(); - Weapon* weap = obj->getCurrentWeapon(); - if (!weap) - return AIUpdateInterface::doLocomotor(); - - // Check if current position is valid for attack - if (isLocationValid(obj, obj->getPosition(), goalObj, goalPos, weap)) { - return AIUpdateInterface::doLocomotor(); - } - - requiredRange = weap->getAttackRange(obj) - RANGE_MARGIN; - - //Adjust target to required distance - if (requiredRange > 0) { - dir.scale(min(dist, requiredRange - TELEPORT_DIST_MARGIN)); - targetPos.sub(&dir); - targetPos.z = TheTerrainLogic->getGroundHeight(targetPos.x, targetPos.y); - } - - // Find proper attack position for adjusted target - if (!findAttackLocation(goalObj, goalPos, &targetPos, &targetAngle)) { - DEBUG_LOG((">>> TPAI - doLoc: isAttacking. FAILED TO FIND VALID LOCATION!\n")); - - // This might happen if we try to attack e.g. a boat in water - // TODO: Should we move as close as we can? - - return AIUpdateInterface::doLocomotor(); - } - //DEBUG_LOG((">>> TPAI - doLoc: findAttackLocation targetPos (AFTER) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); - - //recompute distance and angle - distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - ////targetAngle = atan2(dir.y, dir.x); - dist = sqrt(distSq); - - //DEBUG_LOG((">>> TPAI - doLoc: isAttacking, dist = %f, reqRange = %f\n", dist, requiredRange)); - //m_inAttackPos = TRUE; - } - //else if( /*use special power?*/) { - // //same as with attacks, try to get into range - //} - // else if (getStateMachine()->getCurrentStateID() == AI_ENTER || getStateMachine()->getCurrentStateID() == AI_ENTER) { - else if (goalObj != NULL) { - // We need to correct the position to the outer bounding box of the structure - // TODO: Respect actual geometry, not just radius - requiredRange = goalObj->getGeometryInfo().getBoundingCircleRadius(); - if (requiredRange > 0) { - dir.scale(min(dist, requiredRange)); - targetPos.sub(&dir); - targetPos.z = TheTerrainLogic->getGroundHeight(targetPos.x, targetPos.y); - } - TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &targetPos); - - //recompute distance and angle - distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - - // targetAngle = atan2(dir.y, dir.x); - targetAngle = atan2(goalPos->y - targetPos.y, goalPos->x - targetPos.x); - dist = sqrt(distSq); - } - else { - // TODO: if this doesn't find a location, - TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &targetPos); - - //recompute distance and angle - distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - targetAngle = atan2(dir.y, dir.x); - dist = sqrt(distSq); - } - - // DEBUG_LOG((">>> TPAI - doLoc: teleport with dist = %f\n", dist)); - doTeleport(targetPos, targetAngle, dist); - - return AIUpdateInterface::doLocomotor(); - -} - -//------------------------------------------------------------------------------------------------- -/** - * See if we can do a quick path without pathfinding. - */ -Bool TeleporterAIUpdate::canComputeQuickPath(void) -{ - return true; -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -Bool TeleporterAIUpdate::computeQuickPath(const Coord3D* destination) -{ - return AIUpdateInterface::computeQuickPath(destination); -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void TeleporterAIUpdate::crc( Xfer *xfer ) -{ - // extend base class - AIUpdateInterface::crc(xfer); -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void TeleporterAIUpdate::xfer( Xfer *xfer ) -{ - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // extend base class - AIUpdateInterface::xfer(xfer); - - xfer->xferBool(&m_isDisabled); - - xfer->xferUnsignedInt(&m_disabledUntil); - xfer->xferUnsignedInt(&m_disabledStart); - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void TeleporterAIUpdate::loadPostProcess( void ) -{ - // extend base class - AIUpdateInterface::loadPostProcess(); -} // end loadPostProcess +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// TeleporterAIUpdate.cpp ////////// +// Will give self random move commands +// Author: Graham Smallwood, April 2002 + +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#include "Common/GameAudio.h" +#include "Common/RandomValue.h" +#include "GameLogic/Module/TeleporterAIUpdate.h" +#include "GameLogic/Object.h" +#include "Common/Xfer.h" +#include "Common/DisabledTypes.h" +#include "Common/ModelState.h" +#include "GameClient/Drawable.h" +#include "GameClient/FXList.h" +#include "GameLogic/AI.h" +#include "GameLogic/AIGuard.h" +#include "GameLogic/AIPathfind.h" +#include "GameLogic/Module/AIUpdate.h" +#include "GameLogic/Damage.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/Weapon.h" +#include "GameLogic/PartitionManager.h" +#include "GameLogic/TerrainLogic.h" +#include "GameClient/TintStatus.h" + + +//------------------------------------------------------------------------------------------------- +TeleporterAIUpdateModuleData::TeleporterAIUpdateModuleData( void ) +{ + m_sourceFX = NULL; + m_targetFX = NULL; + m_recoverEndFX = NULL; + m_tintStatus = TINT_STATUS_INVALID; + m_opacityStart = 1.0; + m_opacityEnd = 1.0; +} + +//------------------------------------------------------------------------------------------------- +/*static*/ void TeleporterAIUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + AIUpdateModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + { "MinDistanceForTeleport", INI::parseReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_minDistance) }, + { "DisabledDurationPerDistance", INI::parseDurationReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_disabledDuration) }, + { "TeleportStartFX", INI::parseFXList, NULL, offsetof(TeleporterAIUpdateModuleData, m_sourceFX) }, + { "TeleportTargetFX", INI::parseFXList, NULL, offsetof(TeleporterAIUpdateModuleData, m_targetFX) }, + { "TeleportRecoverEndFX", INI::parseFXList, NULL, offsetof(TeleporterAIUpdateModuleData, m_recoverEndFX) }, + { "TeleportRecoverSoundAmbient", INI::parseAudioEventRTS, NULL, offsetof(TeleporterAIUpdateModuleData, m_recoverSoundLoop) }, + { "TeleportRecoverTint", TintStatusFlags::parseSingleBitFromINI, NULL, offsetof(TeleporterAIUpdateModuleData, m_tintStatus) }, + { "TeleportRecoverOpacityStart", INI::parsePercentToReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_opacityStart) }, + { "TeleportRecoverOpacityEnd", INI::parsePercentToReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_opacityEnd) }, + { 0, 0, 0, 0 } + }; + p.add(dataFieldParse); +} + + +//------------------------------------------------------------------------------------------------- +AIStateMachine* TeleporterAIUpdate::makeStateMachine() +{ + return newInstance(AIStateMachine)( getObject(), "TeleporterAIUpdateMachine"); +} + +//------------------------------------------------------------------------------------------------- +TeleporterAIUpdate::TeleporterAIUpdate( Thing *thing, const ModuleData* moduleData ) : AIUpdateInterface( thing, moduleData ) +{ + m_disabledUntil = 0; + m_disabledStart = 0; + m_isDisabled = false; +} + +//------------------------------------------------------------------------------------------------- +TeleporterAIUpdate::~TeleporterAIUpdate( void ) +{ + +} + +//------------------------------------------------------------------------------------------------- +UpdateSleepTime TeleporterAIUpdate::update(void) +{ + const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); + Object* obj = getObject(); + + //UpdateSleepTime ret = UPDATE_SLEEP_FOREVER; + + UnsignedInt now = TheGameLogic->getFrame(); + + if (m_isDisabled) { + if (m_disabledUntil > now) { + // We are currently disabled + Real progress = __max(__min(INT_TO_REAL(now - m_disabledStart) / INT_TO_REAL(m_disabledUntil - m_disabledStart), 1.0), 0.0); + + Drawable* draw = obj->getDrawable(); + if (draw) + { + // - set opacity + if (d->m_opacityStart < 1.0f || d->m_opacityEnd < 1.0f) { + Real opacity = (1.0 - progress) * d->m_opacityStart + progress * d->m_opacityEnd; + // DEBUG_LOG((">>> TPAI Update: opacity = %f\n", curOpacity)); + draw->setDrawableOpacity(opacity); + //draw->setEffectiveOpacity(opacity); + //draw->setSecondMaterialPassOpacity(opacity); + } + } + // We actually need to stop here, because the default update would allow us to attack while disabled + return UPDATE_SLEEP_NONE; + //ret = UPDATE_SLEEP_NONE; + } + else { + // We are done + removeRecoverEffects(); + m_isDisabled = false; + } + } + + // extend + // UpdateSleepTime ret2 = AIUpdateInterface::update(); + // return (ret < ret2) ? ret : ret2; + + return AIUpdateInterface::update(); + +} // end update + + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void TeleporterAIUpdate::applyRecoverEffects(Real dist) +{ + const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); + Object* obj = getObject(); + + // - set conditionstate + obj->setModelConditionState(MODELCONDITION_TELEPORT_RECOVER); + + // - add ambient sound + m_recoverSoundLoop = d->m_recoverSoundLoop; + m_recoverSoundLoop.setObjectID(obj->getID()); + m_recoverSoundLoop.setPlayingHandle(TheAudio->addAudioEvent(&m_recoverSoundLoop)); + + Drawable* draw = obj->getDrawable(); + if (draw) + { + // - set color tint + if (d->m_tintStatus > TINT_STATUS_INVALID && d->m_tintStatus < TINT_STATUS_COUNT) + { + draw->setTintStatus(d->m_tintStatus); + } + + // - set opacity + if (d->m_opacityStart < 1.0 || d->m_opacityEnd < 1.0) { + //draw->setEffectiveOpacity(1.0); + //draw->setSecondMaterialPassOpacity(1.0); + draw->setDrawableOpacity(1.0); + } + } + +} + +// ------------------------------------------------------------------------------------------------ +void TeleporterAIUpdate::removeRecoverEffects() +{ + const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); + Object* obj = getObject(); + + obj->clearModelConditionState(MODELCONDITION_TELEPORT_RECOVER); + + TheAudio->removeAudioEvent(m_recoverSoundLoop.getPlayingHandle()); + + Drawable* drw = obj->getDrawable(); + if (drw) + { + // - clear color tint + if (d->m_tintStatus > TINT_STATUS_INVALID && d->m_tintStatus < TINT_STATUS_COUNT) + { + drw->clearTintStatus(d->m_tintStatus); + } + } + + FXList::doFXObj(d->m_recoverEndFX, getObject()); +} +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ + +UpdateSleepTime TeleporterAIUpdate::doTeleport(Coord3D targetPos, Real angle, Real dist) +{ + const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); + Object* obj = getObject(); + + FXList::doFXObj(d->m_sourceFX, getObject()); + + obj->setPosition(&targetPos); + obj->setOrientation(angle); + + FXList::doFXObj(d->m_targetFX, getObject()); + + destroyPath(); + + TheAI->pathfinder()->updateGoal(obj, &targetPos, TheTerrainLogic->getLayerForDestination(&targetPos)); + setLocomotorGoalOrientation(angle); + + UnsignedInt disabledFrames = REAL_TO_UNSIGNEDINT(dist * d->m_disabledDuration); + + m_disabledStart = TheGameLogic->getFrame(); + m_disabledUntil = m_disabledStart + disabledFrames; + + m_isDisabled = true; + obj->setDisabledUntil(DISABLED_TELEPORT, m_disabledUntil); + + applyRecoverEffects(dist); + + // return UPDATE_SLEEP(disabledFrames); + return UPDATE_SLEEP_NONE; // We can't actually sleep since we need to adjust some things dynamically + +} + +//------------------------------------------------------------------------------------------------- +Bool TeleporterAIUpdate::isLocationValid(Object* obj, const Coord3D* targetPos, Object* victim, const Coord3D* victimPos, Weapon* weap) +{ + 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); + + return !viewBlocked && inRange && posValid; +} + +//------------------------------------------------------------------------------------------------- +Bool TeleporterAIUpdate::findAttackLocation(Object* victim, const Coord3D* victimPos, Coord3D* targetPos, Real* targetAngle) +{ + Object* obj = getObject(); + Weapon* weap = obj->getCurrentWeapon(); + if (!weap) + return false; + + Coord3D newPos; + newPos.x = targetPos->x; + newPos.y = targetPos->y; + newPos.z = targetPos->z; + + // Check if the current location is valid. + // This needs to be rechecked after the disabled timer. + if (isLocationValid(obj, targetPos, victim, victimPos, weap)) { + if (!TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &newPos)) { + DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination failed!\n")); + } + //else { + // DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); + //} + + if (isLocationValid(obj, &newPos, victim, victimPos, weap)) { + // DEBUG_LOG((">>> TPAI - findAttackLocation: done with initial pos\n")); + *targetPos = newPos; + return true; + } + } + + newPos.x = targetPos->x; + newPos.y = targetPos->y; + newPos.z = targetPos->z; + + + Real RANGE_MARGIN = 10.0f; + + // If the unit's current distance is lower than the attack range, we try to keep this distance + + + Real maxRange = weap->getAttackRange(obj) - RANGE_MARGIN; + Real range = maxRange - weap->getTemplate()->getMinimumAttackRange(); + + + // Calculate direction vector from victim to candidate position + Coord3D dir; + Real distSq = ThePartitionManager->getGoalDistanceSquared(obj, targetPos, victimPos, FROM_CENTER_2D, &dir); + Real dist = sqrt(distSq); + if (dist < maxRange) { + maxRange = dist; + } + + Coord2D direction; + direction.x = -dir.x; + direction.y = -dir.y; + Real initAngle = atan2(direction.y, direction.x); // angle from victim to target + + direction.normalize(); + + const Real maxAngle = deg2rad(180.0f); + const Real step_size_angle = deg2rad(10.0f); + const Real step_size_length = 15.0f; + // const int max_steps = 500; + + const int max_rings = REAL_TO_INT(range / step_size_length); + const int max_steps = REAL_TO_INT(maxAngle / step_size_angle); + // DEBUG_LOG((">>> TPAI - findAttackLocation: range = %f, max_rings = %d\n", range, max_rings)); + for (int ring = 0; ring < max_rings; ++ring) { + + Real radius = maxRange - (ring * step_size_length); + + for (int step = 0; step < max_steps; ++step) { + int sign = (step % 2) ? 1 : -1; + Real angle = initAngle + (step * sign * step_size_angle); + + //polar offset + newPos.x = victimPos->x + radius * cos(angle); + newPos.y = victimPos->y + radius * sin(angle); + newPos.z = TheTerrainLogic->getGroundHeight(newPos.x, newPos.y); + + //viewBlocked = TheAI->pathfinder()->isAttackViewBlockedByObstacle(obj, newPos, victim, *victimPos); + //inRange = weap->isSourceObjectWithGoalPositionWithinAttackRange(obj, &newPos, victim, victimPos); + //destinationLayer = TheTerrainLogic->getLayerForDestination(&newPos); + //posValid = TheAI->pathfinder()->validMovementPosition(getObject()->getCrusherLevel() > 0, destinationLayer, getLocomotorSet(), &newPos); + + // DEBUG_LOG((">>> TPAI - findAttackLocation: candidate Pos: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); + + // TheAI->pathfinder()->adjustTargetDestination(obj, victim, victimPos, weap, &newPos); + if (!TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &newPos)) { + DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination failed!\n")); + } + //else { + // DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); + //} + + /*if (sign == 1) + FXList::doFXPos(debug_fx1, &newPos); + else + FXList::doFXPos(debug_fx2, &newPos);*/ + + if (isLocationValid(obj, &newPos, victim, victimPos, weap)) { + *targetPos = newPos; + *targetAngle = angle + PI; + //DEBUG_LOG((">>> TPAI - findAttackLocation: done after ring=%d, step=%d\n", ring, step)); + + return true; + } + } + } + + DEBUG_LOG((">>> TPAI - findAttackLocation: failed to find attack position\n")); + + return false; +} + +//------------------------------------------------------------------------------------------------- +UpdateSleepTime TeleporterAIUpdate::doLocomotor(void) +{ + if (!isMoving()) { + return AIUpdateInterface::doLocomotor(); + } + + const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); + + Object* obj = getObject(); + + Object* goalObj = getGoalObject(); + const Coord3D* goalPos = getGoalPosition(); + + Real requiredRange = 0; + + Coord3D targetPos; + Coord3D dir; + Real distSq; + + // TODO: Check states + // - (generic) Moving + // - Attacking + // - Guard + // -- GuardAttack + // -- Move to Object + // - Enter + + //Path* path = getPath(); + + // Get TargetPos + + if (goalObj != NULL) { + targetPos = *goalObj->getPosition(); + //goalPos = targetPos; //This should be the same anyways + //DEBUG_LOG((">>> TPAI - doLoc: goalOBJPos (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + //if (isAttacking()) + distSq = ThePartitionManager->getDistanceSquared(obj, goalObj, FROM_BOUNDINGSPHERE_2D, &dir); + //else + // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + } + else if (goalPos != NULL && !(goalPos->x == 0 && goalPos->y == 0 && goalPos->z == 0)) { + targetPos = *goalPos; + //DEBUG_LOG((">>> TPAI - doLoc: goalPOS (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + } + else if (getStateMachine()->getCurrentStateID() == AI_GUARD) { + if (isAttacking()) { + AIGuardMachine* guardMachine = getStateMachine()->getGuardMachine(); + if (guardMachine != NULL) { + ObjectID nemID = guardMachine->getNemesisID(); + if (nemID != INVALID_ID) { + Object* nemesis = TheGameLogic->findObjectByID(nemID); + if (nemesis != NULL) { + goalObj = nemesis; + goalPos = goalObj->getPosition(); + targetPos = *goalPos; + + //DEBUG_LOG((">>> TPAI - doLoc: goalPos GUARD NEMESIS (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + //distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_BOUNDINGSPHERE_2D, &dir); + distSq = ThePartitionManager->getDistanceSquared(obj, goalObj, FROM_BOUNDINGSPHERE_2D, &dir); + } + } + } + } + else if (getGuardLocation() != NULL && !(getGuardLocation()->x == 0 && getGuardLocation()->y == 0 && getGuardLocation()->z == 0)) { // getStateMachine()->isInGuardIdleState() + targetPos = *getGuardLocation(); + //TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &targetPos); + distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + //DEBUG_LOG((">>> TPAI - doLoc: goalPos GUARD (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + if (getStateMachine()->isInGuardIdleState()) { + requiredRange = 25.0f; // Allow extra range to give some room for large groups guarding + } + } + } + else { + DEBUG_LOG((">>> TPAI - doLoc: GOAL POS AND OBJ ARE NULL??\n")); + return UPDATE_SLEEP_FOREVER; + } + + if (getStateMachine()->getCurrentStateID() == AI_ENTER) { + // If we want to enter and got this close, we just move normally + requiredRange = 15.0f; + //} else if (getStateMachine()->getCurrentStateID() == AI_DOCK) { + // // Get the dock's approach position. + // // If we are at least X distance away, teleport, otherwise, do normal movement + // DockUpdateInterface* dock = goalObj->getDockUpdateInterface(); + // if (dock != NULL) { + // int dockIndex; // we don't really need this + // Bool reserved = dock->reserveApproachPosition(obj, &targetPos, &dockIndex); + // if (reserved) { + // // Get dist to goal obj center + // Real distSqObj = ThePartitionManager->getDistanceSquared(obj, goalObj, FROM_CENTER_2D, &dir); + // // Get dist to approach pos + // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + + // DEBUG_LOG((">>> TPAI: DOCK distSq = %f, distSqObj = %f\n", distSq, distSqObj)); + + // // If we are close to both the approach pos and the center pos, move normally + // Real minDistSq = 25.0f * 25.0f; + // if (distSqObj < minDistSq && distSqObj < minDistSq) { + // return AIUpdateInterface::doLocomotor(); + // } + // // otherwise teleport + // } + // } + } + + DEBUG_LOG((">>> TPAI - doLoc: LocomotorGoalType = %d, AI STATE = %s (%d)\n", getLocomotorGoalType(), getStateMachine()->getCurrentStateName(), getStateMachine()->getCurrentStateID())); + + Real RANGE_MARGIN = 5.0f; // We calculate distance this much shorter than weapon range + Real TELEPORT_DIST_MARGIN = 5.0f; // We teleport this much closer than needed + + // Get initial dist and dir + // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + Real dist = sqrt(distSq); + Real targetAngle = atan2(dir.y, dir.x); + dir.normalize(); + + // We are within min range + if (dist <= d->m_minDistance || dist <= requiredRange) { + return AIUpdateInterface::doLocomotor(); + } + + //When we attack, we attempt to teleport into range + if (isAttacking()) { + // requiredRange = obj->getLargestWeaponRange(); + Weapon* weap = obj->getCurrentWeapon(); + if (!weap) + return AIUpdateInterface::doLocomotor(); + + // Check if current position is valid for attack + if (isLocationValid(obj, obj->getPosition(), goalObj, goalPos, weap)) { + return AIUpdateInterface::doLocomotor(); + } + + requiredRange = weap->getAttackRange(obj) - RANGE_MARGIN; + + //Adjust target to required distance + if (requiredRange > 0) { + dir.scale(min(dist, requiredRange - TELEPORT_DIST_MARGIN)); + targetPos.sub(&dir); + targetPos.z = TheTerrainLogic->getGroundHeight(targetPos.x, targetPos.y); + } + + // Find proper attack position for adjusted target + if (!findAttackLocation(goalObj, goalPos, &targetPos, &targetAngle)) { + DEBUG_LOG((">>> TPAI - doLoc: isAttacking. FAILED TO FIND VALID LOCATION!\n")); + + // This might happen if we try to attack e.g. a boat in water + // TODO: Should we move as close as we can? + + return AIUpdateInterface::doLocomotor(); + } + //DEBUG_LOG((">>> TPAI - doLoc: findAttackLocation targetPos (AFTER) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + + //recompute distance and angle + distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + ////targetAngle = atan2(dir.y, dir.x); + dist = sqrt(distSq); + + //DEBUG_LOG((">>> TPAI - doLoc: isAttacking, dist = %f, reqRange = %f\n", dist, requiredRange)); + //m_inAttackPos = TRUE; + } + //else if( /*use special power?*/) { + // //same as with attacks, try to get into range + //} + // else if (getStateMachine()->getCurrentStateID() == AI_ENTER || getStateMachine()->getCurrentStateID() == AI_ENTER) { + else if (goalObj != NULL) { + // We need to correct the position to the outer bounding box of the structure + // TODO: Respect actual geometry, not just radius + requiredRange = goalObj->getGeometryInfo().getBoundingCircleRadius(); + if (requiredRange > 0) { + dir.scale(min(dist, requiredRange)); + targetPos.sub(&dir); + targetPos.z = TheTerrainLogic->getGroundHeight(targetPos.x, targetPos.y); + } + TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &targetPos); + + //recompute distance and angle + distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + + // targetAngle = atan2(dir.y, dir.x); + targetAngle = atan2(goalPos->y - targetPos.y, goalPos->x - targetPos.x); + dist = sqrt(distSq); + } + else { + // TODO: if this doesn't find a location, + TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &targetPos); + + //recompute distance and angle + distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + targetAngle = atan2(dir.y, dir.x); + dist = sqrt(distSq); + } + + // DEBUG_LOG((">>> TPAI - doLoc: teleport with dist = %f\n", dist)); + doTeleport(targetPos, targetAngle, dist); + + return AIUpdateInterface::doLocomotor(); + +} + +//------------------------------------------------------------------------------------------------- +/** + * See if we can do a quick path without pathfinding. + */ +Bool TeleporterAIUpdate::canComputeQuickPath(void) +{ + return true; +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +Bool TeleporterAIUpdate::computeQuickPath(const Coord3D* destination) +{ + return AIUpdateInterface::computeQuickPath(destination); +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void TeleporterAIUpdate::crc( Xfer *xfer ) +{ + // extend base class + AIUpdateInterface::crc(xfer); +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void TeleporterAIUpdate::xfer( Xfer *xfer ) +{ + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // extend base class + AIUpdateInterface::xfer(xfer); + + xfer->xferBool(&m_isDisabled); + + xfer->xferUnsignedInt(&m_disabledUntil); + xfer->xferUnsignedInt(&m_disabledStart); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void TeleporterAIUpdate::loadPostProcess( void ) +{ + // extend base class + AIUpdateInterface::loadPostProcess(); +} // end loadPostProcess diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp index 9aee7fff840..f2d8b51197e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp @@ -627,8 +627,6 @@ Real WeaponTemplate::estimateWeaponTemplateDamage( } - - if (damageType == DAMAGE_SURRENDER || m_allowAttackGarrisonedBldgs) { ContainModuleInterface* contain = victimObj->getContain(); @@ -654,6 +652,13 @@ Real WeaponTemplate::estimateWeaponTemplateDamage( { return 1.0f; } + + // Units that get disabled by Chrono damage cannot be attacked + if (victimObj->isDisabledByType(DISABLED_CHRONO) && + !(damageType == DAMAGE_CHRONO_GUN || damageType == DAMAGE_CHRONO_UNRESISTABLE)) { + return 0.0; + } + } //@todo Kris need to examine the DAMAGE_HACK type for damage estimation purposes. From fc2b94e1851fa17503aa96e0f04f520576577bcd Mon Sep 17 00:00:00 2001 From: Andi Date: Tue, 29 Jul 2025 08:39:28 +0200 Subject: [PATCH 41/42] Add ambient sound for chrono stuff --- GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h | 2 ++ GeneralsMD/Code/GameEngine/Include/Common/MiscAudio.h | 1 + .../Code/GameEngine/Include/GameLogic/Module/ActiveBody.h | 3 +++ GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp | 2 +- .../Code/GameEngine/Source/Common/INI/INIMiscAudio.cpp | 3 ++- .../GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp | 6 ++++++ 6 files changed, 15 insertions(+), 2 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h index 4f0fda90d31..411779d9035 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h @@ -540,6 +540,8 @@ class GlobalData : public SubsystemInterface AsciiString m_chronoDisableParticleSystemMedium; AsciiString m_chronoDisableParticleSystemSmall; + //AudioEventRTS m_chronoDisableSoundLoop; + DeathTypeFlags m_defaultExcludedDeathTypes; diff --git a/GeneralsMD/Code/GameEngine/Include/Common/MiscAudio.h b/GeneralsMD/Code/GameEngine/Include/Common/MiscAudio.h index 5c032f736db..7cbc338d5ed 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/MiscAudio.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/MiscAudio.h @@ -72,6 +72,7 @@ struct MiscAudio AudioEventRTS m_sabotageShutDownBuilding; ///< When Saboteur hits a building AudioEventRTS m_sabotageResetTimerBuilding; ///< When Saboteur hits a building AudioEventRTS m_aircraftWheelScreech; ///< When a jet lands on a runway. + AudioEventRTS m_chronoDisabledSoundLoop; ///< When a unit is being disabled/deleted by a chrono gun }; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h index b7d8575c5a4..57eb9f3d058 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h @@ -34,6 +34,7 @@ // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "Common/DamageFX.h" +#include "Common/MiscAudio.h" #include "GameLogic/Module/BodyModule.h" #include "GameLogic/Damage.h" #include "GameLogic/Armor.h" @@ -181,6 +182,8 @@ class ActiveBody : public BodyModule Bool m_damageFXOverride; BodyParticleSystem *m_particleSystems; ///< particle systems created and attached to this object + + AudioEventRTS m_chronoDisabledSoundLoop; /* Note, you MUST call validateArmorAndDamageFX() before accessing these fields. diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp index e9c1bf3eadd..69518d54c53 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp @@ -554,7 +554,7 @@ GlobalData* GlobalData::m_theOriginal = NULL; {"ChronoDamageParticleSystemLarge", INI::parseAsciiString, NULL, offsetof(GlobalData, m_chronoDisableParticleSystemLarge) }, {"ChronoDamageParticleSystemMedium", INI::parseAsciiString, NULL, offsetof(GlobalData, m_chronoDisableParticleSystemMedium) }, {"ChronoDamageParticleSystemSmall", INI::parseAsciiString, NULL, offsetof(GlobalData, m_chronoDisableParticleSystemSmall) }, - + {"DefaultExcludedDeathTypes", INI::parseDeathTypeFlagsList, NULL, offsetof(GlobalData, m_defaultExcludedDeathTypes) }, { NULL, NULL, NULL, 0 } // keep this last diff --git a/GeneralsMD/Code/GameEngine/Source/Common/INI/INIMiscAudio.cpp b/GeneralsMD/Code/GameEngine/Source/Common/INI/INIMiscAudio.cpp index a5b2261de57..7b1eac68de5 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/INI/INIMiscAudio.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/INI/INIMiscAudio.cpp @@ -65,7 +65,8 @@ const FieldParse MiscAudio::m_fieldParseTable[] = { "RepairSparks", INI::parseAudioEventRTS, NULL, offsetof( MiscAudio, m_repairSparks ) }, { "SabotageShutDownBuilding", INI::parseAudioEventRTS, NULL, offsetof( MiscAudio, m_sabotageShutDownBuilding ) }, { "SabotageResetTimeBuilding", INI::parseAudioEventRTS, NULL, offsetof( MiscAudio, m_sabotageResetTimerBuilding ) }, - { "AircraftWheelScreech", INI::parseAudioEventRTS, NULL, offsetof( MiscAudio, m_aircraftWheelScreech ) }, + { "AircraftWheelScreech", INI::parseAudioEventRTS, NULL, offsetof( MiscAudio, m_aircraftWheelScreech ) }, + { "ChronoDisabledSoundAmbient", INI::parseAudioEventRTS, NULL, offsetof( MiscAudio, m_chronoDisabledSoundLoop) }, { 0, 0, 0, 0 } }; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp index fbab52ccbea..2e48cbe1e02 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp @@ -1385,6 +1385,10 @@ void ActiveBody::onSubdualChronoChange( Bool isNowSubdued ) // Apply Chrono Particles applyChronoParticleSystems(); + m_chronoDisabledSoundLoop = TheAudio->getMiscAudio()->m_chronoDisabledSoundLoop; + m_chronoDisabledSoundLoop.setObjectID(me->getID()); + m_chronoDisabledSoundLoop.setPlayingHandle(TheAudio->addAudioEvent(&m_chronoDisabledSoundLoop)); + ContainModuleInterface *contain = me->getContain(); if ( contain ) contain->orderAllPassengersToIdle( CMD_FROM_AI ); @@ -1396,6 +1400,8 @@ void ActiveBody::onSubdualChronoChange( Bool isNowSubdued ) // Remove Chrono Particles, i.e. restore default particles updateBodyParticleSystems(); + TheAudio->removeAudioEvent(m_chronoDisabledSoundLoop.getPlayingHandle()); + if (me->isKindOf(KINDOF_FS_INTERNET_CENTER)) { //Kris: October 20, 2003 - Patch 1.01 From b3b03019184d64be39a55aef23dcb15a023fa599 Mon Sep 17 00:00:00 2001 From: Andi Date: Tue, 29 Jul 2025 09:52:32 +0200 Subject: [PATCH 42/42] Change line endings --- .../GameEngine/Include/Common/DisabledTypes.h | 244 +- .../GameEngine/Include/Common/GlobalData.h | 1164 +- .../Code/GameEngine/Include/Common/INI.h | 858 +- .../Include/GameClient/TintStatus.h | 104 +- .../GameEngine/Include/GameLogic/Damage.h | 800 +- .../GameLogic/Module/RadiusDecalBehavior.h | 252 +- .../GameLogic/Module/UpgradeSpecialPower.h | 156 +- .../GameEngine/Include/GameLogic/Object.h | 1674 +- .../GameEngine/Source/Common/GlobalData.cpp | 2704 ++-- .../Code/GameEngine/Source/Common/INI/INI.cpp | 4232 ++--- .../Source/Common/System/DisabledTypes.cpp | 126 +- .../Source/Common/System/MemoryInit.cpp | 1636 +- .../Source/Common/Thing/ModuleFactory.cpp | 1502 +- .../Source/GameLogic/Object/Armor.cpp | 384 +- .../Behavior/DelayedUpgradeBehavior.cpp | 496 +- .../GameLogic/Object/Body/ActiveBody.cpp | 3732 ++--- .../Source/GameLogic/Object/Locomotor.cpp | 5688 +++---- .../Source/GameLogic/Object/Object.cpp | 13068 ++++++++-------- .../SpecialPower/UpgradeSpecialPower.cpp | 350 +- .../Update/AIUpdate/TeleporterAIUpdate.cpp | 1250 +- .../Object/Update/RadiusDecalBehavior.cpp | 396 +- .../Object/Upgrade/LocomotorSetUpgrade.cpp | 292 +- .../Source/GameLogic/System/Damage.cpp | 424 +- 23 files changed, 20766 insertions(+), 20766 deletions(-) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/DisabledTypes.h b/GeneralsMD/Code/GameEngine/Include/Common/DisabledTypes.h index 250109c782e..26c1b288141 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/DisabledTypes.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/DisabledTypes.h @@ -1,122 +1,122 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: DisabledTypes.h ////////////////////////////////////////////////////////////////////////// -// Author: Kris Morness, September 2002 -// Desc: Defines all the types of disabled statii any given object can have. -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __DISABLED_TYPES_H_ -#define __DISABLED_TYPES_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "Lib/BaseType.h" -#include "Common/BitFlags.h" -#include "Common/BitFlagsIO.h" - -//------------------------------------------------------------------------------------------------- -/** Kind of flags for determining groups of things that belong together - * NOTE: You *MUST* keep this in the same order as the DisabledNames[] below */ -//------------------------------------------------------------------------------------------------- -enum DisabledType CPP_11(: Int) -{ - DISABLED_DEFAULT, //Typical disable -- like systems, things that don't need to run. - DISABLED_HACKED, //This unit has been hacked - DISABLED_EMP, //This unit has been disabled via electro-magnetic-pulse. - DISABLED_HELD, //Special case -- held means it can fire and isHeld checks to make sure ONLY held is set! - DISABLED_PARALYZED, //Battle plans have changed, and unit is confused/paralyzed - DISABLED_UNMANNED, //Vehicle is unmanned - DISABLED_UNDERPOWERED,//Seperate from ScriptUnderpowered, the owning player has insufficient power. Energy status controls this - DISABLED_FREEFALL, //This unit has been disabled via being in free fall - - DISABLED_AWESTRUCK, - DISABLED_BRAINWASHED, - DISABLED_SUBDUED, ///< Temporarily shut down by Subdual damage - //These ones are specificially for scripts to enable/reenable! - DISABLED_SCRIPT_DISABLED, - DISABLED_SCRIPT_UNDERPOWERED, - - DISABLED_TELEPORT, // Chrono Legionnaire after teleporting - DISABLED_CHRONO, // Chrono Gun removal - - DISABLED_COUNT, - - DISABLED_ANY = 65535 ///< Do not use this value for setting disabled types (read-only) -}; - -typedef BitFlags DisabledMaskType; - -#define MAKE_DISABLED_MASK(k) DisabledMaskType(DisabledMaskType::kInit, (k)) -#define MAKE_DISABLED_MASK2(k,a) DisabledMaskType(DisabledMaskType::kInit, (k), (a)) -#define MAKE_DISABLED_MASK3(k,a,b) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b)) -#define MAKE_DISABLED_MASK4(k,a,b,c) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b), (c)) -#define MAKE_DISABLED_MASK5(k,a,b,c,d) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b), (c), (d)) - -inline Bool TEST_DISABLEDMASK(const DisabledMaskType& m, DisabledType t) -{ - return m.test(t); -} - -inline Bool TEST_DISABLEDMASK_ANY(const DisabledMaskType& m, const DisabledMaskType& mask) -{ - return m.anyIntersectionWith(mask); -} - -inline Bool TEST_DISABLEDMASK_MULTI(const DisabledMaskType& m, const DisabledMaskType& mustBeSet, const DisabledMaskType& mustBeClear) -{ - return m.testSetAndClear(mustBeSet, mustBeClear); -} - -inline Bool DISABLEDMASK_ANY_SET(const DisabledMaskType& m) -{ - return m.any(); -} - -inline void CLEAR_DISABLEDMASK(DisabledMaskType& m) -{ - m.clear(); -} - -inline void SET_ALL_DISABLEDMASK_BITS(DisabledMaskType& m) -{ - m.clear(); - m.flip(); -} - -inline void FLIP_DISABLEDMASK(DisabledMaskType& m) -{ - m.flip(); -} - - - -// defined in Common/System/DisabledTypes.cpp -extern DisabledMaskType DISABLEDMASK_NONE; // inits to all zeroes -extern DisabledMaskType DISABLEDMASK_ALL; // inits to all bits set. -void initDisabledMasks(); - -#endif // __DISABLED_TYPES_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: DisabledTypes.h ////////////////////////////////////////////////////////////////////////// +// Author: Kris Morness, September 2002 +// Desc: Defines all the types of disabled statii any given object can have. +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __DISABLED_TYPES_H_ +#define __DISABLED_TYPES_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Lib/BaseType.h" +#include "Common/BitFlags.h" +#include "Common/BitFlagsIO.h" + +//------------------------------------------------------------------------------------------------- +/** Kind of flags for determining groups of things that belong together + * NOTE: You *MUST* keep this in the same order as the DisabledNames[] below */ +//------------------------------------------------------------------------------------------------- +enum DisabledType CPP_11(: Int) +{ + DISABLED_DEFAULT, //Typical disable -- like systems, things that don't need to run. + DISABLED_HACKED, //This unit has been hacked + DISABLED_EMP, //This unit has been disabled via electro-magnetic-pulse. + DISABLED_HELD, //Special case -- held means it can fire and isHeld checks to make sure ONLY held is set! + DISABLED_PARALYZED, //Battle plans have changed, and unit is confused/paralyzed + DISABLED_UNMANNED, //Vehicle is unmanned + DISABLED_UNDERPOWERED,//Seperate from ScriptUnderpowered, the owning player has insufficient power. Energy status controls this + DISABLED_FREEFALL, //This unit has been disabled via being in free fall + + DISABLED_AWESTRUCK, + DISABLED_BRAINWASHED, + DISABLED_SUBDUED, ///< Temporarily shut down by Subdual damage + //These ones are specificially for scripts to enable/reenable! + DISABLED_SCRIPT_DISABLED, + DISABLED_SCRIPT_UNDERPOWERED, + + DISABLED_TELEPORT, // Chrono Legionnaire after teleporting + DISABLED_CHRONO, // Chrono Gun removal + + DISABLED_COUNT, + + DISABLED_ANY = 65535 ///< Do not use this value for setting disabled types (read-only) +}; + +typedef BitFlags DisabledMaskType; + +#define MAKE_DISABLED_MASK(k) DisabledMaskType(DisabledMaskType::kInit, (k)) +#define MAKE_DISABLED_MASK2(k,a) DisabledMaskType(DisabledMaskType::kInit, (k), (a)) +#define MAKE_DISABLED_MASK3(k,a,b) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b)) +#define MAKE_DISABLED_MASK4(k,a,b,c) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b), (c)) +#define MAKE_DISABLED_MASK5(k,a,b,c,d) DisabledMaskType(DisabledMaskType::kInit, (k), (a), (b), (c), (d)) + +inline Bool TEST_DISABLEDMASK(const DisabledMaskType& m, DisabledType t) +{ + return m.test(t); +} + +inline Bool TEST_DISABLEDMASK_ANY(const DisabledMaskType& m, const DisabledMaskType& mask) +{ + return m.anyIntersectionWith(mask); +} + +inline Bool TEST_DISABLEDMASK_MULTI(const DisabledMaskType& m, const DisabledMaskType& mustBeSet, const DisabledMaskType& mustBeClear) +{ + return m.testSetAndClear(mustBeSet, mustBeClear); +} + +inline Bool DISABLEDMASK_ANY_SET(const DisabledMaskType& m) +{ + return m.any(); +} + +inline void CLEAR_DISABLEDMASK(DisabledMaskType& m) +{ + m.clear(); +} + +inline void SET_ALL_DISABLEDMASK_BITS(DisabledMaskType& m) +{ + m.clear(); + m.flip(); +} + +inline void FLIP_DISABLEDMASK(DisabledMaskType& m) +{ + m.flip(); +} + + + +// defined in Common/System/DisabledTypes.cpp +extern DisabledMaskType DISABLEDMASK_NONE; // inits to all zeroes +extern DisabledMaskType DISABLEDMASK_ALL; // inits to all bits set. +void initDisabledMasks(); + +#endif // __DISABLED_TYPES_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h index 411779d9035..2866f604b16 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h @@ -1,582 +1,582 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: GlobalData.h ///////////////////////////////////////////////////////////////////////////// -// Global data used by both the client and logic -// Author: trolfs, Michae Booth, Colin Day, April 2001 -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef _GLOBALDATA_H_ -#define _GLOBALDATA_H_ - -#include "Common/GameCommon.h" // ensure we get DUMP_PERF_STATS, or not -#include "Common/AsciiString.h" -#include "Common/GameType.h" -#include "Common/GameMemory.h" -#include "Common/SubsystemInterface.h" -#include "GameClient/Color.h" -#include "GameClient/TintStatus.h" -#include "Common/STLTypedefs.h" -#include "Common/GameCommon.h" -#include "Common/Money.h" - -// FORWARD DECLARATIONS /////////////////////////////////////////////////////////////////////////// -struct FieldParse; -enum _TerrainLOD CPP_11(: Int); -class GlobalData; -class INI; -class WeaponBonusSet; -enum BodyDamageType CPP_11(: Int); -enum AIDebugOptions CPP_11(: Int); -typedef UnsignedInt DeathTypeFlags; -//enum DrawableColorTint CPP_11(: Int); - -// PUBLIC ///////////////////////////////////////////////////////////////////////////////////////// - -const Int MAX_GLOBAL_LIGHTS = 3; - -//------------------------------------------------------------------------------------------------- -/** Global data container class - * Defines all global game data used by the system - * @todo Change this entire system. Otherwise this will end up a huge class containing tons of variables, - * and will cause re-compilation dependancies throughout the codebase. - * OOPS -- TOO LATE! :) */ -//------------------------------------------------------------------------------------------------- -class GlobalData : public SubsystemInterface -{ - -public: - - GlobalData(); - virtual ~GlobalData(); - - void init(); - void reset(); - void update() { } - - Bool setTimeOfDay( TimeOfDay tod ); ///< Use this function to set the Time of day; - - static void parseGameDataDefinition( INI* ini ); - - //----------------------------------------------------------------------------------------------- - struct TerrainLighting - { - RGBColor ambient; - RGBColor diffuse; - Coord3D lightPos; - }; - - //----------------------------------------------------------------------------------------------- - //----------------------------------------------------------------------------------------------- - //----------------------------------------------------------------------------------------------- - - AsciiString m_mapName; ///< hack for now, this whole this is going away - AsciiString m_moveHintName; - Bool m_useTrees; - Bool m_useTreeSway; - Bool m_useDrawModuleLOD; - Bool m_useHeatEffects; - Bool m_useFpsLimit; - Bool m_dumpAssetUsage; - Int m_framesPerSecondLimit; - Int m_chipSetType; /// m_standardPublicBones; - - Real m_standardMinefieldDensity; - Real m_standardMinefieldDistance; - - - Bool m_showMetrics; ///< whether or not to show the metrics. - Money m_defaultStartingCash; ///< The amount of cash a player starts with by default. - - Bool m_debugShowGraphicalFramerate; ///< Whether or not to show the graphical framerate bar. - - Int m_powerBarBase; ///< Logrithmic base for the power bar scale - Real m_powerBarIntervals; ///< how many logrithmic intervals the width will be divided into - Int m_powerBarYellowRange; ///< Red if consumption exceeds production, yellow if consumption this close but under, green if further under - Real m_displayGamma; ///. +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: GlobalData.h ///////////////////////////////////////////////////////////////////////////// +// Global data used by both the client and logic +// Author: trolfs, Michae Booth, Colin Day, April 2001 +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef _GLOBALDATA_H_ +#define _GLOBALDATA_H_ + +#include "Common/GameCommon.h" // ensure we get DUMP_PERF_STATS, or not +#include "Common/AsciiString.h" +#include "Common/GameType.h" +#include "Common/GameMemory.h" +#include "Common/SubsystemInterface.h" +#include "GameClient/Color.h" +#include "GameClient/TintStatus.h" +#include "Common/STLTypedefs.h" +#include "Common/GameCommon.h" +#include "Common/Money.h" + +// FORWARD DECLARATIONS /////////////////////////////////////////////////////////////////////////// +struct FieldParse; +enum _TerrainLOD CPP_11(: Int); +class GlobalData; +class INI; +class WeaponBonusSet; +enum BodyDamageType CPP_11(: Int); +enum AIDebugOptions CPP_11(: Int); +typedef UnsignedInt DeathTypeFlags; +//enum DrawableColorTint CPP_11(: Int); + +// PUBLIC ///////////////////////////////////////////////////////////////////////////////////////// + +const Int MAX_GLOBAL_LIGHTS = 3; + +//------------------------------------------------------------------------------------------------- +/** Global data container class + * Defines all global game data used by the system + * @todo Change this entire system. Otherwise this will end up a huge class containing tons of variables, + * and will cause re-compilation dependancies throughout the codebase. + * OOPS -- TOO LATE! :) */ +//------------------------------------------------------------------------------------------------- +class GlobalData : public SubsystemInterface +{ + +public: + + GlobalData(); + virtual ~GlobalData(); + + void init(); + void reset(); + void update() { } + + Bool setTimeOfDay( TimeOfDay tod ); ///< Use this function to set the Time of day; + + static void parseGameDataDefinition( INI* ini ); + + //----------------------------------------------------------------------------------------------- + struct TerrainLighting + { + RGBColor ambient; + RGBColor diffuse; + Coord3D lightPos; + }; + + //----------------------------------------------------------------------------------------------- + //----------------------------------------------------------------------------------------------- + //----------------------------------------------------------------------------------------------- + + AsciiString m_mapName; ///< hack for now, this whole this is going away + AsciiString m_moveHintName; + Bool m_useTrees; + Bool m_useTreeSway; + Bool m_useDrawModuleLOD; + Bool m_useHeatEffects; + Bool m_useFpsLimit; + Bool m_dumpAssetUsage; + Int m_framesPerSecondLimit; + Int m_chipSetType; /// m_standardPublicBones; + + Real m_standardMinefieldDensity; + Real m_standardMinefieldDistance; + + + Bool m_showMetrics; ///< whether or not to show the metrics. + Money m_defaultStartingCash; ///< The amount of cash a player starts with by default. + + Bool m_debugShowGraphicalFramerate; ///< Whether or not to show the graphical framerate bar. + + Int m_powerBarBase; ///< Logrithmic base for the power bar scale + Real m_powerBarIntervals; ///< how many logrithmic intervals the width will be divided into + Int m_powerBarYellowRange; ///< Red if consumption exceeds production, yellow if consumption this close but under, green if further under + Real m_displayGamma; ///. -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: INI.h //////////////////////////////////////////////////////////////////////////////////// -// Author: Colin Day, November 2001 -// Desc: INI Reader -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __INI_H_ -#define __INI_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include // for offsetof, which we don't use but everyone who includes us does -#include "Common/STLTypedefs.h" -#include "Common/AsciiString.h" -#include "Common/GameCommon.h" - -//------------------------------------------------------------------------------------------------- -class INI; -class Xfer; -class File; -enum ScienceType CPP_11(: Int); - -//------------------------------------------------------------------------------------------------- -/** These control the behavior of loading the INI data into items */ -//------------------------------------------------------------------------------------------------- -enum INILoadType CPP_11(: Int) -{ - INI_LOAD_INVALID, ///< invalid load type - INI_LOAD_OVERWRITE, ///< create new or load *over* existing data instance - INI_LOAD_CREATE_OVERRIDES, ///< create new or load into *new* override data instance - INI_LOAD_MULTIFILE ///< create new or continue loading into existing data instance. -}; - -//------------------------------------------------------------------------------------------------- -/** INI constant defines */ -//------------------------------------------------------------------------------------------------- -enum -{ - INI_MAX_CHARS_PER_LINE = 1028, ///< max characters per line entry in any ini file -}; - -//------------------------------------------------------------------------------------------------- -/** Status return codes for the INI reader */ -//------------------------------------------------------------------------------------------------- -enum -{ - // we map all of these to the same "real" error code, because - // we generally don't care why it failed; but since the code distinguishes, - // I didn't want to wipe out that intelligence. if we ever need to distinguish - // failure modes at runtime, just put in real values for these. - INI_CANT_SEARCH_DIR = ERROR_BAD_INI, - INI_INVALID_DIRECTORY = ERROR_BAD_INI, - INI_INVALID_PARAMS = ERROR_BAD_INI, - INI_INVALID_NAME_LIST = ERROR_BAD_INI, - INI_INVALID_DATA = ERROR_BAD_INI, - INI_MISSING_END_TOKEN = ERROR_BAD_INI, - INI_UNKNOWN_TOKEN = ERROR_BAD_INI, - INI_BUFFER_TOO_SMALL = ERROR_BAD_INI, - INI_FILE_NOT_OPEN = ERROR_BAD_INI, - INI_FILE_ALREADY_OPEN = ERROR_BAD_INI, - INI_CANT_OPEN_FILE = ERROR_BAD_INI, - INI_UNKNOWN_ERROR = ERROR_BAD_INI, - INI_END_OF_FILE = ERROR_BAD_INI -}; - -//------------------------------------------------------------------------------------------------- -/** Function typedef for parsing data block fields. - * - * buffer - the character buffer of the line from INI that we are reading and parsing - * instance - instance of what we're loading (for example a thingtemplate instance) - * store - where to store the data parsed, this is a field in the *instance* 'instance' - */ -//------------------------------------------------------------------------------------------------- -typedef void (*INIFieldParseProc)( INI *ini, void *instance, void *store, const void* userData ); - -//------------------------------------------------------------------------------------------------- -typedef const char* ConstCharPtr; -typedef ConstCharPtr* ConstCharPtrArray; - -//------------------------------------------------------------------------------------------------- -struct LookupListRec -{ - const char* name; - Int value; -}; -typedef const LookupListRec *ConstLookupListRecArray; - -//------------------------------------------------------------------------------------------------- -/** Parse tables for all fields of each data block are created using these */ -//------------------------------------------------------------------------------------------------- -struct FieldParse -{ - const char* token; ///< token of the field - INIFieldParseProc parse; ///< the parse function - const void* userData; ///< field-specific data - Int offset; ///< offset to data field - - inline void set(const char* t, INIFieldParseProc p, const void* u, Int o) - { - token = t; - parse = p; - userData = u; - offset = o; - } -}; - -//------------------------------------------------------------------------------------------------- -class MultiIniFieldParse -{ -private: - enum { MAX_MULTI_FIELDS = 16 }; - - const FieldParse* m_fieldParse[MAX_MULTI_FIELDS]; - UnsignedInt m_extraOffset[MAX_MULTI_FIELDS]; - Int m_count; - -public: - MultiIniFieldParse() : m_count(0) - { - //Added By Sadullah Nader - //Initializations missing and needed - for(Int i = 0; i < MAX_MULTI_FIELDS; i++) - m_extraOffset[i] = 0; - // - - } - - void add(const FieldParse* f, UnsignedInt e = 0); - - inline Int getCount() const { return m_count; } - inline const FieldParse* getNthFieldParse(Int i) const { return m_fieldParse[i]; } - inline UnsignedInt getNthExtraOffset(Int i) const { return m_extraOffset[i]; } -}; - -//------------------------------------------------------------------------------------------------- -/** Function typedef for parsing INI types blocks */ -//------------------------------------------------------------------------------------------------- -typedef void (*INIBlockParse)( INI *ini ); -typedef void (*BuildMultiIniFieldProc)(MultiIniFieldParse& p); - -//------------------------------------------------------------------------------------------------- -/** INI Reader interface */ -//------------------------------------------------------------------------------------------------- -class INI -{ - INI(const INI&); - INI& operator=(const INI&); - -public: - - INI(); - ~INI(); - - void loadDirectory( AsciiString dirName, Bool subdirs, INILoadType loadType, Xfer *pXfer ); ///< load directory of INI files - void load( AsciiString filename, INILoadType loadType, Xfer *pXfer ); ///< load INI file - - static Bool isDeclarationOfType( AsciiString blockType, AsciiString blockName, char *bufferToCheck ); - static Bool isEndOfBlock( char *bufferToCheck ); - - // data type parsing (the highest level of what type of thing we're parsing) - static void parseObjectDefinition( INI *ini ); - static void parseObjectReskinDefinition( INI *ini ); - static void parseObjectExtendDefinition( INI* ini ); - static void parseWeaponTemplateDefinition( INI *ini ); - static void parseScienceDefinition( INI *ini ); - static void parseRankDefinition( INI *ini ); - static void parseCrateTemplateDefinition( INI *ini ); - static void parseLocomotorTemplateDefinition( INI *ini ); - static void parseLanguageDefinition( INI *ini ); - static void parsePlayerTemplateDefinition( INI *ini ); - static void parseGameDataDefinition( INI *ini ); - static void parseMapDataDefinition( INI *ini ); - static void parseAnim2DDefinition( INI *ini ); - static void parseAudioEventDefinition( INI *ini ); - static void parseDialogDefinition( INI *ini ); - static void parseMusicTrackDefinition( INI *ini ); - static void parseWebpageURLDefinition( INI *ini ); - static void parseHeaderTemplateDefinition( INI *ini ); - static void parseParticleSystemDefinition( INI *ini ); - static void parseWaterSettingDefinition( INI *ini ); - static void parseWaterTransparencyDefinition( INI *ini ); - static void parseWeatherDefinition( INI *ini ); - static void parseMappedImageDefinition( INI *ini ); - static void parseArmorDefinition( INI *ini ); - static void parseArmorExtendDefinition( INI *ini ); - static void parseDamageFXDefinition( INI *ini ); - static void parseDrawGroupNumberDefinition( INI *ini ); - static void parseTerrainDefinition( INI *ini ); - static void parseTerrainRoadDefinition( INI *ini ); - static void parseTerrainBridgeDefinition( INI *ini ); - static void parseMetaMapDefinition( INI *ini ); - static void parseFXListDefinition( INI *ini ); - static void parseObjectCreationListDefinition( INI* ini ); - static void parseMultiplayerSettingsDefinition( INI* ini ); - static void parseMultiplayerColorDefinition( INI* ini ); - static void parseMultiplayerStartingMoneyChoiceDefinition( INI* ini ); - static void parseOnlineChatColorDefinition( INI* ini ); - static void parseMapCacheDefinition( INI* ini ); - static void parseVideoDefinition( INI* ini ); - static void parseCommandButtonDefinition( INI *ini ); - static void parseCommandSetDefinition( INI *ini ); - static void parseUpgradeDefinition( INI *ini ); - static void parseMouseDefinition( INI* ini ); - static void parseMouseCursorDefinition( INI* ini ); - static void parseAIDataDefinition( INI *ini ); - static void parseSpecialPowerDefinition( INI *ini ); - static void parseInGameUIDefinition( INI *ini ); - static void parseControlBarSchemeDefinition( INI *ini ); - static void parseControlBarResizerDefinition( INI *ini ); - static void parseShellMenuSchemeDefinition( INI *ini ); - static void parseCampaignDefinition( INI *ini ); - static void parseAudioSettingsDefinition( INI *ini ); - static void parseMiscAudio( INI *ini ); - static void parseStaticGameLODDefinition( INI *ini); - static void parseDynamicGameLODDefinition( INI *ini); - static void parseStaticGameLODLevel( INI* ini, void * , void *store, const void*); - static void parseDynamicGameLODLevel( INI* ini, void * , void *store, const void*); - static void parseLODPreset( INI* ini); - static void parseBenchProfile( INI* ini); - static void parseEvaEvent( INI* ini ); - static void parseCredits( INI* ini ); - static void parseWindowTransitions( INI* ini ); - static void parseChallengeModeDefinition( INI* ini ); - - inline AsciiString getFilename( void ) const { return m_filename; } - inline INILoadType getLoadType( void ) const { return m_loadType; } - inline UnsignedInt getLineNum( void ) const { return m_lineNum; } - inline const char *getSeps( void ) const { return m_seps; } - inline const char *getSepsPercent( void ) const { return m_sepsPercent; } - inline const char *getSepsColon( void ) const { return m_sepsColon; } - inline const char *getSepsQuote( void ) { return m_sepsQuote; } - inline Bool isEOF( void ) const { return m_endOfFile; } - - void initFromINI( void *what, const FieldParse* parseTable ); - void initFromINIMulti( void *what, const MultiIniFieldParse& parseTableList ); - void initFromINIMultiProc( void *what, BuildMultiIniFieldProc proc ); - - static void parseUnsignedByte( INI *ini, void *instance, void *store, const void* userData ); - static void parseShort( INI *ini, void *instance, void *store, const void* userData ); - static void parseUnsignedShort( INI *ini, void *instance, void *store, const void* userData ); - static void parseInt( INI *ini, void *instance, void *store, const void* userData ); - static void parseUnsignedInt( INI *ini, void *instance, void *store, const void* userData ); - static void parseReal( INI *ini, void *instance, void *store, const void* userData ); - static void parsePositiveNonZeroReal( INI *ini, void *instance, void *store, const void* userData ); - static void parseBool( INI *ini, void *instance, void *store, const void* userData ); - static void parseBitInInt32( INI *ini, void *instance, void *store, const void* userData ); - static void parseAsciiString( INI *ini, void *instance, void *store, const void* userData ); - static void parseQuotedAsciiString( INI *ini, void *instance, void *store, const void* userData ); - static void parseAsciiStringVector( INI *ini, void *instance, void *store, const void* userData ); - static void parseAsciiStringVectorAppend( INI *ini, void *instance, void *store, const void* userData ); - static void parseAndTranslateLabel( INI *ini, void *instance, void *store, const void* userData ); - static void parseMappedImage( INI *ini, void *instance, void *store, const void *userData ); - static void parseAnim2DTemplate( INI *ini, void *instance, void *store, const void *userData ); - static void parsePercentToReal( INI *ini, void *instance, void *store, const void* userData ); - static void parseRGBColor( INI *ini, void *instance, void *store, const void* userData ); - static void parseRGBColorReal( INI *ini, void *instance, void *store, const void* userData ); - static void parseRGBAColorInt( INI *ini, void *instance, void *store, const void* userData ); - static void parseColorInt( INI *ini, void *instance, void *store, const void* userData ); - static void parseCoord3D( INI *ini, void *instance, void *store, const void* userData ); - static void parseCoord2D( INI *ini, void *instance, void *store, const void *userData ); - static void parseICoord2D( INI *ini, void *instance, void *store, const void *userData ); - static void parseDynamicAudioEventRTS( INI *ini, void *instance, void *store, const void* userData ); - static void parseAudioEventRTS( INI *ini, void *instance, void *store, const void* userData ); - static void parseFXList( INI *ini, void *instance, void *store, const void* userData ); - static void parseParticleSystemTemplate( INI *ini, void *instance, void *store, const void *userData ); - static void parseObjectCreationList( INI *ini, void *instance, void *store, const void* userData ); - static void parseSpecialPowerTemplate( INI *ini, void *instance, void *store, const void *userData ); - static void parseUpgradeTemplate( INI *ini, void *instance, void *store, const void *userData ); - static void parseScience( INI *ini, void *instance, void *store, const void *userData ); - static void parseScienceVector( INI *ini, void *instance, void *store, const void *userData ); - static void parseWeaponBonusVector( INI *ini, void *instance, void *store, const void *userData ); - static void parseWeaponBonusVectorKeepDefault( INI *ini, void *instance, void *store, const void *userData ); - static void parseGameClientRandomVariable( INI* ini, void *instance, void *store, const void* userData ); - static void parseBitString8( INI *ini, void *instance, void *store, const void* userData ); - static void parseBitString32( INI *ini, void *instance, void *store, const void* userData ); - static void parseByteSizedIndexList( INI *ini, void *instance, void *store, const void* userData ); - static void parseIndexList( INI *ini, void *instance, void *store, const void* userData ); - static void parseIndexListOrNone( INI *ini, void *instance, void *store, const void* userData ); - static void parseLookupList( INI *ini, void *instance, void *store, const void* userData ); - static void parseThingTemplate( INI *ini, void *instance, void *store, const void* userData ); - static void parseArmorTemplate( INI *ini, void *instance, void *store, const void* userData ); - static void parseDamageFX( INI *ini, void *instance, void *store, const void* userData ); - static void parseWeaponTemplate( INI *ini, void *instance, void *store, const void* userData ); - // parse a duration in msec and convert to duration in frames - static void parseDurationReal( INI *ini, void *instance, void *store, const void* userData ); - // parse a duration in msec and convert to duration in integral number of frames, (unsignedint) rounding UP - static void parseDurationUnsignedInt( INI *ini, void *instance, void *store, const void* userData ); - static void parseDurationUnsignedShort( INI *ini, void *instance, void *store, const void *userData ); - // parse acceleration in (dist/sec) and convert to (dist/frame) - static void parseVelocityReal( INI *ini, void *instance, void *store, const void* userData ); - // parse acceleration in (dist/sec^2) and convert to (dist/frame^2) - static void parseAccelerationReal( INI *ini, void *instance, void *store, const void* userData ); - // parse angle in degrees and convert to radians - static void parseAngleReal( INI *ini, void *instance, void *store, const void *userData ); - // note that this parses in degrees/sec, and converts to rads/frame! - static void parseAngularVelocityReal( INI *ini, void *instance, void *store, const void *userData ); - static void parseDamageTypeFlags(INI* ini, void* instance, void* store, const void* userData); - static void parseDeathTypeFlags(INI* ini, void* instance, void* store, const void* userData); - static void parseDeathTypeFlagsList(INI* ini, void* instance, void* store, const void* userData); - static void parseVeterancyLevelFlags(INI* ini, void* instance, void* store, const void* userData); - static void parseSoundsList( INI* ini, void *instance, void *store, const void* /*userData*/ ); - - // like parseIndexList but special handling for NONE to return -2 (EVA_None) - static void parseEvaNameIndexList(INI* ini, void* instance, void* store, const void* userData); - - /** - return the next token. if seps is null (or omitted), the standard seps are used. - - this will *never* return null; if there are no more tokens, an exception will be thrown. - */ - const char* getNextToken(const char* seps = NULL); - - /** - just like getNextToken(), except that null is returned if no more tokens are present - (rather than throwing an exception). usually you should call getNextToken(), - but for some cases this is handier (ie, parsing a variable-length number of tokens). - */ - const char* getNextTokenOrNull(const char* seps = NULL); - - /** - This is called when the next thing you expect is something like: - - Tag:value - - pass "Tag" (without the colon) for 'expected', and you will have the 'value' - token returned. - - If "Tag" is not the next token, an error is thrown. - */ - const char* getNextSubToken(const char* expected); - - /** - return the next ascii string. this is usually the same the result of getNextToken(), - except that it allows for quote-delimited strings (eg, "foo bar"), so you can - get strings with spaces, and/or empty strings. - */ - AsciiString getNextAsciiString(); - AsciiString getNextQuotedAsciiString(); //fixed version of above. We can't fix the regular one for fear of breaking existing code. :-( - - /** - utility routine that does a sscanf() on the string to get the Science, and throws - an exception if not of the right form. - */ - static ScienceType scanScience(const char* token); - - /** - utility routine that does a sscanf() on the string to get the int, and throws - an exception if not of the right form. - */ - static Int scanInt(const char* token); - - /** - utility routine that does a sscanf() on the string to get the unsigned int, and throws - an exception if not of the right form. - */ - static UnsignedInt scanUnsignedInt(const char* token); - - /** - utility routine that does a sscanf() on the string to get the real, and throws - an exception if not of the right form. - */ - static Real scanReal(const char* token); - static Real scanPercentToReal(const char* token); - - static Int scanIndexList(const char* token, ConstCharPtrArray nameList); - static Int scanLookupList(const char* token, ConstLookupListRecArray lookupList); - - static Bool scanBool(const char* token); - -protected: - - static Bool isValidINIFilename( const char *filename ); ///< is this a valid .ini filename - - void prepFile( AsciiString filename, INILoadType loadType ); - void unPrepFile(); - - void readLine( void ); - - File *m_file; ///< file pointer of file currently loading - - enum - { - INI_READ_BUFFER = 8192 ///< size of internal read buffer - }; - char m_readBuffer[INI_READ_BUFFER]; ///< internal read buffer - unsigned m_readBufferNext; ///< next char in read buffer - unsigned m_readBufferUsed; ///< number of bytes in read buffer - - AsciiString m_filename; ///< filename of file currently loading - INILoadType m_loadType; ///< load time for current file - UnsignedInt m_lineNum; ///< current line number that's been read - char m_buffer[ INI_MAX_CHARS_PER_LINE+1 ];///< buffer to read file contents into - const char *m_seps; ///< for strtok parsing - const char *m_sepsPercent; ///< m_seps with percent delimiter as well - const char *m_sepsColon; ///< m_seps with colon delimiter as well - const char *m_sepsQuote; ///< token to represent a quoted ascii string - const char *m_blockEndToken; ///< token to represent end of data block - Bool m_endOfFile; ///< TRUE when we've hit EOF -#ifdef DEBUG_CRASHING - char m_curBlockStart[ INI_MAX_CHARS_PER_LINE ]; ///< first line of cur block -#endif -}; - -#endif // __INI_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: INI.h //////////////////////////////////////////////////////////////////////////////////// +// Author: Colin Day, November 2001 +// Desc: INI Reader +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __INI_H_ +#define __INI_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include // for offsetof, which we don't use but everyone who includes us does +#include "Common/STLTypedefs.h" +#include "Common/AsciiString.h" +#include "Common/GameCommon.h" + +//------------------------------------------------------------------------------------------------- +class INI; +class Xfer; +class File; +enum ScienceType CPP_11(: Int); + +//------------------------------------------------------------------------------------------------- +/** These control the behavior of loading the INI data into items */ +//------------------------------------------------------------------------------------------------- +enum INILoadType CPP_11(: Int) +{ + INI_LOAD_INVALID, ///< invalid load type + INI_LOAD_OVERWRITE, ///< create new or load *over* existing data instance + INI_LOAD_CREATE_OVERRIDES, ///< create new or load into *new* override data instance + INI_LOAD_MULTIFILE ///< create new or continue loading into existing data instance. +}; + +//------------------------------------------------------------------------------------------------- +/** INI constant defines */ +//------------------------------------------------------------------------------------------------- +enum +{ + INI_MAX_CHARS_PER_LINE = 1028, ///< max characters per line entry in any ini file +}; + +//------------------------------------------------------------------------------------------------- +/** Status return codes for the INI reader */ +//------------------------------------------------------------------------------------------------- +enum +{ + // we map all of these to the same "real" error code, because + // we generally don't care why it failed; but since the code distinguishes, + // I didn't want to wipe out that intelligence. if we ever need to distinguish + // failure modes at runtime, just put in real values for these. + INI_CANT_SEARCH_DIR = ERROR_BAD_INI, + INI_INVALID_DIRECTORY = ERROR_BAD_INI, + INI_INVALID_PARAMS = ERROR_BAD_INI, + INI_INVALID_NAME_LIST = ERROR_BAD_INI, + INI_INVALID_DATA = ERROR_BAD_INI, + INI_MISSING_END_TOKEN = ERROR_BAD_INI, + INI_UNKNOWN_TOKEN = ERROR_BAD_INI, + INI_BUFFER_TOO_SMALL = ERROR_BAD_INI, + INI_FILE_NOT_OPEN = ERROR_BAD_INI, + INI_FILE_ALREADY_OPEN = ERROR_BAD_INI, + INI_CANT_OPEN_FILE = ERROR_BAD_INI, + INI_UNKNOWN_ERROR = ERROR_BAD_INI, + INI_END_OF_FILE = ERROR_BAD_INI +}; + +//------------------------------------------------------------------------------------------------- +/** Function typedef for parsing data block fields. + * + * buffer - the character buffer of the line from INI that we are reading and parsing + * instance - instance of what we're loading (for example a thingtemplate instance) + * store - where to store the data parsed, this is a field in the *instance* 'instance' + */ +//------------------------------------------------------------------------------------------------- +typedef void (*INIFieldParseProc)( INI *ini, void *instance, void *store, const void* userData ); + +//------------------------------------------------------------------------------------------------- +typedef const char* ConstCharPtr; +typedef ConstCharPtr* ConstCharPtrArray; + +//------------------------------------------------------------------------------------------------- +struct LookupListRec +{ + const char* name; + Int value; +}; +typedef const LookupListRec *ConstLookupListRecArray; + +//------------------------------------------------------------------------------------------------- +/** Parse tables for all fields of each data block are created using these */ +//------------------------------------------------------------------------------------------------- +struct FieldParse +{ + const char* token; ///< token of the field + INIFieldParseProc parse; ///< the parse function + const void* userData; ///< field-specific data + Int offset; ///< offset to data field + + inline void set(const char* t, INIFieldParseProc p, const void* u, Int o) + { + token = t; + parse = p; + userData = u; + offset = o; + } +}; + +//------------------------------------------------------------------------------------------------- +class MultiIniFieldParse +{ +private: + enum { MAX_MULTI_FIELDS = 16 }; + + const FieldParse* m_fieldParse[MAX_MULTI_FIELDS]; + UnsignedInt m_extraOffset[MAX_MULTI_FIELDS]; + Int m_count; + +public: + MultiIniFieldParse() : m_count(0) + { + //Added By Sadullah Nader + //Initializations missing and needed + for(Int i = 0; i < MAX_MULTI_FIELDS; i++) + m_extraOffset[i] = 0; + // + + } + + void add(const FieldParse* f, UnsignedInt e = 0); + + inline Int getCount() const { return m_count; } + inline const FieldParse* getNthFieldParse(Int i) const { return m_fieldParse[i]; } + inline UnsignedInt getNthExtraOffset(Int i) const { return m_extraOffset[i]; } +}; + +//------------------------------------------------------------------------------------------------- +/** Function typedef for parsing INI types blocks */ +//------------------------------------------------------------------------------------------------- +typedef void (*INIBlockParse)( INI *ini ); +typedef void (*BuildMultiIniFieldProc)(MultiIniFieldParse& p); + +//------------------------------------------------------------------------------------------------- +/** INI Reader interface */ +//------------------------------------------------------------------------------------------------- +class INI +{ + INI(const INI&); + INI& operator=(const INI&); + +public: + + INI(); + ~INI(); + + void loadDirectory( AsciiString dirName, Bool subdirs, INILoadType loadType, Xfer *pXfer ); ///< load directory of INI files + void load( AsciiString filename, INILoadType loadType, Xfer *pXfer ); ///< load INI file + + static Bool isDeclarationOfType( AsciiString blockType, AsciiString blockName, char *bufferToCheck ); + static Bool isEndOfBlock( char *bufferToCheck ); + + // data type parsing (the highest level of what type of thing we're parsing) + static void parseObjectDefinition( INI *ini ); + static void parseObjectReskinDefinition( INI *ini ); + static void parseObjectExtendDefinition( INI* ini ); + static void parseWeaponTemplateDefinition( INI *ini ); + static void parseScienceDefinition( INI *ini ); + static void parseRankDefinition( INI *ini ); + static void parseCrateTemplateDefinition( INI *ini ); + static void parseLocomotorTemplateDefinition( INI *ini ); + static void parseLanguageDefinition( INI *ini ); + static void parsePlayerTemplateDefinition( INI *ini ); + static void parseGameDataDefinition( INI *ini ); + static void parseMapDataDefinition( INI *ini ); + static void parseAnim2DDefinition( INI *ini ); + static void parseAudioEventDefinition( INI *ini ); + static void parseDialogDefinition( INI *ini ); + static void parseMusicTrackDefinition( INI *ini ); + static void parseWebpageURLDefinition( INI *ini ); + static void parseHeaderTemplateDefinition( INI *ini ); + static void parseParticleSystemDefinition( INI *ini ); + static void parseWaterSettingDefinition( INI *ini ); + static void parseWaterTransparencyDefinition( INI *ini ); + static void parseWeatherDefinition( INI *ini ); + static void parseMappedImageDefinition( INI *ini ); + static void parseArmorDefinition( INI *ini ); + static void parseArmorExtendDefinition( INI *ini ); + static void parseDamageFXDefinition( INI *ini ); + static void parseDrawGroupNumberDefinition( INI *ini ); + static void parseTerrainDefinition( INI *ini ); + static void parseTerrainRoadDefinition( INI *ini ); + static void parseTerrainBridgeDefinition( INI *ini ); + static void parseMetaMapDefinition( INI *ini ); + static void parseFXListDefinition( INI *ini ); + static void parseObjectCreationListDefinition( INI* ini ); + static void parseMultiplayerSettingsDefinition( INI* ini ); + static void parseMultiplayerColorDefinition( INI* ini ); + static void parseMultiplayerStartingMoneyChoiceDefinition( INI* ini ); + static void parseOnlineChatColorDefinition( INI* ini ); + static void parseMapCacheDefinition( INI* ini ); + static void parseVideoDefinition( INI* ini ); + static void parseCommandButtonDefinition( INI *ini ); + static void parseCommandSetDefinition( INI *ini ); + static void parseUpgradeDefinition( INI *ini ); + static void parseMouseDefinition( INI* ini ); + static void parseMouseCursorDefinition( INI* ini ); + static void parseAIDataDefinition( INI *ini ); + static void parseSpecialPowerDefinition( INI *ini ); + static void parseInGameUIDefinition( INI *ini ); + static void parseControlBarSchemeDefinition( INI *ini ); + static void parseControlBarResizerDefinition( INI *ini ); + static void parseShellMenuSchemeDefinition( INI *ini ); + static void parseCampaignDefinition( INI *ini ); + static void parseAudioSettingsDefinition( INI *ini ); + static void parseMiscAudio( INI *ini ); + static void parseStaticGameLODDefinition( INI *ini); + static void parseDynamicGameLODDefinition( INI *ini); + static void parseStaticGameLODLevel( INI* ini, void * , void *store, const void*); + static void parseDynamicGameLODLevel( INI* ini, void * , void *store, const void*); + static void parseLODPreset( INI* ini); + static void parseBenchProfile( INI* ini); + static void parseEvaEvent( INI* ini ); + static void parseCredits( INI* ini ); + static void parseWindowTransitions( INI* ini ); + static void parseChallengeModeDefinition( INI* ini ); + + inline AsciiString getFilename( void ) const { return m_filename; } + inline INILoadType getLoadType( void ) const { return m_loadType; } + inline UnsignedInt getLineNum( void ) const { return m_lineNum; } + inline const char *getSeps( void ) const { return m_seps; } + inline const char *getSepsPercent( void ) const { return m_sepsPercent; } + inline const char *getSepsColon( void ) const { return m_sepsColon; } + inline const char *getSepsQuote( void ) { return m_sepsQuote; } + inline Bool isEOF( void ) const { return m_endOfFile; } + + void initFromINI( void *what, const FieldParse* parseTable ); + void initFromINIMulti( void *what, const MultiIniFieldParse& parseTableList ); + void initFromINIMultiProc( void *what, BuildMultiIniFieldProc proc ); + + static void parseUnsignedByte( INI *ini, void *instance, void *store, const void* userData ); + static void parseShort( INI *ini, void *instance, void *store, const void* userData ); + static void parseUnsignedShort( INI *ini, void *instance, void *store, const void* userData ); + static void parseInt( INI *ini, void *instance, void *store, const void* userData ); + static void parseUnsignedInt( INI *ini, void *instance, void *store, const void* userData ); + static void parseReal( INI *ini, void *instance, void *store, const void* userData ); + static void parsePositiveNonZeroReal( INI *ini, void *instance, void *store, const void* userData ); + static void parseBool( INI *ini, void *instance, void *store, const void* userData ); + static void parseBitInInt32( INI *ini, void *instance, void *store, const void* userData ); + static void parseAsciiString( INI *ini, void *instance, void *store, const void* userData ); + static void parseQuotedAsciiString( INI *ini, void *instance, void *store, const void* userData ); + static void parseAsciiStringVector( INI *ini, void *instance, void *store, const void* userData ); + static void parseAsciiStringVectorAppend( INI *ini, void *instance, void *store, const void* userData ); + static void parseAndTranslateLabel( INI *ini, void *instance, void *store, const void* userData ); + static void parseMappedImage( INI *ini, void *instance, void *store, const void *userData ); + static void parseAnim2DTemplate( INI *ini, void *instance, void *store, const void *userData ); + static void parsePercentToReal( INI *ini, void *instance, void *store, const void* userData ); + static void parseRGBColor( INI *ini, void *instance, void *store, const void* userData ); + static void parseRGBColorReal( INI *ini, void *instance, void *store, const void* userData ); + static void parseRGBAColorInt( INI *ini, void *instance, void *store, const void* userData ); + static void parseColorInt( INI *ini, void *instance, void *store, const void* userData ); + static void parseCoord3D( INI *ini, void *instance, void *store, const void* userData ); + static void parseCoord2D( INI *ini, void *instance, void *store, const void *userData ); + static void parseICoord2D( INI *ini, void *instance, void *store, const void *userData ); + static void parseDynamicAudioEventRTS( INI *ini, void *instance, void *store, const void* userData ); + static void parseAudioEventRTS( INI *ini, void *instance, void *store, const void* userData ); + static void parseFXList( INI *ini, void *instance, void *store, const void* userData ); + static void parseParticleSystemTemplate( INI *ini, void *instance, void *store, const void *userData ); + static void parseObjectCreationList( INI *ini, void *instance, void *store, const void* userData ); + static void parseSpecialPowerTemplate( INI *ini, void *instance, void *store, const void *userData ); + static void parseUpgradeTemplate( INI *ini, void *instance, void *store, const void *userData ); + static void parseScience( INI *ini, void *instance, void *store, const void *userData ); + static void parseScienceVector( INI *ini, void *instance, void *store, const void *userData ); + static void parseWeaponBonusVector( INI *ini, void *instance, void *store, const void *userData ); + static void parseWeaponBonusVectorKeepDefault( INI *ini, void *instance, void *store, const void *userData ); + static void parseGameClientRandomVariable( INI* ini, void *instance, void *store, const void* userData ); + static void parseBitString8( INI *ini, void *instance, void *store, const void* userData ); + static void parseBitString32( INI *ini, void *instance, void *store, const void* userData ); + static void parseByteSizedIndexList( INI *ini, void *instance, void *store, const void* userData ); + static void parseIndexList( INI *ini, void *instance, void *store, const void* userData ); + static void parseIndexListOrNone( INI *ini, void *instance, void *store, const void* userData ); + static void parseLookupList( INI *ini, void *instance, void *store, const void* userData ); + static void parseThingTemplate( INI *ini, void *instance, void *store, const void* userData ); + static void parseArmorTemplate( INI *ini, void *instance, void *store, const void* userData ); + static void parseDamageFX( INI *ini, void *instance, void *store, const void* userData ); + static void parseWeaponTemplate( INI *ini, void *instance, void *store, const void* userData ); + // parse a duration in msec and convert to duration in frames + static void parseDurationReal( INI *ini, void *instance, void *store, const void* userData ); + // parse a duration in msec and convert to duration in integral number of frames, (unsignedint) rounding UP + static void parseDurationUnsignedInt( INI *ini, void *instance, void *store, const void* userData ); + static void parseDurationUnsignedShort( INI *ini, void *instance, void *store, const void *userData ); + // parse acceleration in (dist/sec) and convert to (dist/frame) + static void parseVelocityReal( INI *ini, void *instance, void *store, const void* userData ); + // parse acceleration in (dist/sec^2) and convert to (dist/frame^2) + static void parseAccelerationReal( INI *ini, void *instance, void *store, const void* userData ); + // parse angle in degrees and convert to radians + static void parseAngleReal( INI *ini, void *instance, void *store, const void *userData ); + // note that this parses in degrees/sec, and converts to rads/frame! + static void parseAngularVelocityReal( INI *ini, void *instance, void *store, const void *userData ); + static void parseDamageTypeFlags(INI* ini, void* instance, void* store, const void* userData); + static void parseDeathTypeFlags(INI* ini, void* instance, void* store, const void* userData); + static void parseDeathTypeFlagsList(INI* ini, void* instance, void* store, const void* userData); + static void parseVeterancyLevelFlags(INI* ini, void* instance, void* store, const void* userData); + static void parseSoundsList( INI* ini, void *instance, void *store, const void* /*userData*/ ); + + // like parseIndexList but special handling for NONE to return -2 (EVA_None) + static void parseEvaNameIndexList(INI* ini, void* instance, void* store, const void* userData); + + /** + return the next token. if seps is null (or omitted), the standard seps are used. + + this will *never* return null; if there are no more tokens, an exception will be thrown. + */ + const char* getNextToken(const char* seps = NULL); + + /** + just like getNextToken(), except that null is returned if no more tokens are present + (rather than throwing an exception). usually you should call getNextToken(), + but for some cases this is handier (ie, parsing a variable-length number of tokens). + */ + const char* getNextTokenOrNull(const char* seps = NULL); + + /** + This is called when the next thing you expect is something like: + + Tag:value + + pass "Tag" (without the colon) for 'expected', and you will have the 'value' + token returned. + + If "Tag" is not the next token, an error is thrown. + */ + const char* getNextSubToken(const char* expected); + + /** + return the next ascii string. this is usually the same the result of getNextToken(), + except that it allows for quote-delimited strings (eg, "foo bar"), so you can + get strings with spaces, and/or empty strings. + */ + AsciiString getNextAsciiString(); + AsciiString getNextQuotedAsciiString(); //fixed version of above. We can't fix the regular one for fear of breaking existing code. :-( + + /** + utility routine that does a sscanf() on the string to get the Science, and throws + an exception if not of the right form. + */ + static ScienceType scanScience(const char* token); + + /** + utility routine that does a sscanf() on the string to get the int, and throws + an exception if not of the right form. + */ + static Int scanInt(const char* token); + + /** + utility routine that does a sscanf() on the string to get the unsigned int, and throws + an exception if not of the right form. + */ + static UnsignedInt scanUnsignedInt(const char* token); + + /** + utility routine that does a sscanf() on the string to get the real, and throws + an exception if not of the right form. + */ + static Real scanReal(const char* token); + static Real scanPercentToReal(const char* token); + + static Int scanIndexList(const char* token, ConstCharPtrArray nameList); + static Int scanLookupList(const char* token, ConstLookupListRecArray lookupList); + + static Bool scanBool(const char* token); + +protected: + + static Bool isValidINIFilename( const char *filename ); ///< is this a valid .ini filename + + void prepFile( AsciiString filename, INILoadType loadType ); + void unPrepFile(); + + void readLine( void ); + + File *m_file; ///< file pointer of file currently loading + + enum + { + INI_READ_BUFFER = 8192 ///< size of internal read buffer + }; + char m_readBuffer[INI_READ_BUFFER]; ///< internal read buffer + unsigned m_readBufferNext; ///< next char in read buffer + unsigned m_readBufferUsed; ///< number of bytes in read buffer + + AsciiString m_filename; ///< filename of file currently loading + INILoadType m_loadType; ///< load time for current file + UnsignedInt m_lineNum; ///< current line number that's been read + char m_buffer[ INI_MAX_CHARS_PER_LINE+1 ];///< buffer to read file contents into + const char *m_seps; ///< for strtok parsing + const char *m_sepsPercent; ///< m_seps with percent delimiter as well + const char *m_sepsColon; ///< m_seps with colon delimiter as well + const char *m_sepsQuote; ///< token to represent a quoted ascii string + const char *m_blockEndToken; ///< token to represent end of data block + Bool m_endOfFile; ///< TRUE when we've hit EOF +#ifdef DEBUG_CRASHING + char m_curBlockStart[ INI_MAX_CHARS_PER_LINE ]; ///< first line of cur block +#endif +}; + +#endif // __INI_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/TintStatus.h b/GeneralsMD/Code/GameEngine/Include/GameClient/TintStatus.h index 60a100e5733..6ed01a55744 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/TintStatus.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/TintStatus.h @@ -1,53 +1,53 @@ - -#pragma once -#ifndef __TINTSTATUS_H__ -#define __TINTSTATUS_H__ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "Lib/BaseType.h" -#include "Common/BitFlags.h" -#include "Common/BitFlagsIO.h" - -// Tint status types can now be used via ini; -// Sync with TintStatusFlags::s_bitNameList[] in Drawable.cpp -enum TintStatus CPP_11(: Int) -{ - TINT_STATUS_INVALID = 0, - - TINT_STATUS_DISABLED = 1,///< drawable tint color is deathly dark grey - TINT_STATUS_IRRADIATED, ///< drawable tint color is sickly green - TINT_STATUS_POISONED, ///< drawable tint color is open-sore red - TINT_STATUS_GAINING_SUBDUAL_DAMAGE, ///< When gaining subdual damage, we tint SUBDUAL_DAMAGE_COLOR - TINT_STATUS_FRENZY, ///< When frenzied, we tint FRENZY_COLOR - // New generic entries: - TINT_STATUS_SHIELDED, ///< When shielded, we tint SHIELDED_COLOR - TINT_STATUS_DEMORALIZED, - TINT_STATUS_BOOST, - TINT_STATUS_TELEPORT_RECOVER, ///< (Chrono Legionnaire -> recover from teleport) - TINT_STATUS_DISABLED_CHRONO, ///< Unit disabled by chrono gun - TINT_STATUS_GAINING_CHRONO_DAMAGE, ///< Unit getting damaged from chrono gun - TINT_STATUS_EXTRA1, - TINT_STATUS_EXTRA2, - TINT_STATUS_EXTRA3, - TINT_STATUS_EXTRA4, - TINT_STATUS_EXTRA5, - TINT_STATUS_EXTRA6, - TINT_STATUS_EXTRA7, - TINT_STATUS_EXTRA8, - - TINT_STATUS_COUNT // Keep this last -}; - -//------------------- -struct DrawableColorTint -{ - RGBColor color; - RGBColor colorInfantry; - UnsignedInt attackFrames; - UnsignedInt decayFrames; -}; - -typedef BitFlags TintStatusFlags; - -// -------- + +#pragma once +#ifndef __TINTSTATUS_H__ +#define __TINTSTATUS_H__ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Lib/BaseType.h" +#include "Common/BitFlags.h" +#include "Common/BitFlagsIO.h" + +// Tint status types can now be used via ini; +// Sync with TintStatusFlags::s_bitNameList[] in Drawable.cpp +enum TintStatus CPP_11(: Int) +{ + TINT_STATUS_INVALID = 0, + + TINT_STATUS_DISABLED = 1,///< drawable tint color is deathly dark grey + TINT_STATUS_IRRADIATED, ///< drawable tint color is sickly green + TINT_STATUS_POISONED, ///< drawable tint color is open-sore red + TINT_STATUS_GAINING_SUBDUAL_DAMAGE, ///< When gaining subdual damage, we tint SUBDUAL_DAMAGE_COLOR + TINT_STATUS_FRENZY, ///< When frenzied, we tint FRENZY_COLOR + // New generic entries: + TINT_STATUS_SHIELDED, ///< When shielded, we tint SHIELDED_COLOR + TINT_STATUS_DEMORALIZED, + TINT_STATUS_BOOST, + TINT_STATUS_TELEPORT_RECOVER, ///< (Chrono Legionnaire -> recover from teleport) + TINT_STATUS_DISABLED_CHRONO, ///< Unit disabled by chrono gun + TINT_STATUS_GAINING_CHRONO_DAMAGE, ///< Unit getting damaged from chrono gun + TINT_STATUS_EXTRA1, + TINT_STATUS_EXTRA2, + TINT_STATUS_EXTRA3, + TINT_STATUS_EXTRA4, + TINT_STATUS_EXTRA5, + TINT_STATUS_EXTRA6, + TINT_STATUS_EXTRA7, + TINT_STATUS_EXTRA8, + + TINT_STATUS_COUNT // Keep this last +}; + +//------------------- +struct DrawableColorTint +{ + RGBColor color; + RGBColor colorInfantry; + UnsignedInt attackFrames; + UnsignedInt decayFrames; +}; + +typedef BitFlags TintStatusFlags; + +// -------- #endif /* __TINTSTATUS_H__ */ \ No newline at end of file diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h index 5dbe6269a47..fad83d9ea6e 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h @@ -1,400 +1,400 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: Damage.h ///////////////////////////////////////////////////////////////////////////////// -// Author: Colin Day, November 2001 -// Desc: Damage description -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __DAMAGE_H_ -#define __DAMAGE_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "Common/BitFlags.h" -#include "Common/GameType.h" -#include "Common/ObjectStatusTypes.h" // Precompiled header anyway, no detangling possibility -#include "Common/Snapshot.h" - - -// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// -class Object; -class INI; -class ThingTemplate; - -//------------------------------------------------------------------------------------------------- -/** Damage types, keep this in sync with DamageTypeFlags::s_bitNameList[] */ -//------------------------------------------------------------------------------------------------- -enum DamageType CPP_11(: Int) -{ - DAMAGE_EXPLOSION = 0, - DAMAGE_CRUSH = 1, - DAMAGE_ARMOR_PIERCING = 2, - DAMAGE_SMALL_ARMS = 3, - DAMAGE_GATTLING = 4, - DAMAGE_RADIATION = 5, - DAMAGE_FLAME = 6, - DAMAGE_LASER = 7, - DAMAGE_SNIPER = 8, - DAMAGE_POISON = 9, - DAMAGE_HEALING = 10, - DAMAGE_UNRESISTABLE = 11, // this is for scripting to cause 'armorproof' damage - DAMAGE_WATER = 12, - DAMAGE_DEPLOY = 13, // for transports to deploy units and order them to all attack. - DAMAGE_SURRENDER = 14, // if something "dies" to surrender damage, they surrender.... duh! - DAMAGE_HACK = 15, - DAMAGE_KILLPILOT = 16, // special snipe attack that kills the pilot and renders a vehicle unmanned. - DAMAGE_PENALTY = 17, // from game penalty (you won't receive radar warnings BTW) - DAMAGE_FALLING = 18, - DAMAGE_MELEE = 19, // Blades, clubs... - DAMAGE_DISARM = 20, // "special" damage type used for disarming mines, bombs, etc (NOT for "disarming" an opponent!) - DAMAGE_HAZARD_CLEANUP = 21, // special damage type for cleaning up hazards like radiation or bio-poison. - DAMAGE_PARTICLE_BEAM = 22, // Incinerates virtually everything (insanely powerful orbital beam) - DAMAGE_TOPPLING = 23, // damage from getting toppled. - DAMAGE_INFANTRY_MISSILE = 24, - DAMAGE_AURORA_BOMB = 25, - DAMAGE_LAND_MINE = 26, - DAMAGE_JET_MISSILES = 27, - DAMAGE_STEALTHJET_MISSILES = 28, - DAMAGE_MOLOTOV_COCKTAIL = 29, - DAMAGE_COMANCHE_VULCAN = 30, - DAMAGE_SUBDUAL_MISSILE = 31, ///< Damage that does not kill you, but produces some special effect based on your Body Module. Seperate HP from normal damage. - DAMAGE_SUBDUAL_VEHICLE = 32, - DAMAGE_SUBDUAL_BUILDING = 33, - DAMAGE_SUBDUAL_UNRESISTABLE = 34, - DAMAGE_MICROWAVE = 35, ///< Radiation that only affects infantry - DAMAGE_KILL_GARRISONED = 36, ///< Kills Passengers up to the number specified in Damage - DAMAGE_STATUS = 37, ///< Damage that gives a status condition, not that does hitpoint damage - // -- - // Generic additional damage types (no special logic) - DAMAGE_SONIC, - DAMAGE_ACID, - DAMAGE_JET_BOMB, - DAMAGE_ANTI_TANK_GUN, - DAMAGE_ANTI_TANK_MISSILE, - DAMAGE_ANTI_AIR_GUN, - DAMAGE_ANTI_AIR_MISSILE, - DAMAGE_SEISMIC, - DAMAGE_RAD_BEAM, - DAMAGE_TESLA, - - // Specific damage types with special logic attached - DAMAGE_CHRONO_GUN, ///< Disable target and remove them once health threshold is reached - DAMAGE_CHRONO_UNRESISTABLE, ///< Used for recovery from CHRONO_GUN - // DAMAGE_ZOMBIE_VIRUS, // TODO - // DAMAGE_MIND_CONTROL, // TODO - - - // Please note: There is a string array DamageTypeFlags::s_bitNameList[] - - DAMAGE_NUM_TYPES // keep this last -}; - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- - -typedef BitFlags DamageTypeFlags; - -inline Bool getDamageTypeFlag(DamageTypeFlags flags, DamageType dt) -{ - return flags.test(dt); -} - -inline DamageTypeFlags setDamageTypeFlag(DamageTypeFlags flags, DamageType dt) -{ - flags.set(dt, TRUE); - return flags; -} - -inline DamageTypeFlags clearDamageTypeFlag(DamageTypeFlags flags, DamageType dt) -{ - flags.set(dt, FALSE); - return flags; -} - -// Instead of checking against a single damage type, gather the question here so we can have more than one -inline Bool IsSubdualDamage( DamageType type ) -{ - switch( type ) - { - case DAMAGE_SUBDUAL_MISSILE: - case DAMAGE_SUBDUAL_VEHICLE: - case DAMAGE_SUBDUAL_BUILDING: - case DAMAGE_SUBDUAL_UNRESISTABLE: - return TRUE; - } - - return FALSE; -} - -/// Does this type of damage go to internalChangeHealth? -inline Bool IsHealthDamagingDamage( DamageType type ) -{ - // The need for this function brought to you by "Have the guy with no game experience write the weapon code" Foundation. - // Health Damage should be one type of WeaponEffect. Thinking "Weapons can only do damage" is why AoE is boring. - switch( type ) - { - case DAMAGE_STATUS: - case DAMAGE_SUBDUAL_MISSILE: - case DAMAGE_SUBDUAL_VEHICLE: - case DAMAGE_SUBDUAL_BUILDING: - case DAMAGE_SUBDUAL_UNRESISTABLE: - case DAMAGE_KILLPILOT: - case DAMAGE_KILL_GARRISONED: - return FALSE; - } - - return TRUE; -} - -inline void SET_ALL_DAMAGE_TYPE_BITS(DamageTypeFlags& m) -{ - m.clear(); - m.flip(); -} - -extern DamageTypeFlags DAMAGE_TYPE_FLAGS_NONE; -extern DamageTypeFlags DAMAGE_TYPE_FLAGS_ALL; -void initDamageTypeFlags(); - - -//------------------------------------------------------------------------------------------------- -/** Death types, keep this in sync with TheDeathNames[] */ -//------------------------------------------------------------------------------------------------- -enum DeathType CPP_11(: Int) -{ - // note that these DELIBERATELY have (slightly) different names from the damage names, - // since there isn't necessarily a one-to-one correspondence. e.g., DEATH_BURNED - // can come from DAMAGE_FLAME but also from DAMAGE_PARTICLE_BEAM. - DEATH_NORMAL = 0, - DEATH_NONE = 1, ///< this is a "special case" that can't normally cause death - DEATH_CRUSHED = 2, - DEATH_BURNED = 3, - DEATH_EXPLODED = 4, - DEATH_POISONED = 5, - DEATH_TOPPLED = 6, - DEATH_FLOODED = 7, - DEATH_SUICIDED = 8, - DEATH_LASERED = 9, - DEATH_DETONATED = 10, /**< this is the "death" that occurs when a missile/warhead/etc detonates normally, - as opposed to being shot down, etc */ - DEATH_SPLATTED = 11, /**< the death that results from DAMAGE_FALLING */ - DEATH_POISONED_BETA = 12, - - // these are the "extra" types for yet-to-be-defined stuff. Don't bother renaming or adding - // or removing these; they are reserved for modders :-) - DEATH_EXTRA_2 = 13, - DEATH_EXTRA_3 = 14, - DEATH_EXTRA_4 = 15, - DEATH_EXTRA_5 = 16, - DEATH_EXTRA_6 = 17, - DEATH_EXTRA_7 = 18, - DEATH_EXTRA_8 = 19, - DEATH_POISONED_GAMMA = 20, - - //New Death Types - DEATH_CHRONO, - - DEATH_NUM_TYPES // keep this last -}; - -#ifdef DEFINE_DEATH_NAMES -static const char *TheDeathNames[] = -{ - "NORMAL", - "NONE", - "CRUSHED", - "BURNED", - "EXPLODED", - "POISONED", - "TOPPLED", - "FLOODED", - "SUICIDED", - "LASERED", - "DETONATED", - "SPLATTED", - "POISONED_BETA", - "EXTRA_2", - "EXTRA_3", - "EXTRA_4", - "EXTRA_5", - "EXTRA_6", - "EXTRA_7", - "EXTRA_8", - "POISONED_GAMMA", - //New: - "CHRONO", - - NULL -}; -#endif // end DEFINE_DEATH_NAMES - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- - -typedef UnsignedInt DeathTypeFlags; - -const DeathTypeFlags DEATH_TYPE_FLAGS_ALL = 0xffffffff; -const DeathTypeFlags DEATH_TYPE_FLAGS_NONE = 0x00000000; - -inline Bool getDeathTypeFlag(DeathTypeFlags flags, DeathType dt) -{ - return (flags & (1UL << (dt - 1))) != 0; -} - -inline DeathTypeFlags setDeathTypeFlag(DeathTypeFlags flags, DeathType dt) -{ - return (flags | (1UL << (dt - 1))); -} - -inline DeathTypeFlags clearDeathTypeFlag(DeathTypeFlags flags, DeathType dt) -{ - return (flags & ~(1UL << (dt - 1))); -} - -//------------------------------------------------------------------------------------------------- -/** Damage info inputs */ -//------------------------------------------------------------------------------------------------- -class DamageInfoInput : public Snapshot -{ - -public: - - DamageInfoInput( void ) - { - m_sourceID = INVALID_ID; - m_sourceTemplate = NULL; - m_sourcePlayerMask = 0; - m_damageType = DAMAGE_EXPLOSION; - m_damageStatusType = OBJECT_STATUS_NONE; - m_damageFXOverride = DAMAGE_UNRESISTABLE; - m_deathType = DEATH_NORMAL; - m_amount = 0; - m_kill = FALSE; - - m_shockWaveVector.zero(); - m_shockWaveAmount = 0.0f; - m_shockWaveRadius = 0.0f; - m_shockWaveTaperOff = 0.0f; - } - - ObjectID m_sourceID; ///< source of the damage - const ThingTemplate *m_sourceTemplate; ///< source of the damage (the template). - PlayerMaskType m_sourcePlayerMask; ///< Player mask of m_sourceID. - DamageType m_damageType; ///< type of damage - ObjectStatusTypes m_damageStatusType; ///< If status damage, what type - DamageType m_damageFXOverride; ///< If not marked as the default of Unresistable, the damage type to use in doDamageFX instead of the real damamge type - DeathType m_deathType; ///< if this kills us, death type to be used - Real m_amount; ///< # value of how much damage to inflict - Bool m_kill; ///< will always cause object to die regardless of damage. - - // These are used for damage causing shockwave, forcing units affected to be pushed around - Coord3D m_shockWaveVector; ///< This represents the incoming damage vector - Real m_shockWaveAmount; ///< This represents the amount of shockwave created by the damage. 0 = no shockwave, 1.0 = shockwave equal to damage. - Real m_shockWaveRadius; ///< This represents the effect radius of the shockwave. - Real m_shockWaveTaperOff; ///< This represents the taper off effect of the shockwave at the tip of the radius. 0.0 means shockwave is 0% at the radius edge. - - -protected: - - // snapshot methods - virtual void crc( Xfer *xfer ) { } - virtual void xfer( Xfer *xfer ); - virtual void loadPostProcess( void ) { } - -}; - -const Real HUGE_DAMAGE_AMOUNT = 999999.0f; - -//------------------------------------------------------------------------------------------------- -/** Damage into outputs */ -//------------------------------------------------------------------------------------------------- -class DamageInfoOutput : public Snapshot -{ - -public: - - DamageInfoOutput( void ) - { - m_actualDamageDealt = 0; - m_actualDamageClipped = 0; - m_noEffect = false; - } - - /** - m_actualDamageDealt is the damage we tried to apply to object (after multipliers and such). - m_actualDamageClipped is the value of m_actualDamageDealt, but clipped to the max health remaining of the obj. - example: - a mammoth tank fires a round at a small tank, attempting 100 damage. - the small tank has a damage multiplier of 50%, meaning that only 50 damage is applied. - furthermore, the small tank has only 30 health remaining. - so: m_actualDamageDealt = 50, m_actualDamageClipped = 30. - - this distinction is useful, since visual fx really wants to do the fx for "50 damage", - even though it was more than necessary to kill this object; game logic, on the other hand, - may want to know the "clipped" damage for ai purposes. - */ - Real m_actualDamageDealt; - Real m_actualDamageClipped; ///< (see comment for m_actualDamageDealt) - Bool m_noEffect; ///< if true, no damage was done at all (generally due to being InactiveBody) - -protected: - - // snapshot methods - virtual void crc( Xfer *xfer ) { } - virtual void xfer( Xfer *xfer ); - virtual void loadPostProcess( void ) { } - -}; - -//------------------------------------------------------------------------------------------------- -/** DamageInfo is a descriptor of damage we're trying to inflict. The structure - * is divided up into two parts, inputs and outputs. - * - * INPUTS: You must provide valid values for these fields in order for damage - * calculation to correctly take place - * OUTPUT: Upon returning from damage issuing functions, the output fields - * will be filled with the results of the damage occurrence - */ -//------------------------------------------------------------------------------------------------- -class DamageInfo : public Snapshot -{ - -public: - - DamageInfoInput in; ///< inputs for the damage info - DamageInfoOutput out; ///< results for the damage occurrence - -protected: - - virtual void crc( Xfer *xfer ) { } - virtual void xfer( Xfer *xfer ); - virtual void loadPostProcess( void ){ } - -}; - -#endif // __DAMAGE_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: Damage.h ///////////////////////////////////////////////////////////////////////////////// +// Author: Colin Day, November 2001 +// Desc: Damage description +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __DAMAGE_H_ +#define __DAMAGE_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Common/BitFlags.h" +#include "Common/GameType.h" +#include "Common/ObjectStatusTypes.h" // Precompiled header anyway, no detangling possibility +#include "Common/Snapshot.h" + + +// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// +class Object; +class INI; +class ThingTemplate; + +//------------------------------------------------------------------------------------------------- +/** Damage types, keep this in sync with DamageTypeFlags::s_bitNameList[] */ +//------------------------------------------------------------------------------------------------- +enum DamageType CPP_11(: Int) +{ + DAMAGE_EXPLOSION = 0, + DAMAGE_CRUSH = 1, + DAMAGE_ARMOR_PIERCING = 2, + DAMAGE_SMALL_ARMS = 3, + DAMAGE_GATTLING = 4, + DAMAGE_RADIATION = 5, + DAMAGE_FLAME = 6, + DAMAGE_LASER = 7, + DAMAGE_SNIPER = 8, + DAMAGE_POISON = 9, + DAMAGE_HEALING = 10, + DAMAGE_UNRESISTABLE = 11, // this is for scripting to cause 'armorproof' damage + DAMAGE_WATER = 12, + DAMAGE_DEPLOY = 13, // for transports to deploy units and order them to all attack. + DAMAGE_SURRENDER = 14, // if something "dies" to surrender damage, they surrender.... duh! + DAMAGE_HACK = 15, + DAMAGE_KILLPILOT = 16, // special snipe attack that kills the pilot and renders a vehicle unmanned. + DAMAGE_PENALTY = 17, // from game penalty (you won't receive radar warnings BTW) + DAMAGE_FALLING = 18, + DAMAGE_MELEE = 19, // Blades, clubs... + DAMAGE_DISARM = 20, // "special" damage type used for disarming mines, bombs, etc (NOT for "disarming" an opponent!) + DAMAGE_HAZARD_CLEANUP = 21, // special damage type for cleaning up hazards like radiation or bio-poison. + DAMAGE_PARTICLE_BEAM = 22, // Incinerates virtually everything (insanely powerful orbital beam) + DAMAGE_TOPPLING = 23, // damage from getting toppled. + DAMAGE_INFANTRY_MISSILE = 24, + DAMAGE_AURORA_BOMB = 25, + DAMAGE_LAND_MINE = 26, + DAMAGE_JET_MISSILES = 27, + DAMAGE_STEALTHJET_MISSILES = 28, + DAMAGE_MOLOTOV_COCKTAIL = 29, + DAMAGE_COMANCHE_VULCAN = 30, + DAMAGE_SUBDUAL_MISSILE = 31, ///< Damage that does not kill you, but produces some special effect based on your Body Module. Seperate HP from normal damage. + DAMAGE_SUBDUAL_VEHICLE = 32, + DAMAGE_SUBDUAL_BUILDING = 33, + DAMAGE_SUBDUAL_UNRESISTABLE = 34, + DAMAGE_MICROWAVE = 35, ///< Radiation that only affects infantry + DAMAGE_KILL_GARRISONED = 36, ///< Kills Passengers up to the number specified in Damage + DAMAGE_STATUS = 37, ///< Damage that gives a status condition, not that does hitpoint damage + // -- + // Generic additional damage types (no special logic) + DAMAGE_SONIC, + DAMAGE_ACID, + DAMAGE_JET_BOMB, + DAMAGE_ANTI_TANK_GUN, + DAMAGE_ANTI_TANK_MISSILE, + DAMAGE_ANTI_AIR_GUN, + DAMAGE_ANTI_AIR_MISSILE, + DAMAGE_SEISMIC, + DAMAGE_RAD_BEAM, + DAMAGE_TESLA, + + // Specific damage types with special logic attached + DAMAGE_CHRONO_GUN, ///< Disable target and remove them once health threshold is reached + DAMAGE_CHRONO_UNRESISTABLE, ///< Used for recovery from CHRONO_GUN + // DAMAGE_ZOMBIE_VIRUS, // TODO + // DAMAGE_MIND_CONTROL, // TODO + + + // Please note: There is a string array DamageTypeFlags::s_bitNameList[] + + DAMAGE_NUM_TYPES // keep this last +}; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +typedef BitFlags DamageTypeFlags; + +inline Bool getDamageTypeFlag(DamageTypeFlags flags, DamageType dt) +{ + return flags.test(dt); +} + +inline DamageTypeFlags setDamageTypeFlag(DamageTypeFlags flags, DamageType dt) +{ + flags.set(dt, TRUE); + return flags; +} + +inline DamageTypeFlags clearDamageTypeFlag(DamageTypeFlags flags, DamageType dt) +{ + flags.set(dt, FALSE); + return flags; +} + +// Instead of checking against a single damage type, gather the question here so we can have more than one +inline Bool IsSubdualDamage( DamageType type ) +{ + switch( type ) + { + case DAMAGE_SUBDUAL_MISSILE: + case DAMAGE_SUBDUAL_VEHICLE: + case DAMAGE_SUBDUAL_BUILDING: + case DAMAGE_SUBDUAL_UNRESISTABLE: + return TRUE; + } + + return FALSE; +} + +/// Does this type of damage go to internalChangeHealth? +inline Bool IsHealthDamagingDamage( DamageType type ) +{ + // The need for this function brought to you by "Have the guy with no game experience write the weapon code" Foundation. + // Health Damage should be one type of WeaponEffect. Thinking "Weapons can only do damage" is why AoE is boring. + switch( type ) + { + case DAMAGE_STATUS: + case DAMAGE_SUBDUAL_MISSILE: + case DAMAGE_SUBDUAL_VEHICLE: + case DAMAGE_SUBDUAL_BUILDING: + case DAMAGE_SUBDUAL_UNRESISTABLE: + case DAMAGE_KILLPILOT: + case DAMAGE_KILL_GARRISONED: + return FALSE; + } + + return TRUE; +} + +inline void SET_ALL_DAMAGE_TYPE_BITS(DamageTypeFlags& m) +{ + m.clear(); + m.flip(); +} + +extern DamageTypeFlags DAMAGE_TYPE_FLAGS_NONE; +extern DamageTypeFlags DAMAGE_TYPE_FLAGS_ALL; +void initDamageTypeFlags(); + + +//------------------------------------------------------------------------------------------------- +/** Death types, keep this in sync with TheDeathNames[] */ +//------------------------------------------------------------------------------------------------- +enum DeathType CPP_11(: Int) +{ + // note that these DELIBERATELY have (slightly) different names from the damage names, + // since there isn't necessarily a one-to-one correspondence. e.g., DEATH_BURNED + // can come from DAMAGE_FLAME but also from DAMAGE_PARTICLE_BEAM. + DEATH_NORMAL = 0, + DEATH_NONE = 1, ///< this is a "special case" that can't normally cause death + DEATH_CRUSHED = 2, + DEATH_BURNED = 3, + DEATH_EXPLODED = 4, + DEATH_POISONED = 5, + DEATH_TOPPLED = 6, + DEATH_FLOODED = 7, + DEATH_SUICIDED = 8, + DEATH_LASERED = 9, + DEATH_DETONATED = 10, /**< this is the "death" that occurs when a missile/warhead/etc detonates normally, + as opposed to being shot down, etc */ + DEATH_SPLATTED = 11, /**< the death that results from DAMAGE_FALLING */ + DEATH_POISONED_BETA = 12, + + // these are the "extra" types for yet-to-be-defined stuff. Don't bother renaming or adding + // or removing these; they are reserved for modders :-) + DEATH_EXTRA_2 = 13, + DEATH_EXTRA_3 = 14, + DEATH_EXTRA_4 = 15, + DEATH_EXTRA_5 = 16, + DEATH_EXTRA_6 = 17, + DEATH_EXTRA_7 = 18, + DEATH_EXTRA_8 = 19, + DEATH_POISONED_GAMMA = 20, + + //New Death Types + DEATH_CHRONO, + + DEATH_NUM_TYPES // keep this last +}; + +#ifdef DEFINE_DEATH_NAMES +static const char *TheDeathNames[] = +{ + "NORMAL", + "NONE", + "CRUSHED", + "BURNED", + "EXPLODED", + "POISONED", + "TOPPLED", + "FLOODED", + "SUICIDED", + "LASERED", + "DETONATED", + "SPLATTED", + "POISONED_BETA", + "EXTRA_2", + "EXTRA_3", + "EXTRA_4", + "EXTRA_5", + "EXTRA_6", + "EXTRA_7", + "EXTRA_8", + "POISONED_GAMMA", + //New: + "CHRONO", + + NULL +}; +#endif // end DEFINE_DEATH_NAMES + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +typedef UnsignedInt DeathTypeFlags; + +const DeathTypeFlags DEATH_TYPE_FLAGS_ALL = 0xffffffff; +const DeathTypeFlags DEATH_TYPE_FLAGS_NONE = 0x00000000; + +inline Bool getDeathTypeFlag(DeathTypeFlags flags, DeathType dt) +{ + return (flags & (1UL << (dt - 1))) != 0; +} + +inline DeathTypeFlags setDeathTypeFlag(DeathTypeFlags flags, DeathType dt) +{ + return (flags | (1UL << (dt - 1))); +} + +inline DeathTypeFlags clearDeathTypeFlag(DeathTypeFlags flags, DeathType dt) +{ + return (flags & ~(1UL << (dt - 1))); +} + +//------------------------------------------------------------------------------------------------- +/** Damage info inputs */ +//------------------------------------------------------------------------------------------------- +class DamageInfoInput : public Snapshot +{ + +public: + + DamageInfoInput( void ) + { + m_sourceID = INVALID_ID; + m_sourceTemplate = NULL; + m_sourcePlayerMask = 0; + m_damageType = DAMAGE_EXPLOSION; + m_damageStatusType = OBJECT_STATUS_NONE; + m_damageFXOverride = DAMAGE_UNRESISTABLE; + m_deathType = DEATH_NORMAL; + m_amount = 0; + m_kill = FALSE; + + m_shockWaveVector.zero(); + m_shockWaveAmount = 0.0f; + m_shockWaveRadius = 0.0f; + m_shockWaveTaperOff = 0.0f; + } + + ObjectID m_sourceID; ///< source of the damage + const ThingTemplate *m_sourceTemplate; ///< source of the damage (the template). + PlayerMaskType m_sourcePlayerMask; ///< Player mask of m_sourceID. + DamageType m_damageType; ///< type of damage + ObjectStatusTypes m_damageStatusType; ///< If status damage, what type + DamageType m_damageFXOverride; ///< If not marked as the default of Unresistable, the damage type to use in doDamageFX instead of the real damamge type + DeathType m_deathType; ///< if this kills us, death type to be used + Real m_amount; ///< # value of how much damage to inflict + Bool m_kill; ///< will always cause object to die regardless of damage. + + // These are used for damage causing shockwave, forcing units affected to be pushed around + Coord3D m_shockWaveVector; ///< This represents the incoming damage vector + Real m_shockWaveAmount; ///< This represents the amount of shockwave created by the damage. 0 = no shockwave, 1.0 = shockwave equal to damage. + Real m_shockWaveRadius; ///< This represents the effect radius of the shockwave. + Real m_shockWaveTaperOff; ///< This represents the taper off effect of the shockwave at the tip of the radius. 0.0 means shockwave is 0% at the radius edge. + + +protected: + + // snapshot methods + virtual void crc( Xfer *xfer ) { } + virtual void xfer( Xfer *xfer ); + virtual void loadPostProcess( void ) { } + +}; + +const Real HUGE_DAMAGE_AMOUNT = 999999.0f; + +//------------------------------------------------------------------------------------------------- +/** Damage into outputs */ +//------------------------------------------------------------------------------------------------- +class DamageInfoOutput : public Snapshot +{ + +public: + + DamageInfoOutput( void ) + { + m_actualDamageDealt = 0; + m_actualDamageClipped = 0; + m_noEffect = false; + } + + /** + m_actualDamageDealt is the damage we tried to apply to object (after multipliers and such). + m_actualDamageClipped is the value of m_actualDamageDealt, but clipped to the max health remaining of the obj. + example: + a mammoth tank fires a round at a small tank, attempting 100 damage. + the small tank has a damage multiplier of 50%, meaning that only 50 damage is applied. + furthermore, the small tank has only 30 health remaining. + so: m_actualDamageDealt = 50, m_actualDamageClipped = 30. + + this distinction is useful, since visual fx really wants to do the fx for "50 damage", + even though it was more than necessary to kill this object; game logic, on the other hand, + may want to know the "clipped" damage for ai purposes. + */ + Real m_actualDamageDealt; + Real m_actualDamageClipped; ///< (see comment for m_actualDamageDealt) + Bool m_noEffect; ///< if true, no damage was done at all (generally due to being InactiveBody) + +protected: + + // snapshot methods + virtual void crc( Xfer *xfer ) { } + virtual void xfer( Xfer *xfer ); + virtual void loadPostProcess( void ) { } + +}; + +//------------------------------------------------------------------------------------------------- +/** DamageInfo is a descriptor of damage we're trying to inflict. The structure + * is divided up into two parts, inputs and outputs. + * + * INPUTS: You must provide valid values for these fields in order for damage + * calculation to correctly take place + * OUTPUT: Upon returning from damage issuing functions, the output fields + * will be filled with the results of the damage occurrence + */ +//------------------------------------------------------------------------------------------------- +class DamageInfo : public Snapshot +{ + +public: + + DamageInfoInput in; ///< inputs for the damage info + DamageInfoOutput out; ///< results for the damage occurrence + +protected: + + virtual void crc( Xfer *xfer ) { } + virtual void xfer( Xfer *xfer ); + virtual void loadPostProcess( void ){ } + +}; + +#endif // __DAMAGE_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h index a22c8dbdfa7..acaa1984030 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h @@ -1,126 +1,126 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: RadiusDecalBehavior.h ///////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __RadiusDecalBehavior_H_ -#define __RadiusDecalBehavior_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "GameLogic/Module/BehaviorModule.h" -#include "GameLogic/Module/UpgradeModule.h" -#include "GameLogic/Module/UpdateModule.h" -#include "GameClient/RadiusDecal.h" - -//------------------------------------------------------------------------------------------------- -class RadiusDecalBehaviorModuleData : public UpdateModuleData -{ -public: - UpgradeMuxData m_upgradeMuxData; - Bool m_initiallyActive; - - RadiusDecalTemplate m_decalTemplate; - Real m_decalRadius; - - RadiusDecalBehaviorModuleData(); - - static void buildFieldParse(MultiIniFieldParse& p); -}; - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -class RadiusDecalBehavior : public UpdateModule, public UpgradeMux -{ - - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( RadiusDecalBehavior, "RadiusDecalBehavior" ) - MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( RadiusDecalBehavior, RadiusDecalBehaviorModuleData ) - -public: - - RadiusDecalBehavior( Thing *thing, const ModuleData* moduleData ); - // virtual destructor prototype provided by memory pool declaration - - // module methids - static Int getInterfaceMask() { return UpdateModule::getInterfaceMask() | (MODULEINTERFACE_UPGRADE); } - - // BehaviorModule - virtual UpgradeModuleInterface* getUpgrade() { return this; } - - //void createRadiusDecal( const Coord3D& pos ); - // void createRadiusDecal( const RadiusDecalTemplate& tmpl, Real radius, const Coord3D& pos ); - - void createRadiusDecal( void ); - void killRadiusDecal( void ); - - // UpdateModuleInterface - virtual UpdateSleepTime update(); - - virtual DisabledMaskType getDisabledTypesToProcess() const { return MAKE_DISABLED_MASK(DISABLED_HELD); } - -protected: - - - virtual void upgradeImplementation() - { - createRadiusDecal(); - setWakeFrame(getObject(), UPDATE_SLEEP_NONE); - } - - virtual void getUpgradeActivationMasks(UpgradeMaskType& activation, UpgradeMaskType& conflicting) const - { - getRadiusDecalBehaviorModuleData()->m_upgradeMuxData.getUpgradeActivationMasks(activation, conflicting); - } - - virtual void performUpgradeFX() - { - getRadiusDecalBehaviorModuleData()->m_upgradeMuxData.performUpgradeFX(getObject()); - } - - virtual void processUpgradeRemoval() - { - // I can't take it any more. Let the record show that I think the UpgradeMux multiple inheritence is CRAP. - getRadiusDecalBehaviorModuleData()->m_upgradeMuxData.muxDataProcessUpgradeRemoval(getObject()); - } - - virtual Bool requiresAllActivationUpgrades() const - { - return getRadiusDecalBehaviorModuleData()->m_upgradeMuxData.m_requiresAllTriggers; - } - - inline Bool isUpgradeActive() const { return isAlreadyUpgraded(); } - - virtual Bool isSubObjectsUpgrade() { return false; } - -private: - - RadiusDecal m_radiusDecal; - - void clearDecal( void ); -}; - -#endif // __RadiusDecalBehavior_H_ - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: RadiusDecalBehavior.h ///////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __RadiusDecalBehavior_H_ +#define __RadiusDecalBehavior_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "GameLogic/Module/BehaviorModule.h" +#include "GameLogic/Module/UpgradeModule.h" +#include "GameLogic/Module/UpdateModule.h" +#include "GameClient/RadiusDecal.h" + +//------------------------------------------------------------------------------------------------- +class RadiusDecalBehaviorModuleData : public UpdateModuleData +{ +public: + UpgradeMuxData m_upgradeMuxData; + Bool m_initiallyActive; + + RadiusDecalTemplate m_decalTemplate; + Real m_decalRadius; + + RadiusDecalBehaviorModuleData(); + + static void buildFieldParse(MultiIniFieldParse& p); +}; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class RadiusDecalBehavior : public UpdateModule, public UpgradeMux +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( RadiusDecalBehavior, "RadiusDecalBehavior" ) + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( RadiusDecalBehavior, RadiusDecalBehaviorModuleData ) + +public: + + RadiusDecalBehavior( Thing *thing, const ModuleData* moduleData ); + // virtual destructor prototype provided by memory pool declaration + + // module methids + static Int getInterfaceMask() { return UpdateModule::getInterfaceMask() | (MODULEINTERFACE_UPGRADE); } + + // BehaviorModule + virtual UpgradeModuleInterface* getUpgrade() { return this; } + + //void createRadiusDecal( const Coord3D& pos ); + // void createRadiusDecal( const RadiusDecalTemplate& tmpl, Real radius, const Coord3D& pos ); + + void createRadiusDecal( void ); + void killRadiusDecal( void ); + + // UpdateModuleInterface + virtual UpdateSleepTime update(); + + virtual DisabledMaskType getDisabledTypesToProcess() const { return MAKE_DISABLED_MASK(DISABLED_HELD); } + +protected: + + + virtual void upgradeImplementation() + { + createRadiusDecal(); + setWakeFrame(getObject(), UPDATE_SLEEP_NONE); + } + + virtual void getUpgradeActivationMasks(UpgradeMaskType& activation, UpgradeMaskType& conflicting) const + { + getRadiusDecalBehaviorModuleData()->m_upgradeMuxData.getUpgradeActivationMasks(activation, conflicting); + } + + virtual void performUpgradeFX() + { + getRadiusDecalBehaviorModuleData()->m_upgradeMuxData.performUpgradeFX(getObject()); + } + + virtual void processUpgradeRemoval() + { + // I can't take it any more. Let the record show that I think the UpgradeMux multiple inheritence is CRAP. + getRadiusDecalBehaviorModuleData()->m_upgradeMuxData.muxDataProcessUpgradeRemoval(getObject()); + } + + virtual Bool requiresAllActivationUpgrades() const + { + return getRadiusDecalBehaviorModuleData()->m_upgradeMuxData.m_requiresAllTriggers; + } + + inline Bool isUpgradeActive() const { return isAlreadyUpgraded(); } + + virtual Bool isSubObjectsUpgrade() { return false; } + +private: + + RadiusDecal m_radiusDecal; + + void clearDecal( void ); +}; + +#endif // __RadiusDecalBehavior_H_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UpgradeSpecialPower.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UpgradeSpecialPower.h index 87b475a60ed..8ec2286d3f2 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UpgradeSpecialPower.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UpgradeSpecialPower.h @@ -1,78 +1,78 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: UpgradeSpecialPower.h ///////////////////////////////////////////////////////////////// -// Author: Andreas W, July 25 -// Desc: Special Power will grant an upgrade to the object -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#pragma once - -#ifndef __UPGRADE_SPECIAL_POWER_H_ -#define __UPGRADE_SPECIAL_POWER_H_ - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "GameLogic/Module/SpecialPowerModule.h" - -// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// -class FXList; - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -class UpgradeSpecialPowerModuleData : public SpecialPowerModuleData -{ - -public: - - UpgradeSpecialPowerModuleData(void); - - static void buildFieldParse(MultiIniFieldParse& p); - - AsciiString m_upgradeName; ///< name of the upgrade to be granted. - -}; - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -class UpgradeSpecialPower : public SpecialPowerModule -{ - - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(UpgradeSpecialPower, "UpgradeSpecialPower") - MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA(UpgradeSpecialPower, UpgradeSpecialPowerModuleData) - -public: - - UpgradeSpecialPower(Thing* thing, const ModuleData* moduleData); - // virtual destructor prototype provided by memory pool object - - virtual void doSpecialPower(UnsignedInt commandOptions); - - virtual void doSpecialPowerAtObject(Object* obj, UnsignedInt commandOptions); - -protected: - - void grantUpgrade(Object* object); -}; - -#endif +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: UpgradeSpecialPower.h ///////////////////////////////////////////////////////////////// +// Author: Andreas W, July 25 +// Desc: Special Power will grant an upgrade to the object +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef __UPGRADE_SPECIAL_POWER_H_ +#define __UPGRADE_SPECIAL_POWER_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "GameLogic/Module/SpecialPowerModule.h" + +// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// +class FXList; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class UpgradeSpecialPowerModuleData : public SpecialPowerModuleData +{ + +public: + + UpgradeSpecialPowerModuleData(void); + + static void buildFieldParse(MultiIniFieldParse& p); + + AsciiString m_upgradeName; ///< name of the upgrade to be granted. + +}; + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +class UpgradeSpecialPower : public SpecialPowerModule +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(UpgradeSpecialPower, "UpgradeSpecialPower") + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA(UpgradeSpecialPower, UpgradeSpecialPowerModuleData) + +public: + + UpgradeSpecialPower(Thing* thing, const ModuleData* moduleData); + // virtual destructor prototype provided by memory pool object + + virtual void doSpecialPower(UnsignedInt commandOptions); + + virtual void doSpecialPowerAtObject(Object* obj, UnsignedInt commandOptions); + +protected: + + void grantUpgrade(Object* object); +}; + +#endif diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h index 18156264983..99cdf4b75be 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h @@ -1,837 +1,837 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// Object.h /////////////////////////////////////////////////////////////////// -// Simple base object -// Author: Michael S. Booth, October 2000 - -#pragma once -#ifndef _OBJECT_H_ -#define _OBJECT_H_ - -#include "Lib/BaseType.h" - -#include "Common/Geometry.h" -#include "Common/Snapshot.h" -#include "Common/SpecialPowerMaskType.h" -#include "Common/DisabledTypes.h" -#include "Common/Thing.h" -#include "Common/ObjectStatusTypes.h" -#include "Common/Upgrade.h" - -#include "GameClient/Color.h" - -#include "GameLogic/Damage.h" //for kill() -#include "GameLogic/WeaponBonusConditionFlags.h" -#include "GameLogic/WeaponSet.h" -#include "GameLogic/WeaponSetFlags.h" -#include "GameLogic/Module/StealthUpdate.h" - -//----------------------------------------------------------------------------- -// Forward References -//----------------------------------------------------------------------------- - -class AIGroup; -class AIUpdateInterface; -class Anim2DTemplate; -class BehaviorModule; -class BehaviorModuleInterface; -class BodyModuleInterface; -class CollideModule; -class CollideModuleInterface; -class CommandButton; -class ContainModuleInterface; -class CountermeasuresBehaviorInterface; -class CreateModuleInterface; -class DamageInfo; -class DamageInfoInput; -class DamageModule; -class DamageModuleInterface; -class DestroyModuleInterface; -class DockUpdateInterface; -class Dict; -class DieModule; -class DieModuleInterface; -class ExitInterface; -class ExperienceTracker; -class FiringTracker; -class Module; -class PartitionData; -class PhysicsBehavior; -class PhysicsUpdate; -class Player; -class PolygonTrigger; -class ProductionUpdateInterface; -class ProjectileUpdateInterface; -class RadarObject; -class SightingInfo; -class SpawnBehaviorInterface; -class SpecialAbilityUpdate; -class SpecialPowerCompletionDie; -class SpecialPowerModuleInterface; -class SpecialPowerTemplate; -class SpecialPowerUpdateInterface; -class Team; -class UpdateModule; -class UpdateModuleInterface; -class UpgradeModule; -class UpgradeModuleInterface; -class UpgradeTemplate; - -class ObjectHeldHelper; -class ObjectDisabledHelper; -class ObjectSMCHelper; -class ObjectRepulsorHelper; -class StatusDamageHelper; -class SubdualDamageHelper; -class ChronoDamageHelper; -class TempWeaponBonusHelper; -class ObjectWeaponStatusHelper; -class ObjectDefectionHelper; - -enum CommandSourceType CPP_11(: Int); -enum HackerAttackMode CPP_11(: Int); -enum NameKeyType CPP_11(: Int); -enum SpecialPowerType CPP_11(: Int); -enum WeaponBonusConditionType CPP_11(: Int); -enum WeaponChoiceCriteria CPP_11(: Int); -enum WeaponSetConditionType CPP_11(: Int); -enum WeaponSetType CPP_11(: Int); -enum ArmorSetType CPP_11(: Int); -enum WeaponStatus CPP_11(: Int); -enum RadarPriorityType CPP_11(: Int); -enum CanAttackResult CPP_11(: Int); -// enum TintStatus CPP_11(: Int); - -// For ObjectStatusTypes -#include "Common/ObjectStatusTypes.h" - -// For ObjectScriptStatusBit -#include "GameLogic/ObjectScriptStatusBits.h" - -// For TintStatus -#include "GameClient/TintStatus.h" - -//----------------------------------------------------------------------------- -// Type Defines -//----------------------------------------------------------------------------- - -struct TTriggerInfo -{ - const PolygonTrigger* pTrigger; ///< The trigger area that the object is inside. - Byte entered; ///< True if the object entered this trigger area this frame. - Byte exited; ///< True if the object entered this trigger area this frame. - Byte isInside; ///< True if the object is inside this trigger area this frame. - Byte padding; ///< unused. - - TTriggerInfo() : entered(false), exited(false), isInside(false), padding(false), pTrigger(NULL) { } - -}; - -//---------------------------------------------------- - - -enum CrushSquishTestType CPP_11(: Int) -{ - TEST_CRUSH_ONLY, - TEST_SQUISH_ONLY, - TEST_CRUSH_OR_SQUISH -}; - - -// --------------------------------------------------- -/** - * Object definition. Objects are manipulated via TheGameLogic singleton. - * @todo Create an ObjectInterface class. - */ -class Object : public Thing, public Snapshot -{ - - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(Object, "ObjectPool" ) - /// destructor is non-public in order to require the use of TheGameLogic->destroyObject() - MEMORY_POOL_DELETEINSTANCE_VISIBILITY(protected) - -public: - - /// Object constructor automatically attaches all objects to "TheGameLogic" - Object(const ThingTemplate *thing, const ObjectStatusMaskType &objectStatusMask, Team *team); - - void initObject(); - - void onDestroy(); ///< run during TheGameLogic::destroyObject - - Object* getNextObject() { return m_next; } - const Object* getNextObject() const { return m_next; } - - void updateObjValuesFromMapProperties(Dict* properties); ///< Brings in properties set in the editor. - - // ids and binding - ObjectID getID() const { return m_id; } ///< this object's unique ID - void friend_bindToDrawable( Drawable *draw ); ///< set drawable association. for use ONLY by GameLogic! - Drawable* getDrawable() const { return m_drawable; } ///< drawable (if any) bound to obj - - ObjectID getProducerID() const { return m_producerID; } - void setProducer(const Object* obj); - - ObjectID getBuilderID() const { return m_builderID; } - void setBuilder( const Object *obj ); - - void enterGroup( AIGroup *group ); ///< become a member of the AIGroup - void leaveGroup( void ); ///< leave our current AIGroup - AIGroup *getGroup(void); - - // physical properties - Bool isMobile() const; ///< returns true if object is currently able to move - Bool isAbleToAttack() const; ///< returns true if object currently has some kind of attack capability - - void maskObject( Bool mask ); ///< mask/unmask object - - /** - Booby traps are set off by many random actions, so those actions are responsible for calling this. - Return value is if a booby trap was set off, so caller can react. - Those actions are: planting any type of bomb, entering, starting to capture, dying. - */ - Bool checkAndDetonateBoobyTrap(const Object *victim); - - // cannot set velocity, since this is calculated from position every frame - Bool isDestroyed() const { return m_status.test( OBJECT_STATUS_DESTROYED ); } ///< Returns TRUE if object has been destroyed - Bool isAirborneTarget() const { return m_status.test( OBJECT_STATUS_AIRBORNE_TARGET ); } ///< Our locomotor will control marking us as a valid target for anti air weapons or not - Bool isUsingAirborneLocomotor( void ) const; ///< returns true if the current locomotor is an airborne one - - /// central place for us to put any additional capture logic - void onCapture( Player *oldOwner, Player *newOwner ); - - /// And game death logic. Destroy is deletion of object as code - void onDie( DamageInfo *damageInfo ); - - // health and damage - void attemptDamage( DamageInfo *damageInfo ); ///< damage object as specified by the info - void attemptHealing(Real amount, const Object* source); ///< heal object as specified by the info - Bool attemptHealingFromSoleBenefactor ( Real amount, const Object* source, UnsignedInt duration );///< for the non-stacking healers like ambulance and propaganda - ObjectID getSoleHealingBenefactor( void ) const; - - Real estimateDamage( DamageInfoInput& damageInfo ) const; - void kill( DamageType damageType = DAMAGE_UNRESISTABLE, DeathType deathType = DEATH_NORMAL ); ///< kill the object with an optional type of damage and death. - void healCompletely(); ///< Restore max health to this Object - void notifySubdualDamage( Real amount );///< At this level, we just pass this on to our helper and do a special tint - void notifyChronoDamage( Real amount );///< At this level, we just pass this on to our helper and do a special tint - void doStatusDamage( ObjectStatusTypes status, Real duration );///< At this level, we just pass this on to our helper - void doTempWeaponBonus( WeaponBonusConditionType status, UnsignedInt duration, TintStatus tintStatus = TINT_STATUS_INVALID );///< At this level, we just pass this on to our helper - - void scoreTheKill( const Object *victim ); ///< I just killed this object. - void onVeterancyLevelChanged( VeterancyLevel oldLevel, VeterancyLevel newLevel, Bool provideFeedback = TRUE ); ///< I just achieved this level right this moment - ExperienceTracker* getExperienceTracker() {return m_experienceTracker;} - const ExperienceTracker* getExperienceTracker() const {return m_experienceTracker;} - VeterancyLevel getVeterancyLevel() const; - - inline const AsciiString& getName() const { return m_name; } - inline void setName( const AsciiString& newName ) { m_name = newName; } - - inline Team* getTeam() { return m_team; } - inline const Team *getTeam() const { return m_team; } - - void restoreOriginalTeam(); - - void setTeam( Team* team ); ///< sets the unit's team AND original team - void setTemporaryTeam( Team* team ); ///< sets the unit's team BUT NOT its original team - - Player* getControllingPlayer() const; - Relationship getRelationship(const Object *that) const; - - Color getIndicatorColor() const; - Color getNightIndicatorColor() const; - Bool hasCustomIndicatorColor() const { return m_indicatorColor != 0; } - void setCustomIndicatorColor(Color c); - void removeCustomIndicatorColor(); - - Bool isLocallyControlled() const; - Bool isNeutralControlled() const; - - Bool getIsUndetectedDefector(void) const { return BitIsSet(m_privateStatus, UNDETECTED_DEFECTOR); } - void friend_setUndetectedDefector(Bool status); - - inline Bool isOffMap() const { return BitIsSet(m_privateStatus, OFF_MAP); } - - inline Bool isCaptured() const { return BitIsSet(m_privateStatus, CAPTURED); } - void setCaptured(Bool isCaptured); - - inline const GeometryInfo& getGeometryInfo() const { return m_geometryInfo; } - void setGeometryInfo(const GeometryInfo& geom); - void setGeometryInfoZ( Real newZ ); - - void onCollide( Object *other, const Coord3D *loc, const Coord3D *normal ); - - Real getCarrierDeckHeight() const; - // access to modules - //----------------------------------------------------------------------------- - - //This is a good creation inspector. There's been multitudes of issues with conflicts of - //Objects getting constructed causing crashes either because the modules aren't created - //yet, and there's stuff being done inside of setTeam() that cares. - Bool areModulesReady() const { return m_modulesReady; } - - BehaviorModule** getBehaviorModules() const { return m_behaviors; } - - BodyModuleInterface* getBodyModule() const { return m_body; } - ContainModuleInterface* getContain() const { return m_contain; } - StealthUpdate* getStealth() const { return m_stealth; } - SpawnBehaviorInterface* getSpawnBehaviorInterface() const; - ProjectileUpdateInterface* getProjectileUpdateInterface() const; - - - // special case for the AIUpdateInterface, since it will be referred to a great deal - inline AIUpdateInterface *getAIUpdateInterface() { return m_ai; } - inline const AIUpdateInterface* getAIUpdateInterface() const { return m_ai; } - - inline AIUpdateInterface *getAI() { return m_ai; } - inline const AIUpdateInterface* getAI() const { return m_ai; } - - inline PhysicsBehavior* getPhysics() { return m_physics; } - inline const PhysicsBehavior* getPhysics() const { return m_physics; } - void topple( const Coord3D *toppleDirection, Real toppleSpeed, UnsignedInt options ); - - UpdateModule* findUpdateModule(NameKeyType key) const { return (UpdateModule*)findModule(key); } - DamageModule* findDamageModule(NameKeyType key) const { return (DamageModule*)findModule(key); } - - Bool isSalvageCrate() const; - - // - // Find us our production update interface if we have one. This method exists simply - // because we do this in a lot of places in the code and I want a convenient way to get thsi (CBD) - // - ProductionUpdateInterface* getProductionUpdateInterface( void ); - - // - // Find us our dock update interface if we have one. Again, this method exists simple - // because we want to do this in a lot of places throughout the code - // - DockUpdateInterface *getDockUpdateInterface( void ); - - // Ditto for special powers -- Kris - SpecialPowerModuleInterface* findSpecialPowerModuleInterface( SpecialPowerType type ) const; - SpecialPowerModuleInterface* findAnyShortcutSpecialPowerModuleInterface() const; - SpecialAbilityUpdate* findSpecialAbilityUpdate( SpecialPowerType type ) const; - SpecialPowerCompletionDie* findSpecialPowerCompletionDie() const; - SpecialPowerUpdateInterface* findSpecialPowerWithOverridableDestinationActive( SpecialPowerType type = SPECIAL_INVALID ) const; - SpecialPowerUpdateInterface* findSpecialPowerWithOverridableDestination( SpecialPowerType type = SPECIAL_INVALID ) const; - - CountermeasuresBehaviorInterface* getCountermeasuresBehaviorInterface(); - const CountermeasuresBehaviorInterface* getCountermeasuresBehaviorInterface() const; - - inline ObjectStatusMaskType getStatusBits() const { return m_status; } - inline Bool testStatus( ObjectStatusTypes bit ) const { return m_status.test( bit ); } - void setStatus( ObjectStatusMaskType objectStatus, Bool set = true ); - inline void clearStatus( ObjectStatusMaskType objectStatus ) { setStatus( objectStatus, false ); } - void updateUpgradeModules(); ///< We need to go through our Upgrade Modules and see which should be activated - UpgradeMaskType getObjectCompletedUpgradeMask() const { return m_objectUpgradesCompleted; } ///< Upgrades I complete locally - - //This function sucks. - //It was added for objects that can disguise as other objects and contain upgraded subobject overrides. - //A concrete example is the bomb truck. Different payloads are displayed based on which upgrades have been - //made. When the bomb truck disguises as something else, these subobjects are lost because the vector is - //stored in W3DDrawModule. When we revert back to the original bomb truck, we call this function to - //recalculate those upgraded subobjects. - void forceRefreshSubObjectUpgradeStatus(); - - // Useful for status bits that can be set by the scripting system - inline Bool testScriptStatusBit(ObjectScriptStatusBit b) const { return BitIsSet(m_scriptStatus, b); } - void setScriptStatus( ObjectScriptStatusBit bit, Bool set = true ); - inline void clearScriptStatus( ObjectScriptStatusBit bit ) { setScriptStatus(bit, false); } - - // Selectable is individually controlled on an object by object basis for design now. - // It defaults to the thingTemplate->isKindof(KINDOF_SELECTABLE), however, it can be overridden on an - // object by object basis. Finally, it can be temporarily overriden by the OBJECT_STATUS_UNSELECTABLE. - // jba. - void setSelectable(Bool selectable); - Bool isSelectable() const; - - Bool isMassSelectable() const; - - // User specified formation. - void setFormationID(enum FormationID id) {m_formationID = id;} - enum FormationID getFormationID(void) const {return m_formationID;} - void setFormationOffset(const Coord2D& offset) {m_formationOffset = offset;} - void getFormationOffset(Coord2D* offset) const {*offset = m_formationOffset;} - - -//THIS FUNCTION BELONGS AT THE OBJECT LEVEL BECAUSE THERE IS AT LEAST ONE SPECIAL UNIT -//(ANGRY MOB) WHICH NEEDS LOGIC-SIDE POSITION CALC'S... -//IT WOULD PROBABLY BE WISE TO MOVE ALL THE HARD-CODED DEFAULTS BELOW -//INTO A new Drawable::getHealthBox..() WHICH USES GEOM0INFO, MODEL DATA, INI DATA, ETC. - void getHealthBoxPosition(Coord3D& pos) const; - Bool getHealthBoxDimensions(Real &healthBoxHeight, Real &healthBoxWidth) const; - - inline Bool isEffectivelyDead() const { return (m_privateStatus & EFFECTIVELY_DEAD) != 0; } - void setEffectivelyDead(Bool dead); - - void markSingleUseCommandUsed() { m_singleUseCommandUsed = true; } - Bool hasSingleUseCommandBeenUsed() const { return m_singleUseCommandUsed; } - - /// returns true iff the object can run over the other object. - Bool canCrushOrSquish(Object *otherObj, CrushSquishTestType testType = TEST_CRUSH_OR_SQUISH) const; - UnsignedByte getCrusherLevel() const; - UnsignedByte getCrushableLevel() const; - - Bool hasUpgrade( const UpgradeTemplate *upgradeT ) const ; ///< does this object already have this upgrade - Bool affectedByUpgrade( const UpgradeTemplate *upgradeT ) const ; ///< can the object even "have" this upgrade, will it do something? - void giveUpgrade( const UpgradeTemplate *upgradeT ); ///< give upgrade to this object - void removeUpgrade( const UpgradeTemplate *upgradeT ); ///< remove upgrade from this object - - Bool hasCountermeasures() const; - void reportMissileForCountermeasures( Object *missile ); - ObjectID calculateCountermeasureToDivertTo( const Object& victim ); - - void calcNaturalRallyPoint(Coord2D *pt); ///< calc the "natural" starting rally point - void setConstructionPercent( Real percent ) { m_constructionPercent = percent; } - Real getConstructionPercent() const { return m_constructionPercent; } - - void setLayer( PathfindLayerEnum layer ); - PathfindLayerEnum getLayer() const { return m_layer; } - - void setDestinationLayer( PathfindLayerEnum layer ); - PathfindLayerEnum getDestinationLayer() const { return m_destinationLayer; } - - void prependToList(Object **pListHead); - void removeFromList(Object **pListHead); - Bool isInList(Object **pListHead) const; - - // this is intended for use ONLY by GameLogic. - void friend_deleteInstance() { deleteInstance(); } - - /// cache the partition module (should be called only by PartitionData) - void friend_setPartitionData(PartitionData *pd) { m_partitionData = pd; } - PartitionData *friend_getPartitionData() const { return m_partitionData; } - const PartitionData *friend_getConstPartitionData() const { return m_partitionData; } - - void onPartitionCellChange();///< We have moved a 'significant' amount, so do maintenence that can be considered 'cell-based' - void handlePartitionCellMaintenance(); ///< Undo and redo all shroud actions. Call when something has changed, like position or ownership or Death - - Real getVisionRange() const; ///< How far can you see? This is dynamic so it is in Object. - void setVisionRange( Real newVisionRange ); ///< Access to setting someone's Vision distance - Real getShroudRange() const; ///< How far can you shroud? Even more dynamic since it'll start at zero for everyone. - void setShroudRange( Real newShroudRange ); ///< Access to setting someone's shrouding distance - Real getShroudClearingRange() const; ///< How far do you clear shroud? - void setShroudClearingRange( Real newShroudClearingRange ); ///< Access to setting someone's clear shroud distance - void setVisionSpied(Bool setting, Int byWhom);///< Change who is looking through our eyes - - // Both of these calls are intended to only be used by TerrainLogic, specifically setActiveBoundary() - void friend_prepareForMapBoundaryAdjust(void); - void friend_notifyOfNewMapBoundary(void); - - // data for the radar - void friend_setRadarData( RadarObject *rd ) { m_radarData = rd; } - RadarObject *friend_getRadarData() { return m_radarData; } - RadarPriorityType getRadarPriority() const; - - // contained-by - inline Object *getContainedBy() { return m_containedBy; } - inline const Object *getContainedBy() const { return m_containedBy; } - inline UnsignedInt getContainedByFrame() const { return m_containedByFrame; } - inline Bool isContained() const { return m_containedBy != NULL; } - void onContainedBy( Object *containedBy ); - void onRemovedFrom( Object *removedFrom ); - Int getTransportSlotCount() const; - void friend_setContainedBy( Object *containedBy ) { m_containedBy = containedBy; } - - // Special Powers ------------------------------------------------------------------------------- - SpecialPowerModuleInterface *getSpecialPowerModule( const SpecialPowerTemplate *specialPowerTemplate ) const; - void doSpecialPower( const SpecialPowerTemplate *specialPowerTemplate, UnsignedInt commandOptions, Bool forced = false ); ///< execute power - void doSpecialPowerAtObject( const SpecialPowerTemplate *specialPowerTemplate, Object *obj, UnsignedInt commandOptions, Bool forced = false ); ///< execute power - void doSpecialPowerAtLocation( const SpecialPowerTemplate *specialPowerTemplate, const Coord3D *loc, Real angle, UnsignedInt commandOptions, Bool forced = false ); ///< execute power - void doSpecialPowerUsingWaypoints( const SpecialPowerTemplate *specialPowerTemplate, const Waypoint *way, UnsignedInt commandOptions, Bool forced = false ); ///< execute power - - void doCommandButton( const CommandButton *commandButton, CommandSourceType cmdSource ); - void doCommandButtonAtObject( const CommandButton *commandButton, Object *obj, CommandSourceType cmdSource ); - void doCommandButtonAtPosition( const CommandButton *commandButton, const Coord3D *pos, CommandSourceType cmdSource ); - void doCommandButtonUsingWaypoints( const CommandButton *commandButton, const Waypoint *way, CommandSourceType cmdSource ); - - /** - For Object specific dynamic command sets. Different from the Science specific ones handled in ThingTemplate - */ - const AsciiString& getCommandSetString() const; - void setCommandSetStringOverride( AsciiString newCommandSetString ) { m_commandSetStringOverride = newCommandSetString; } - - /// People are faking their commandsets, and, Surprise!, they are authoritative. Challenge everything. - Bool canProduceUpgrade( const UpgradeTemplate *upgrade ); - - - // Weapons & Damage ------------------------------------------------------------------------------------------------- - void reloadAllAmmo(Bool now); - Bool isOutOfAmmo() const; - Bool hasAnyWeapon() const; - Bool hasAnyDamageWeapon() const; //Kris: a should be used for real weapons that directly inflict damage... not deploy, hack, etc. - Bool hasWeaponToDealDamageType(DamageType typeToDeal) const; - Real getLargestWeaponRange() const; - UnsignedInt getMostPercentReadyToFireAnyWeapon() const; - - Weapon* getWeaponInWeaponSlot(WeaponSlotType wslot) const { return m_weaponSet.getWeaponInWeaponSlot(wslot); } - UnsignedInt getWeaponInWeaponSlotCommandSourceMask( WeaponSlotType wSlot ) const { return m_weaponSet.getNthCommandSourceMask( wSlot ); } - Bool getWeaponInWeaponSlotSyncedToSlot(WeaponSlotType thisSlot, WeaponSlotType otherSlot) const; - - // see if this current weapon set's weapons has shared reload times - const Bool isReloadTimeShared() const { return m_weaponSet.isSharedReloadTime(); } - - Weapon* getCurrentWeapon(WeaponSlotType* wslot = NULL); - const Weapon* getCurrentWeapon(WeaponSlotType* wslot = NULL) const; - void setFiringConditionForCurrentWeapon() const; - void adjustModelConditionForWeaponStatus(); ///< Check to see if I should change my model condition. - void fireCurrentWeapon(Object *target); - void fireCurrentWeapon(const Coord3D* pos); - void preFireCurrentWeapon( const Object *victim ); - void preFireCurrentWeapon(const Coord3D* pos); - UnsignedInt getLastShotFiredFrame() const; ///< Get the frame a shot was last fired on - ObjectID getLastVictimID() const; ///< Get the last victim we shot at - Weapon* findWaypointFollowingCapableWeapon(); - Bool getAmmoPipShowingInfo(Int& numTotal, Int& numFull) const; - - void notifyFiringTrackerShotFired( const Weapon* weaponFired, ObjectID victimID ) ; - - /** - Determines if the unit has any weapon that could conceivably - harm the victim. this does not take range, ammo, etc. into - account, but immutable weapon properties, such as "can you - target airborne victims". - */ - /* - NOTE: getAbleToAttackSpecificObject NO LONGER internally calls isAbleToAttack(), - since that isn't an incredibly fast call, and this is called repeatedly in some inner loops - where we already know that isAbleToAttack() == true. so you should always - call isAbleToAttack prior to calling this! (srj) - */ - CanAttackResult getAbleToAttackSpecificObject( AbleToAttackType t, const Object* target, CommandSourceType commandSource, WeaponSlotType specificSlot = (WeaponSlotType)-1 ) const; - - //Used for base defenses and otherwise stationary units to see if you can attack a position potentially out of range. - CanAttackResult getAbleToUseWeaponAgainstTarget( AbleToAttackType attackType, const Object *victim, const Coord3D *pos, CommandSourceType commandSource, WeaponSlotType specificSlot = (WeaponSlotType)-1 ) const; - - /** - Selects the best weapon for the given target, and sets it as the current weapon. - If there is no weapon that can damage the target, false is returned (and the current-weapon is unchanged). - Note that this DOES take weapon attack range into account. - */ - Bool chooseBestWeaponForTarget(const Object* target, WeaponChoiceCriteria criteria, CommandSourceType cmdSource); - - // set and/or clear a single modelcondition flag - void setModelConditionState( ModelConditionFlagType a ); - void clearModelConditionState( ModelConditionFlagType a ); - void clearAndSetModelConditionState( ModelConditionFlagType clr, ModelConditionFlagType set ); - - //Special model states are states that are turned on for a period of time, and turned off - //automatically -- used for cheer, and scripted special moment animations. Setting a special - //state will automatically clear any other special states that may be turned on so you can only - //have one at a time. - void setSpecialModelConditionState( ModelConditionFlagType set, UnsignedInt frames = 0 ); - void clearSpecialModelConditionStates(); - - // set and/or clear multiple modelcondition flags - void clearModelConditionFlags( const ModelConditionFlags& clr ); - void setModelConditionFlags( const ModelConditionFlags& set ); - void clearAndSetModelConditionFlags( const ModelConditionFlags& clr, const ModelConditionFlags& set ); - - void setWeaponSetFlag(WeaponSetType wst); - void clearWeaponSetFlag(WeaponSetType wst); - inline Bool testWeaponSetFlag(WeaponSetType wst) const { return m_curWeaponSetFlags.test(wst); } - inline const WeaponSetFlags& getWeaponSetFlags() const { return m_curWeaponSetFlags; } - Bool setWeaponLock( WeaponSlotType weaponSlot, WeaponLockType lockType ){ return m_weaponSet.setWeaponLock( weaponSlot, lockType ); } - void releaseWeaponLock(WeaponLockType lockType){ m_weaponSet.releaseWeaponLock(lockType); } - Bool isCurWeaponLocked() const { return m_weaponSet.isCurWeaponLocked(); } - - void setArmorSetFlag(ArmorSetType ast); - void clearArmorSetFlag(ArmorSetType ast); - Bool testArmorSetFlag(ArmorSetType ast) const; - - /// return true if the template has the specified special power flag set - // @todo: inline - Bool hasSpecialPower( SpecialPowerType type ) const; - Bool hasAnySpecialPower() const; - - void setWeaponBonusCondition(WeaponBonusConditionType wst); - void clearWeaponBonusCondition(WeaponBonusConditionType wst); - - // note, the !=0 at the end is important, to convert this into a boolean type! (srj) - Bool testWeaponBonusCondition(WeaponBonusConditionType wst) const { return (m_weaponBonusCondition & (1 << wst)) != 0; } - inline WeaponBonusConditionFlags getWeaponBonusCondition() const { return m_weaponBonusCondition; } - inline void setWeaponBonusConditionFlags(WeaponBonusConditionFlags flags) { m_weaponBonusCondition = flags; } - - Bool getSingleLogicalBonePosition(const char* boneName, Coord3D* position, Matrix3D* transform) const; - Bool getSingleLogicalBonePositionOnTurret(WhichTurretType whichTurret, const char* boneName, Coord3D* position, Matrix3D* transform) const; - Int getMultiLogicalBonePosition(const char* boneNamePrefix, Int maxBones, Coord3D* positions, Matrix3D* transforms, Bool convertToWorld = TRUE ) const; - - // Entered & exited. - Bool didEnter(const PolygonTrigger *pTrigger) const; - Bool didExit(const PolygonTrigger *pTrigger) const; - Bool isInside(const PolygonTrigger *pTrigger) const; - - // exiting of any kind - ExitInterface *getObjectExitInterface() const; ///< get exit interface is present - Bool hasExitInterface() const { return getObjectExitInterface() != 0; } - - ObjectShroudStatus getShroudedStatus(Int playerIndex) const; - - DisabledMaskType getDisabledFlags() const { return m_disabledMask; } - Bool isDisabled() const { return m_disabledMask.any(); } - Bool clearDisabled( DisabledType type ); - - void setDisabled( DisabledType type ); - void setDisabledUntil( DisabledType type, UnsignedInt frame ); - Bool isDisabledByType( DisabledType type ) const { return TEST_DISABLEDMASK( m_disabledMask, type ); } - - UnsignedInt getDisabledUntil( DisabledType type = DISABLED_ANY ) const; - - void pauseAllSpecialPowers( const Bool disabling ) const; - - //Checks any timers and clears disabled statii that have expired. - void checkDisabledStatus(); - - //When an AIAttackState is over, it needs to clean up any weapons that might be in leech range mode - //or else those weapons will have unlimited range! - void clearLeechRangeModeForAllWeapons(); - - Int getNumConsecutiveShotsFiredAtTarget( const Object *victim) const; - - void setHealthBoxOffset( const Coord3D& offset ) { m_healthBoxOffset = offset; } ///< for special amorphous like angry mob - - void defect( Team *newTeam, UnsignedInt detectionTime ); - void goInvulnerable( UnsignedInt time ); - - // This is public, since there is no Thing level master setting of Turret stuff. It is all done in a sleepy hamlet - // of a module called TurretAI. - virtual void reactToTurretChange( WhichTurretType turret, Real oldRotation, Real oldPitch ); - - // Convenience function for checking certain kindof bits - Bool isStructure(void) const; - - // Convenience function for checking certain kindof bits - Bool isFactionStructure(void) const; - - // Convenience function for checking certain kindof bits - Bool isNonFactionStructure(void) const; - - Bool isHero(void) const; - - Bool getReceivingDifficultyBonus() const { return m_isReceivingDifficultyBonus; } - void setReceivingDifficultyBonus(Bool receive); - - inline UnsignedInt getSafeOcclusionFrame(void) { return m_safeOcclusionFrame; } //< this is an object specific frame at which it's safe to enable building occlusion. - inline void setSafeOcclusionFrame(UnsignedInt frame) { m_safeOcclusionFrame = frame;} - - // All of our cheating for radars and power go here. - // This is the function that we now call in becomingTeamMember to adjust our power. - // If incoming is true, we're working on the incoming player, if its false, we're on the outgoing - // player. These are friend_s for player. - void friend_adjustPowerForPlayer( Bool incoming ); - -protected: - - void setOrRestoreTeam( Team* team, Bool restoring ); - - void onDisabledEdge(Bool becomingDisabled); - // All of our cheating for radars and power go here. - - - // snapshot methods - void crc( Xfer *xfer ); - void xfer( Xfer *xfer ); - void loadPostProcess(); - - void handleShroud(); - void handleValueMap(); - void handleThreatMap(); - - // NOTE NOTE NOTE -- this is a private method. Do Not Ever Make It Public. - // If you think you need to make it public, you are wrong. Don't do it. - // It will go away someday. Yeah, right. Just like GlobalData. - Module* findModule(NameKeyType key) const; - - Bool didEnterOrExit() const; - - void setID( ObjectID id ); - virtual Object *asObjectMeth() { return this; } - virtual const Object *asObjectMeth() const { return this; } - - virtual Real calculateHeightAboveTerrain(void) const; // Calculates the actual height above terrain. Doesn't use cache. - - void updateTriggerAreaFlags(void); - void setTriggerAreaFlagsForChangeInPosition(void); - - /// Look and unlook are protected. They should be called from Object::reasonToLook. Like Capture, or death. - void look(); - void unlook(); - void shroud(); - void unshroud(); - - /// value and threat functions are protected, and should only be called from handleValueMap - void addValue(); - void removeValue(); - - void addThreat(); - void removeThreat(); - - virtual void reactToTransformChange(const Matrix3D* oldMtx, const Coord3D* oldPos, Real oldAngle); - -private: - - // yes, private. No, really. Private. Don't expose. - enum ObjectPrivateStatusBits - { - EFFECTIVELY_DEAD = (1 << 0), ///< Object is effectively dead - UNDETECTED_DEFECTOR = (1 << 1), ///< set to true when I defect from my team; set to false when I attack anything or when time runs out - CAPTURED = (1 << 2), ///< set to true if I've been captured, otherwise, its false. (Note: Never becomes false once it's true) - OFF_MAP = (1 << 3) ///< set to true if I am known to be OFF the current map. - // NOTE: Object currently only uses a Byte for this, so if you add status bits, you may need to enlarge that field. - }; - - ObjectID m_id; ///< this object's unique ID - ObjectID m_producerID; ///< object that produced us, if any - ObjectID m_builderID; ///< object that is building or has built us (dozers or workers are builders) - Drawable* m_drawable; ///< drawable (if any) for this object - AsciiString m_name; ///< internal name - - Object * m_next; - Object * m_prev; - ObjectStatusMaskType m_status; ///< status bits (see ObjectStatusMaskType) - - GeometryInfo m_geometryInfo; - - AIGroup* m_group; ///< if non-NULL, we are part of this group of agents - - // These will last for my lifetime. I will reuse them and reset them. The truly dynamic ones are in PartitionManager - SightingInfo *m_partitionLastLook; ///< Where and for whom I last looked, so I can undo its effects when I stop - SightingInfo *m_partitionRevealAllLastLook; ///< And a seperate look to reveal at a different range if so marked - Int m_visionSpiedBy[MAX_PLAYER_COUNT]; ///< Reference count of having units spied on by players. - PlayerMaskType m_visionSpiedMask; ///< For quick lookup and edge triggered maintenance - - SightingInfo *m_partitionLastShroud; ///< Where and for whom I last shrouded, so I can undo its effects when I stop - SightingInfo *m_partitionLastThreat; ///< Where and for whom I last delt with threat, so I can undo its effects when I stop - SightingInfo *m_partitionLastValue; ///< Where and for whom I last delt with value, so I can undo its effects when I stop - - Real m_visionRange; ///< looking range - Real m_shroudClearingRange; ///< looking range for shroud ONLY - Real m_shroudRange; ///< like looking range, this is how far I shroud others' looks - - DisabledMaskType m_disabledMask; - UnsignedInt m_disabledTillFrame[ DISABLED_COUNT ]; - - UnsignedInt m_smcUntil; - - enum { NUM_SLEEP_HELPERS = 8 }; - ObjectRepulsorHelper* m_repulsorHelper; - ObjectSMCHelper* m_smcHelper; - ObjectWeaponStatusHelper* m_wsHelper; - ObjectDefectionHelper* m_defectionHelper; - StatusDamageHelper* m_statusDamageHelper; - SubdualDamageHelper* m_subdualDamageHelper; - ChronoDamageHelper* m_chronoDamageHelper; - TempWeaponBonusHelper* m_tempWeaponBonusHelper; - FiringTracker* m_firingTracker; ///< Tracker is really a "helper" and is included NUM_SLEEP_HELPERS - - // modules - BehaviorModule** m_behaviors; // BehaviorModule, not BehaviorModuleInterface - - // cache these, for convenience - ContainModuleInterface* m_contain; - BodyModuleInterface* m_body; - StealthUpdate* m_stealth; - - AIUpdateInterface* m_ai; ///< ai interface (if any), cached for handy access. (duplicate of entry in the module array!) - PhysicsBehavior* m_physics; ///< physics interface (if any), cached for handy access. (duplicate of entry in the module array!) - - PartitionData* m_partitionData; ///< our PartitionData - RadarObject* m_radarData; ///< radar data - ExperienceTracker* m_experienceTracker; ///< Manages experience, gaining levels, and value when killed - - Object* m_containedBy; /**< an object can only be contained by at most one - other object, this is that object (if present) */ - ObjectID m_xferContainedByID; ///< xfer uses IDs to store pointers and looks them up after - UnsignedInt m_containedByFrame; ///< frame we were contained by m_containedBy - - Real m_constructionPercent; ///< for objects being built ... this is the amount completed (0.0 to 100.0) - UpgradeMaskType m_objectUpgradesCompleted; ///< Bit field of upgrades locally completed. - - Team* m_team; ///< team that is current owner of this guy - AsciiString m_originalTeamName; ///< team that was the original ("birth") team of this guy - Color m_indicatorColor; ///< if nonzero, use this instead of controlling player's color - - Coord3D m_healthBoxOffset; ///< generally zero, except for special amorphous ones like angry mob - - /// @todo srj -- convert to non-DLINK list, after it is once again possible to test the change - MAKE_DLINK(Object, TeamMemberList) ///< other Things that are members of the same team - - // Weapons & Damage ------------------------------------------------------------------------------------------------- - WeaponSet m_weaponSet; - WeaponSetFlags m_curWeaponSetFlags; - WeaponBonusConditionFlags m_weaponBonusCondition; - Byte m_lastWeaponCondition[WEAPONSLOT_COUNT]; - - SpecialPowerMaskType m_specialPowerBits; ///< bits determining what kind of special abilities this object has access to. - - //////////////////////////////////////< for the non-stacking healers like ambulance and propaganda - ObjectID m_soleHealingBenefactorID; ///< who is the only other object that can give me this non-stacking heal benefit? - UnsignedInt m_soleHealingBenefactorExpirationFrame; ///< on what frame can I accept healing (thus to switch) from a new benefactor - /////////////////////////////////// - - // Entered & exited housekeeping. - enum { MAX_TRIGGER_AREA_INFOS = 5 }; - TTriggerInfo m_triggerInfo[MAX_TRIGGER_AREA_INFOS]; - UnsignedInt m_enteredOrExitedFrame; - ICoord3D m_iPos; - - PathfindLayerEnum m_layer; // Layer object is pathing on. - PathfindLayerEnum m_destinationLayer; // Layer of current path goal. - - // User formations. - FormationID m_formationID; - Coord2D m_formationOffset; - - AsciiString m_commandSetStringOverride;///< To allow specific object to switch command sets - - UnsignedInt m_safeOcclusionFrame; ///. +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// Object.h /////////////////////////////////////////////////////////////////// +// Simple base object +// Author: Michael S. Booth, October 2000 + +#pragma once +#ifndef _OBJECT_H_ +#define _OBJECT_H_ + +#include "Lib/BaseType.h" + +#include "Common/Geometry.h" +#include "Common/Snapshot.h" +#include "Common/SpecialPowerMaskType.h" +#include "Common/DisabledTypes.h" +#include "Common/Thing.h" +#include "Common/ObjectStatusTypes.h" +#include "Common/Upgrade.h" + +#include "GameClient/Color.h" + +#include "GameLogic/Damage.h" //for kill() +#include "GameLogic/WeaponBonusConditionFlags.h" +#include "GameLogic/WeaponSet.h" +#include "GameLogic/WeaponSetFlags.h" +#include "GameLogic/Module/StealthUpdate.h" + +//----------------------------------------------------------------------------- +// Forward References +//----------------------------------------------------------------------------- + +class AIGroup; +class AIUpdateInterface; +class Anim2DTemplate; +class BehaviorModule; +class BehaviorModuleInterface; +class BodyModuleInterface; +class CollideModule; +class CollideModuleInterface; +class CommandButton; +class ContainModuleInterface; +class CountermeasuresBehaviorInterface; +class CreateModuleInterface; +class DamageInfo; +class DamageInfoInput; +class DamageModule; +class DamageModuleInterface; +class DestroyModuleInterface; +class DockUpdateInterface; +class Dict; +class DieModule; +class DieModuleInterface; +class ExitInterface; +class ExperienceTracker; +class FiringTracker; +class Module; +class PartitionData; +class PhysicsBehavior; +class PhysicsUpdate; +class Player; +class PolygonTrigger; +class ProductionUpdateInterface; +class ProjectileUpdateInterface; +class RadarObject; +class SightingInfo; +class SpawnBehaviorInterface; +class SpecialAbilityUpdate; +class SpecialPowerCompletionDie; +class SpecialPowerModuleInterface; +class SpecialPowerTemplate; +class SpecialPowerUpdateInterface; +class Team; +class UpdateModule; +class UpdateModuleInterface; +class UpgradeModule; +class UpgradeModuleInterface; +class UpgradeTemplate; + +class ObjectHeldHelper; +class ObjectDisabledHelper; +class ObjectSMCHelper; +class ObjectRepulsorHelper; +class StatusDamageHelper; +class SubdualDamageHelper; +class ChronoDamageHelper; +class TempWeaponBonusHelper; +class ObjectWeaponStatusHelper; +class ObjectDefectionHelper; + +enum CommandSourceType CPP_11(: Int); +enum HackerAttackMode CPP_11(: Int); +enum NameKeyType CPP_11(: Int); +enum SpecialPowerType CPP_11(: Int); +enum WeaponBonusConditionType CPP_11(: Int); +enum WeaponChoiceCriteria CPP_11(: Int); +enum WeaponSetConditionType CPP_11(: Int); +enum WeaponSetType CPP_11(: Int); +enum ArmorSetType CPP_11(: Int); +enum WeaponStatus CPP_11(: Int); +enum RadarPriorityType CPP_11(: Int); +enum CanAttackResult CPP_11(: Int); +// enum TintStatus CPP_11(: Int); + +// For ObjectStatusTypes +#include "Common/ObjectStatusTypes.h" + +// For ObjectScriptStatusBit +#include "GameLogic/ObjectScriptStatusBits.h" + +// For TintStatus +#include "GameClient/TintStatus.h" + +//----------------------------------------------------------------------------- +// Type Defines +//----------------------------------------------------------------------------- + +struct TTriggerInfo +{ + const PolygonTrigger* pTrigger; ///< The trigger area that the object is inside. + Byte entered; ///< True if the object entered this trigger area this frame. + Byte exited; ///< True if the object entered this trigger area this frame. + Byte isInside; ///< True if the object is inside this trigger area this frame. + Byte padding; ///< unused. + + TTriggerInfo() : entered(false), exited(false), isInside(false), padding(false), pTrigger(NULL) { } + +}; + +//---------------------------------------------------- + + +enum CrushSquishTestType CPP_11(: Int) +{ + TEST_CRUSH_ONLY, + TEST_SQUISH_ONLY, + TEST_CRUSH_OR_SQUISH +}; + + +// --------------------------------------------------- +/** + * Object definition. Objects are manipulated via TheGameLogic singleton. + * @todo Create an ObjectInterface class. + */ +class Object : public Thing, public Snapshot +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE(Object, "ObjectPool" ) + /// destructor is non-public in order to require the use of TheGameLogic->destroyObject() + MEMORY_POOL_DELETEINSTANCE_VISIBILITY(protected) + +public: + + /// Object constructor automatically attaches all objects to "TheGameLogic" + Object(const ThingTemplate *thing, const ObjectStatusMaskType &objectStatusMask, Team *team); + + void initObject(); + + void onDestroy(); ///< run during TheGameLogic::destroyObject + + Object* getNextObject() { return m_next; } + const Object* getNextObject() const { return m_next; } + + void updateObjValuesFromMapProperties(Dict* properties); ///< Brings in properties set in the editor. + + // ids and binding + ObjectID getID() const { return m_id; } ///< this object's unique ID + void friend_bindToDrawable( Drawable *draw ); ///< set drawable association. for use ONLY by GameLogic! + Drawable* getDrawable() const { return m_drawable; } ///< drawable (if any) bound to obj + + ObjectID getProducerID() const { return m_producerID; } + void setProducer(const Object* obj); + + ObjectID getBuilderID() const { return m_builderID; } + void setBuilder( const Object *obj ); + + void enterGroup( AIGroup *group ); ///< become a member of the AIGroup + void leaveGroup( void ); ///< leave our current AIGroup + AIGroup *getGroup(void); + + // physical properties + Bool isMobile() const; ///< returns true if object is currently able to move + Bool isAbleToAttack() const; ///< returns true if object currently has some kind of attack capability + + void maskObject( Bool mask ); ///< mask/unmask object + + /** + Booby traps are set off by many random actions, so those actions are responsible for calling this. + Return value is if a booby trap was set off, so caller can react. + Those actions are: planting any type of bomb, entering, starting to capture, dying. + */ + Bool checkAndDetonateBoobyTrap(const Object *victim); + + // cannot set velocity, since this is calculated from position every frame + Bool isDestroyed() const { return m_status.test( OBJECT_STATUS_DESTROYED ); } ///< Returns TRUE if object has been destroyed + Bool isAirborneTarget() const { return m_status.test( OBJECT_STATUS_AIRBORNE_TARGET ); } ///< Our locomotor will control marking us as a valid target for anti air weapons or not + Bool isUsingAirborneLocomotor( void ) const; ///< returns true if the current locomotor is an airborne one + + /// central place for us to put any additional capture logic + void onCapture( Player *oldOwner, Player *newOwner ); + + /// And game death logic. Destroy is deletion of object as code + void onDie( DamageInfo *damageInfo ); + + // health and damage + void attemptDamage( DamageInfo *damageInfo ); ///< damage object as specified by the info + void attemptHealing(Real amount, const Object* source); ///< heal object as specified by the info + Bool attemptHealingFromSoleBenefactor ( Real amount, const Object* source, UnsignedInt duration );///< for the non-stacking healers like ambulance and propaganda + ObjectID getSoleHealingBenefactor( void ) const; + + Real estimateDamage( DamageInfoInput& damageInfo ) const; + void kill( DamageType damageType = DAMAGE_UNRESISTABLE, DeathType deathType = DEATH_NORMAL ); ///< kill the object with an optional type of damage and death. + void healCompletely(); ///< Restore max health to this Object + void notifySubdualDamage( Real amount );///< At this level, we just pass this on to our helper and do a special tint + void notifyChronoDamage( Real amount );///< At this level, we just pass this on to our helper and do a special tint + void doStatusDamage( ObjectStatusTypes status, Real duration );///< At this level, we just pass this on to our helper + void doTempWeaponBonus( WeaponBonusConditionType status, UnsignedInt duration, TintStatus tintStatus = TINT_STATUS_INVALID );///< At this level, we just pass this on to our helper + + void scoreTheKill( const Object *victim ); ///< I just killed this object. + void onVeterancyLevelChanged( VeterancyLevel oldLevel, VeterancyLevel newLevel, Bool provideFeedback = TRUE ); ///< I just achieved this level right this moment + ExperienceTracker* getExperienceTracker() {return m_experienceTracker;} + const ExperienceTracker* getExperienceTracker() const {return m_experienceTracker;} + VeterancyLevel getVeterancyLevel() const; + + inline const AsciiString& getName() const { return m_name; } + inline void setName( const AsciiString& newName ) { m_name = newName; } + + inline Team* getTeam() { return m_team; } + inline const Team *getTeam() const { return m_team; } + + void restoreOriginalTeam(); + + void setTeam( Team* team ); ///< sets the unit's team AND original team + void setTemporaryTeam( Team* team ); ///< sets the unit's team BUT NOT its original team + + Player* getControllingPlayer() const; + Relationship getRelationship(const Object *that) const; + + Color getIndicatorColor() const; + Color getNightIndicatorColor() const; + Bool hasCustomIndicatorColor() const { return m_indicatorColor != 0; } + void setCustomIndicatorColor(Color c); + void removeCustomIndicatorColor(); + + Bool isLocallyControlled() const; + Bool isNeutralControlled() const; + + Bool getIsUndetectedDefector(void) const { return BitIsSet(m_privateStatus, UNDETECTED_DEFECTOR); } + void friend_setUndetectedDefector(Bool status); + + inline Bool isOffMap() const { return BitIsSet(m_privateStatus, OFF_MAP); } + + inline Bool isCaptured() const { return BitIsSet(m_privateStatus, CAPTURED); } + void setCaptured(Bool isCaptured); + + inline const GeometryInfo& getGeometryInfo() const { return m_geometryInfo; } + void setGeometryInfo(const GeometryInfo& geom); + void setGeometryInfoZ( Real newZ ); + + void onCollide( Object *other, const Coord3D *loc, const Coord3D *normal ); + + Real getCarrierDeckHeight() const; + // access to modules + //----------------------------------------------------------------------------- + + //This is a good creation inspector. There's been multitudes of issues with conflicts of + //Objects getting constructed causing crashes either because the modules aren't created + //yet, and there's stuff being done inside of setTeam() that cares. + Bool areModulesReady() const { return m_modulesReady; } + + BehaviorModule** getBehaviorModules() const { return m_behaviors; } + + BodyModuleInterface* getBodyModule() const { return m_body; } + ContainModuleInterface* getContain() const { return m_contain; } + StealthUpdate* getStealth() const { return m_stealth; } + SpawnBehaviorInterface* getSpawnBehaviorInterface() const; + ProjectileUpdateInterface* getProjectileUpdateInterface() const; + + + // special case for the AIUpdateInterface, since it will be referred to a great deal + inline AIUpdateInterface *getAIUpdateInterface() { return m_ai; } + inline const AIUpdateInterface* getAIUpdateInterface() const { return m_ai; } + + inline AIUpdateInterface *getAI() { return m_ai; } + inline const AIUpdateInterface* getAI() const { return m_ai; } + + inline PhysicsBehavior* getPhysics() { return m_physics; } + inline const PhysicsBehavior* getPhysics() const { return m_physics; } + void topple( const Coord3D *toppleDirection, Real toppleSpeed, UnsignedInt options ); + + UpdateModule* findUpdateModule(NameKeyType key) const { return (UpdateModule*)findModule(key); } + DamageModule* findDamageModule(NameKeyType key) const { return (DamageModule*)findModule(key); } + + Bool isSalvageCrate() const; + + // + // Find us our production update interface if we have one. This method exists simply + // because we do this in a lot of places in the code and I want a convenient way to get thsi (CBD) + // + ProductionUpdateInterface* getProductionUpdateInterface( void ); + + // + // Find us our dock update interface if we have one. Again, this method exists simple + // because we want to do this in a lot of places throughout the code + // + DockUpdateInterface *getDockUpdateInterface( void ); + + // Ditto for special powers -- Kris + SpecialPowerModuleInterface* findSpecialPowerModuleInterface( SpecialPowerType type ) const; + SpecialPowerModuleInterface* findAnyShortcutSpecialPowerModuleInterface() const; + SpecialAbilityUpdate* findSpecialAbilityUpdate( SpecialPowerType type ) const; + SpecialPowerCompletionDie* findSpecialPowerCompletionDie() const; + SpecialPowerUpdateInterface* findSpecialPowerWithOverridableDestinationActive( SpecialPowerType type = SPECIAL_INVALID ) const; + SpecialPowerUpdateInterface* findSpecialPowerWithOverridableDestination( SpecialPowerType type = SPECIAL_INVALID ) const; + + CountermeasuresBehaviorInterface* getCountermeasuresBehaviorInterface(); + const CountermeasuresBehaviorInterface* getCountermeasuresBehaviorInterface() const; + + inline ObjectStatusMaskType getStatusBits() const { return m_status; } + inline Bool testStatus( ObjectStatusTypes bit ) const { return m_status.test( bit ); } + void setStatus( ObjectStatusMaskType objectStatus, Bool set = true ); + inline void clearStatus( ObjectStatusMaskType objectStatus ) { setStatus( objectStatus, false ); } + void updateUpgradeModules(); ///< We need to go through our Upgrade Modules and see which should be activated + UpgradeMaskType getObjectCompletedUpgradeMask() const { return m_objectUpgradesCompleted; } ///< Upgrades I complete locally + + //This function sucks. + //It was added for objects that can disguise as other objects and contain upgraded subobject overrides. + //A concrete example is the bomb truck. Different payloads are displayed based on which upgrades have been + //made. When the bomb truck disguises as something else, these subobjects are lost because the vector is + //stored in W3DDrawModule. When we revert back to the original bomb truck, we call this function to + //recalculate those upgraded subobjects. + void forceRefreshSubObjectUpgradeStatus(); + + // Useful for status bits that can be set by the scripting system + inline Bool testScriptStatusBit(ObjectScriptStatusBit b) const { return BitIsSet(m_scriptStatus, b); } + void setScriptStatus( ObjectScriptStatusBit bit, Bool set = true ); + inline void clearScriptStatus( ObjectScriptStatusBit bit ) { setScriptStatus(bit, false); } + + // Selectable is individually controlled on an object by object basis for design now. + // It defaults to the thingTemplate->isKindof(KINDOF_SELECTABLE), however, it can be overridden on an + // object by object basis. Finally, it can be temporarily overriden by the OBJECT_STATUS_UNSELECTABLE. + // jba. + void setSelectable(Bool selectable); + Bool isSelectable() const; + + Bool isMassSelectable() const; + + // User specified formation. + void setFormationID(enum FormationID id) {m_formationID = id;} + enum FormationID getFormationID(void) const {return m_formationID;} + void setFormationOffset(const Coord2D& offset) {m_formationOffset = offset;} + void getFormationOffset(Coord2D* offset) const {*offset = m_formationOffset;} + + +//THIS FUNCTION BELONGS AT THE OBJECT LEVEL BECAUSE THERE IS AT LEAST ONE SPECIAL UNIT +//(ANGRY MOB) WHICH NEEDS LOGIC-SIDE POSITION CALC'S... +//IT WOULD PROBABLY BE WISE TO MOVE ALL THE HARD-CODED DEFAULTS BELOW +//INTO A new Drawable::getHealthBox..() WHICH USES GEOM0INFO, MODEL DATA, INI DATA, ETC. + void getHealthBoxPosition(Coord3D& pos) const; + Bool getHealthBoxDimensions(Real &healthBoxHeight, Real &healthBoxWidth) const; + + inline Bool isEffectivelyDead() const { return (m_privateStatus & EFFECTIVELY_DEAD) != 0; } + void setEffectivelyDead(Bool dead); + + void markSingleUseCommandUsed() { m_singleUseCommandUsed = true; } + Bool hasSingleUseCommandBeenUsed() const { return m_singleUseCommandUsed; } + + /// returns true iff the object can run over the other object. + Bool canCrushOrSquish(Object *otherObj, CrushSquishTestType testType = TEST_CRUSH_OR_SQUISH) const; + UnsignedByte getCrusherLevel() const; + UnsignedByte getCrushableLevel() const; + + Bool hasUpgrade( const UpgradeTemplate *upgradeT ) const ; ///< does this object already have this upgrade + Bool affectedByUpgrade( const UpgradeTemplate *upgradeT ) const ; ///< can the object even "have" this upgrade, will it do something? + void giveUpgrade( const UpgradeTemplate *upgradeT ); ///< give upgrade to this object + void removeUpgrade( const UpgradeTemplate *upgradeT ); ///< remove upgrade from this object + + Bool hasCountermeasures() const; + void reportMissileForCountermeasures( Object *missile ); + ObjectID calculateCountermeasureToDivertTo( const Object& victim ); + + void calcNaturalRallyPoint(Coord2D *pt); ///< calc the "natural" starting rally point + void setConstructionPercent( Real percent ) { m_constructionPercent = percent; } + Real getConstructionPercent() const { return m_constructionPercent; } + + void setLayer( PathfindLayerEnum layer ); + PathfindLayerEnum getLayer() const { return m_layer; } + + void setDestinationLayer( PathfindLayerEnum layer ); + PathfindLayerEnum getDestinationLayer() const { return m_destinationLayer; } + + void prependToList(Object **pListHead); + void removeFromList(Object **pListHead); + Bool isInList(Object **pListHead) const; + + // this is intended for use ONLY by GameLogic. + void friend_deleteInstance() { deleteInstance(); } + + /// cache the partition module (should be called only by PartitionData) + void friend_setPartitionData(PartitionData *pd) { m_partitionData = pd; } + PartitionData *friend_getPartitionData() const { return m_partitionData; } + const PartitionData *friend_getConstPartitionData() const { return m_partitionData; } + + void onPartitionCellChange();///< We have moved a 'significant' amount, so do maintenence that can be considered 'cell-based' + void handlePartitionCellMaintenance(); ///< Undo and redo all shroud actions. Call when something has changed, like position or ownership or Death + + Real getVisionRange() const; ///< How far can you see? This is dynamic so it is in Object. + void setVisionRange( Real newVisionRange ); ///< Access to setting someone's Vision distance + Real getShroudRange() const; ///< How far can you shroud? Even more dynamic since it'll start at zero for everyone. + void setShroudRange( Real newShroudRange ); ///< Access to setting someone's shrouding distance + Real getShroudClearingRange() const; ///< How far do you clear shroud? + void setShroudClearingRange( Real newShroudClearingRange ); ///< Access to setting someone's clear shroud distance + void setVisionSpied(Bool setting, Int byWhom);///< Change who is looking through our eyes + + // Both of these calls are intended to only be used by TerrainLogic, specifically setActiveBoundary() + void friend_prepareForMapBoundaryAdjust(void); + void friend_notifyOfNewMapBoundary(void); + + // data for the radar + void friend_setRadarData( RadarObject *rd ) { m_radarData = rd; } + RadarObject *friend_getRadarData() { return m_radarData; } + RadarPriorityType getRadarPriority() const; + + // contained-by + inline Object *getContainedBy() { return m_containedBy; } + inline const Object *getContainedBy() const { return m_containedBy; } + inline UnsignedInt getContainedByFrame() const { return m_containedByFrame; } + inline Bool isContained() const { return m_containedBy != NULL; } + void onContainedBy( Object *containedBy ); + void onRemovedFrom( Object *removedFrom ); + Int getTransportSlotCount() const; + void friend_setContainedBy( Object *containedBy ) { m_containedBy = containedBy; } + + // Special Powers ------------------------------------------------------------------------------- + SpecialPowerModuleInterface *getSpecialPowerModule( const SpecialPowerTemplate *specialPowerTemplate ) const; + void doSpecialPower( const SpecialPowerTemplate *specialPowerTemplate, UnsignedInt commandOptions, Bool forced = false ); ///< execute power + void doSpecialPowerAtObject( const SpecialPowerTemplate *specialPowerTemplate, Object *obj, UnsignedInt commandOptions, Bool forced = false ); ///< execute power + void doSpecialPowerAtLocation( const SpecialPowerTemplate *specialPowerTemplate, const Coord3D *loc, Real angle, UnsignedInt commandOptions, Bool forced = false ); ///< execute power + void doSpecialPowerUsingWaypoints( const SpecialPowerTemplate *specialPowerTemplate, const Waypoint *way, UnsignedInt commandOptions, Bool forced = false ); ///< execute power + + void doCommandButton( const CommandButton *commandButton, CommandSourceType cmdSource ); + void doCommandButtonAtObject( const CommandButton *commandButton, Object *obj, CommandSourceType cmdSource ); + void doCommandButtonAtPosition( const CommandButton *commandButton, const Coord3D *pos, CommandSourceType cmdSource ); + void doCommandButtonUsingWaypoints( const CommandButton *commandButton, const Waypoint *way, CommandSourceType cmdSource ); + + /** + For Object specific dynamic command sets. Different from the Science specific ones handled in ThingTemplate + */ + const AsciiString& getCommandSetString() const; + void setCommandSetStringOverride( AsciiString newCommandSetString ) { m_commandSetStringOverride = newCommandSetString; } + + /// People are faking their commandsets, and, Surprise!, they are authoritative. Challenge everything. + Bool canProduceUpgrade( const UpgradeTemplate *upgrade ); + + + // Weapons & Damage ------------------------------------------------------------------------------------------------- + void reloadAllAmmo(Bool now); + Bool isOutOfAmmo() const; + Bool hasAnyWeapon() const; + Bool hasAnyDamageWeapon() const; //Kris: a should be used for real weapons that directly inflict damage... not deploy, hack, etc. + Bool hasWeaponToDealDamageType(DamageType typeToDeal) const; + Real getLargestWeaponRange() const; + UnsignedInt getMostPercentReadyToFireAnyWeapon() const; + + Weapon* getWeaponInWeaponSlot(WeaponSlotType wslot) const { return m_weaponSet.getWeaponInWeaponSlot(wslot); } + UnsignedInt getWeaponInWeaponSlotCommandSourceMask( WeaponSlotType wSlot ) const { return m_weaponSet.getNthCommandSourceMask( wSlot ); } + Bool getWeaponInWeaponSlotSyncedToSlot(WeaponSlotType thisSlot, WeaponSlotType otherSlot) const; + + // see if this current weapon set's weapons has shared reload times + const Bool isReloadTimeShared() const { return m_weaponSet.isSharedReloadTime(); } + + Weapon* getCurrentWeapon(WeaponSlotType* wslot = NULL); + const Weapon* getCurrentWeapon(WeaponSlotType* wslot = NULL) const; + void setFiringConditionForCurrentWeapon() const; + void adjustModelConditionForWeaponStatus(); ///< Check to see if I should change my model condition. + void fireCurrentWeapon(Object *target); + void fireCurrentWeapon(const Coord3D* pos); + void preFireCurrentWeapon( const Object *victim ); + void preFireCurrentWeapon(const Coord3D* pos); + UnsignedInt getLastShotFiredFrame() const; ///< Get the frame a shot was last fired on + ObjectID getLastVictimID() const; ///< Get the last victim we shot at + Weapon* findWaypointFollowingCapableWeapon(); + Bool getAmmoPipShowingInfo(Int& numTotal, Int& numFull) const; + + void notifyFiringTrackerShotFired( const Weapon* weaponFired, ObjectID victimID ) ; + + /** + Determines if the unit has any weapon that could conceivably + harm the victim. this does not take range, ammo, etc. into + account, but immutable weapon properties, such as "can you + target airborne victims". + */ + /* + NOTE: getAbleToAttackSpecificObject NO LONGER internally calls isAbleToAttack(), + since that isn't an incredibly fast call, and this is called repeatedly in some inner loops + where we already know that isAbleToAttack() == true. so you should always + call isAbleToAttack prior to calling this! (srj) + */ + CanAttackResult getAbleToAttackSpecificObject( AbleToAttackType t, const Object* target, CommandSourceType commandSource, WeaponSlotType specificSlot = (WeaponSlotType)-1 ) const; + + //Used for base defenses and otherwise stationary units to see if you can attack a position potentially out of range. + CanAttackResult getAbleToUseWeaponAgainstTarget( AbleToAttackType attackType, const Object *victim, const Coord3D *pos, CommandSourceType commandSource, WeaponSlotType specificSlot = (WeaponSlotType)-1 ) const; + + /** + Selects the best weapon for the given target, and sets it as the current weapon. + If there is no weapon that can damage the target, false is returned (and the current-weapon is unchanged). + Note that this DOES take weapon attack range into account. + */ + Bool chooseBestWeaponForTarget(const Object* target, WeaponChoiceCriteria criteria, CommandSourceType cmdSource); + + // set and/or clear a single modelcondition flag + void setModelConditionState( ModelConditionFlagType a ); + void clearModelConditionState( ModelConditionFlagType a ); + void clearAndSetModelConditionState( ModelConditionFlagType clr, ModelConditionFlagType set ); + + //Special model states are states that are turned on for a period of time, and turned off + //automatically -- used for cheer, and scripted special moment animations. Setting a special + //state will automatically clear any other special states that may be turned on so you can only + //have one at a time. + void setSpecialModelConditionState( ModelConditionFlagType set, UnsignedInt frames = 0 ); + void clearSpecialModelConditionStates(); + + // set and/or clear multiple modelcondition flags + void clearModelConditionFlags( const ModelConditionFlags& clr ); + void setModelConditionFlags( const ModelConditionFlags& set ); + void clearAndSetModelConditionFlags( const ModelConditionFlags& clr, const ModelConditionFlags& set ); + + void setWeaponSetFlag(WeaponSetType wst); + void clearWeaponSetFlag(WeaponSetType wst); + inline Bool testWeaponSetFlag(WeaponSetType wst) const { return m_curWeaponSetFlags.test(wst); } + inline const WeaponSetFlags& getWeaponSetFlags() const { return m_curWeaponSetFlags; } + Bool setWeaponLock( WeaponSlotType weaponSlot, WeaponLockType lockType ){ return m_weaponSet.setWeaponLock( weaponSlot, lockType ); } + void releaseWeaponLock(WeaponLockType lockType){ m_weaponSet.releaseWeaponLock(lockType); } + Bool isCurWeaponLocked() const { return m_weaponSet.isCurWeaponLocked(); } + + void setArmorSetFlag(ArmorSetType ast); + void clearArmorSetFlag(ArmorSetType ast); + Bool testArmorSetFlag(ArmorSetType ast) const; + + /// return true if the template has the specified special power flag set + // @todo: inline + Bool hasSpecialPower( SpecialPowerType type ) const; + Bool hasAnySpecialPower() const; + + void setWeaponBonusCondition(WeaponBonusConditionType wst); + void clearWeaponBonusCondition(WeaponBonusConditionType wst); + + // note, the !=0 at the end is important, to convert this into a boolean type! (srj) + Bool testWeaponBonusCondition(WeaponBonusConditionType wst) const { return (m_weaponBonusCondition & (1 << wst)) != 0; } + inline WeaponBonusConditionFlags getWeaponBonusCondition() const { return m_weaponBonusCondition; } + inline void setWeaponBonusConditionFlags(WeaponBonusConditionFlags flags) { m_weaponBonusCondition = flags; } + + Bool getSingleLogicalBonePosition(const char* boneName, Coord3D* position, Matrix3D* transform) const; + Bool getSingleLogicalBonePositionOnTurret(WhichTurretType whichTurret, const char* boneName, Coord3D* position, Matrix3D* transform) const; + Int getMultiLogicalBonePosition(const char* boneNamePrefix, Int maxBones, Coord3D* positions, Matrix3D* transforms, Bool convertToWorld = TRUE ) const; + + // Entered & exited. + Bool didEnter(const PolygonTrigger *pTrigger) const; + Bool didExit(const PolygonTrigger *pTrigger) const; + Bool isInside(const PolygonTrigger *pTrigger) const; + + // exiting of any kind + ExitInterface *getObjectExitInterface() const; ///< get exit interface is present + Bool hasExitInterface() const { return getObjectExitInterface() != 0; } + + ObjectShroudStatus getShroudedStatus(Int playerIndex) const; + + DisabledMaskType getDisabledFlags() const { return m_disabledMask; } + Bool isDisabled() const { return m_disabledMask.any(); } + Bool clearDisabled( DisabledType type ); + + void setDisabled( DisabledType type ); + void setDisabledUntil( DisabledType type, UnsignedInt frame ); + Bool isDisabledByType( DisabledType type ) const { return TEST_DISABLEDMASK( m_disabledMask, type ); } + + UnsignedInt getDisabledUntil( DisabledType type = DISABLED_ANY ) const; + + void pauseAllSpecialPowers( const Bool disabling ) const; + + //Checks any timers and clears disabled statii that have expired. + void checkDisabledStatus(); + + //When an AIAttackState is over, it needs to clean up any weapons that might be in leech range mode + //or else those weapons will have unlimited range! + void clearLeechRangeModeForAllWeapons(); + + Int getNumConsecutiveShotsFiredAtTarget( const Object *victim) const; + + void setHealthBoxOffset( const Coord3D& offset ) { m_healthBoxOffset = offset; } ///< for special amorphous like angry mob + + void defect( Team *newTeam, UnsignedInt detectionTime ); + void goInvulnerable( UnsignedInt time ); + + // This is public, since there is no Thing level master setting of Turret stuff. It is all done in a sleepy hamlet + // of a module called TurretAI. + virtual void reactToTurretChange( WhichTurretType turret, Real oldRotation, Real oldPitch ); + + // Convenience function for checking certain kindof bits + Bool isStructure(void) const; + + // Convenience function for checking certain kindof bits + Bool isFactionStructure(void) const; + + // Convenience function for checking certain kindof bits + Bool isNonFactionStructure(void) const; + + Bool isHero(void) const; + + Bool getReceivingDifficultyBonus() const { return m_isReceivingDifficultyBonus; } + void setReceivingDifficultyBonus(Bool receive); + + inline UnsignedInt getSafeOcclusionFrame(void) { return m_safeOcclusionFrame; } //< this is an object specific frame at which it's safe to enable building occlusion. + inline void setSafeOcclusionFrame(UnsignedInt frame) { m_safeOcclusionFrame = frame;} + + // All of our cheating for radars and power go here. + // This is the function that we now call in becomingTeamMember to adjust our power. + // If incoming is true, we're working on the incoming player, if its false, we're on the outgoing + // player. These are friend_s for player. + void friend_adjustPowerForPlayer( Bool incoming ); + +protected: + + void setOrRestoreTeam( Team* team, Bool restoring ); + + void onDisabledEdge(Bool becomingDisabled); + // All of our cheating for radars and power go here. + + + // snapshot methods + void crc( Xfer *xfer ); + void xfer( Xfer *xfer ); + void loadPostProcess(); + + void handleShroud(); + void handleValueMap(); + void handleThreatMap(); + + // NOTE NOTE NOTE -- this is a private method. Do Not Ever Make It Public. + // If you think you need to make it public, you are wrong. Don't do it. + // It will go away someday. Yeah, right. Just like GlobalData. + Module* findModule(NameKeyType key) const; + + Bool didEnterOrExit() const; + + void setID( ObjectID id ); + virtual Object *asObjectMeth() { return this; } + virtual const Object *asObjectMeth() const { return this; } + + virtual Real calculateHeightAboveTerrain(void) const; // Calculates the actual height above terrain. Doesn't use cache. + + void updateTriggerAreaFlags(void); + void setTriggerAreaFlagsForChangeInPosition(void); + + /// Look and unlook are protected. They should be called from Object::reasonToLook. Like Capture, or death. + void look(); + void unlook(); + void shroud(); + void unshroud(); + + /// value and threat functions are protected, and should only be called from handleValueMap + void addValue(); + void removeValue(); + + void addThreat(); + void removeThreat(); + + virtual void reactToTransformChange(const Matrix3D* oldMtx, const Coord3D* oldPos, Real oldAngle); + +private: + + // yes, private. No, really. Private. Don't expose. + enum ObjectPrivateStatusBits + { + EFFECTIVELY_DEAD = (1 << 0), ///< Object is effectively dead + UNDETECTED_DEFECTOR = (1 << 1), ///< set to true when I defect from my team; set to false when I attack anything or when time runs out + CAPTURED = (1 << 2), ///< set to true if I've been captured, otherwise, its false. (Note: Never becomes false once it's true) + OFF_MAP = (1 << 3) ///< set to true if I am known to be OFF the current map. + // NOTE: Object currently only uses a Byte for this, so if you add status bits, you may need to enlarge that field. + }; + + ObjectID m_id; ///< this object's unique ID + ObjectID m_producerID; ///< object that produced us, if any + ObjectID m_builderID; ///< object that is building or has built us (dozers or workers are builders) + Drawable* m_drawable; ///< drawable (if any) for this object + AsciiString m_name; ///< internal name + + Object * m_next; + Object * m_prev; + ObjectStatusMaskType m_status; ///< status bits (see ObjectStatusMaskType) + + GeometryInfo m_geometryInfo; + + AIGroup* m_group; ///< if non-NULL, we are part of this group of agents + + // These will last for my lifetime. I will reuse them and reset them. The truly dynamic ones are in PartitionManager + SightingInfo *m_partitionLastLook; ///< Where and for whom I last looked, so I can undo its effects when I stop + SightingInfo *m_partitionRevealAllLastLook; ///< And a seperate look to reveal at a different range if so marked + Int m_visionSpiedBy[MAX_PLAYER_COUNT]; ///< Reference count of having units spied on by players. + PlayerMaskType m_visionSpiedMask; ///< For quick lookup and edge triggered maintenance + + SightingInfo *m_partitionLastShroud; ///< Where and for whom I last shrouded, so I can undo its effects when I stop + SightingInfo *m_partitionLastThreat; ///< Where and for whom I last delt with threat, so I can undo its effects when I stop + SightingInfo *m_partitionLastValue; ///< Where and for whom I last delt with value, so I can undo its effects when I stop + + Real m_visionRange; ///< looking range + Real m_shroudClearingRange; ///< looking range for shroud ONLY + Real m_shroudRange; ///< like looking range, this is how far I shroud others' looks + + DisabledMaskType m_disabledMask; + UnsignedInt m_disabledTillFrame[ DISABLED_COUNT ]; + + UnsignedInt m_smcUntil; + + enum { NUM_SLEEP_HELPERS = 8 }; + ObjectRepulsorHelper* m_repulsorHelper; + ObjectSMCHelper* m_smcHelper; + ObjectWeaponStatusHelper* m_wsHelper; + ObjectDefectionHelper* m_defectionHelper; + StatusDamageHelper* m_statusDamageHelper; + SubdualDamageHelper* m_subdualDamageHelper; + ChronoDamageHelper* m_chronoDamageHelper; + TempWeaponBonusHelper* m_tempWeaponBonusHelper; + FiringTracker* m_firingTracker; ///< Tracker is really a "helper" and is included NUM_SLEEP_HELPERS + + // modules + BehaviorModule** m_behaviors; // BehaviorModule, not BehaviorModuleInterface + + // cache these, for convenience + ContainModuleInterface* m_contain; + BodyModuleInterface* m_body; + StealthUpdate* m_stealth; + + AIUpdateInterface* m_ai; ///< ai interface (if any), cached for handy access. (duplicate of entry in the module array!) + PhysicsBehavior* m_physics; ///< physics interface (if any), cached for handy access. (duplicate of entry in the module array!) + + PartitionData* m_partitionData; ///< our PartitionData + RadarObject* m_radarData; ///< radar data + ExperienceTracker* m_experienceTracker; ///< Manages experience, gaining levels, and value when killed + + Object* m_containedBy; /**< an object can only be contained by at most one + other object, this is that object (if present) */ + ObjectID m_xferContainedByID; ///< xfer uses IDs to store pointers and looks them up after + UnsignedInt m_containedByFrame; ///< frame we were contained by m_containedBy + + Real m_constructionPercent; ///< for objects being built ... this is the amount completed (0.0 to 100.0) + UpgradeMaskType m_objectUpgradesCompleted; ///< Bit field of upgrades locally completed. + + Team* m_team; ///< team that is current owner of this guy + AsciiString m_originalTeamName; ///< team that was the original ("birth") team of this guy + Color m_indicatorColor; ///< if nonzero, use this instead of controlling player's color + + Coord3D m_healthBoxOffset; ///< generally zero, except for special amorphous ones like angry mob + + /// @todo srj -- convert to non-DLINK list, after it is once again possible to test the change + MAKE_DLINK(Object, TeamMemberList) ///< other Things that are members of the same team + + // Weapons & Damage ------------------------------------------------------------------------------------------------- + WeaponSet m_weaponSet; + WeaponSetFlags m_curWeaponSetFlags; + WeaponBonusConditionFlags m_weaponBonusCondition; + Byte m_lastWeaponCondition[WEAPONSLOT_COUNT]; + + SpecialPowerMaskType m_specialPowerBits; ///< bits determining what kind of special abilities this object has access to. + + //////////////////////////////////////< for the non-stacking healers like ambulance and propaganda + ObjectID m_soleHealingBenefactorID; ///< who is the only other object that can give me this non-stacking heal benefit? + UnsignedInt m_soleHealingBenefactorExpirationFrame; ///< on what frame can I accept healing (thus to switch) from a new benefactor + /////////////////////////////////// + + // Entered & exited housekeeping. + enum { MAX_TRIGGER_AREA_INFOS = 5 }; + TTriggerInfo m_triggerInfo[MAX_TRIGGER_AREA_INFOS]; + UnsignedInt m_enteredOrExitedFrame; + ICoord3D m_iPos; + + PathfindLayerEnum m_layer; // Layer object is pathing on. + PathfindLayerEnum m_destinationLayer; // Layer of current path goal. + + // User formations. + FormationID m_formationID; + Coord2D m_formationOffset; + + AsciiString m_commandSetStringOverride;///< To allow specific object to switch command sets + + UnsignedInt m_safeOcclusionFrame; ///. -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: GlobalData.cpp /////////////////////////////////////////////////////////////////////////// -// The GameLogicData object -// Author: trolfs, Michael Booth, Colin Day, April 2001 -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//#pragma once - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine - -#define DEFINE_TERRAIN_LOD_NAMES -#define DEFINE_TIME_OF_DAY_NAMES -#define DEFINE_WEATHER_NAMES -#define DEFINE_BODYDAMAGETYPE_NAMES -#define DEFINE_PANNING_NAMES - -#include "Common/crc.h" -#include "Common/file.h" -#include "Common/FileSystem.h" -#include "Common/GameAudio.h" -#include "Common/INI.h" -#include "Common/Registry.h" -#include "Common/UserPreferences.h" -#include "Common/version.h" - -#include "GameLogic/AI.h" -#include "GameLogic/Weapon.h" -#include "GameLogic/Module/BodyModule.h" - -#include "GameClient/Color.h" -#include "GameClient/TerrainVisual.h" -#include "GameClient/TintStatus.h" - -#include "GameNetwork/FirewallHelper.h" - -// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// -GlobalData* TheWritableGlobalData = NULL; ///< The global data singleton - -//------------------------------------------------------------------------------------------------- -GlobalData* GlobalData::m_theOriginal = NULL; - - - -//------------------------------------------------------------------------------------------------- -/*static*/ void GlobalData::parseTintStatusType(INI* ini, void* instance, void* store, const void* userData) -{ - TintStatus tintType = (TintStatus)INI::scanIndexList(ini->getNextToken(), TintStatusFlags::getBitNames()); - - DrawableColorTint* colorTintTypes = (DrawableColorTint*)(store); - DrawableColorTint* tintEntry = &colorTintTypes[tintType]; - - INI::parseRGBColorReal(ini, instance, &tintEntry->color, NULL); - INI::parseRGBColorReal(ini, instance, &tintEntry->colorInfantry, NULL); - - INI::parseUnsignedInt(ini, instance, &tintEntry->attackFrames, NULL); - INI::parseUnsignedInt(ini, instance, &tintEntry->decayFrames, NULL); -} - - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -/*static*/ const FieldParse GlobalData::s_GlobalDataFieldParseTable[] = -{ - { "Windowed", INI::parseBool, NULL, offsetof( GlobalData, m_windowed ) }, - { "XResolution", INI::parseInt, NULL, offsetof( GlobalData, m_xResolution ) }, - { "YResolution", INI::parseInt, NULL, offsetof( GlobalData, m_yResolution ) }, - { "MapName", INI::parseAsciiString,NULL, offsetof( GlobalData, m_mapName ) }, - { "MoveHintName", INI::parseAsciiString,NULL, offsetof( GlobalData, m_moveHintName ) }, - { "UseTrees", INI::parseBool, NULL, offsetof( GlobalData, m_useTrees ) }, - { "UseFPSLimit", INI::parseBool, NULL, offsetof( GlobalData, m_useFpsLimit ) }, - { "DumpAssetUsage", INI::parseBool, NULL, offsetof( GlobalData, m_dumpAssetUsage ) }, - { "FramesPerSecondLimit", INI::parseInt, NULL, offsetof( GlobalData, m_framesPerSecondLimit ) }, - { "ChipsetType", INI::parseInt, NULL, offsetof( GlobalData, m_chipSetType ) }, - { "MaxShellScreens", INI::parseInt, NULL, offsetof( GlobalData, m_maxShellScreens ) }, - { "UseCloudMap", INI::parseBool, NULL, offsetof( GlobalData, m_useCloudMap ) }, - { "UseLightMap", INI::parseBool, NULL, offsetof( GlobalData, m_useLightMap ) }, - { "BilinearTerrainTex", INI::parseBool, NULL, offsetof( GlobalData, m_bilinearTerrainTex ) }, - { "TrilinearTerrainTex", INI::parseBool, NULL, offsetof( GlobalData, m_trilinearTerrainTex ) }, - { "MultiPassTerrain", INI::parseBool, NULL, offsetof( GlobalData, m_multiPassTerrain ) }, - { "AdjustCliffTextures", INI::parseBool, NULL, offsetof( GlobalData, m_adjustCliffTextures ) }, - { "Use3WayTerrainBlends", INI::parseInt, NULL, offsetof( GlobalData, m_use3WayTerrainBlends ) }, - { "StretchTerrain", INI::parseBool, NULL, offsetof( GlobalData, m_stretchTerrain ) }, - { "UseHalfHeightMap", INI::parseBool, NULL, offsetof( GlobalData, m_useHalfHeightMap ) }, - - - { "DrawEntireTerrain", INI::parseBool, NULL, offsetof( GlobalData, m_drawEntireTerrain ) }, - { "TerrainLOD", INI::parseIndexList, TerrainLODNames, offsetof( GlobalData, m_terrainLOD ) }, - { "TerrainLODTargetTimeMS", INI::parseInt, NULL, offsetof( GlobalData, m_terrainLODTargetTimeMS ) }, - { "RightMouseAlwaysScrolls", INI::parseBool, NULL, offsetof( GlobalData, m_rightMouseAlwaysScrolls ) }, - { "UseWaterPlane", INI::parseBool, NULL, offsetof( GlobalData, m_useWaterPlane ) }, - { "UseCloudPlane", INI::parseBool, NULL, offsetof( GlobalData, m_useCloudPlane ) }, - { "DownwindAngle", INI::parseReal, NULL, offsetof( GlobalData, m_downwindAngle ) }, - { "UseShadowVolumes", INI::parseBool, NULL, offsetof( GlobalData, m_useShadowVolumes ) }, - { "UseShadowDecals", INI::parseBool, NULL, offsetof( GlobalData, m_useShadowDecals ) }, - { "TextureReductionFactor", INI::parseInt, NULL, offsetof( GlobalData, m_textureReductionFactor ) }, - { "UseBehindBuildingMarker", INI::parseBool, NULL, offsetof( GlobalData, m_enableBehindBuildingMarkers ) }, - { "WaterPositionX", INI::parseReal, NULL, offsetof( GlobalData, m_waterPositionX ) }, - { "WaterPositionY", INI::parseReal, NULL, offsetof( GlobalData, m_waterPositionY ) }, - { "WaterPositionZ", INI::parseReal, NULL, offsetof( GlobalData, m_waterPositionZ ) }, - { "WaterExtentX", INI::parseReal, NULL, offsetof( GlobalData, m_waterExtentX ) }, - { "WaterExtentY", INI::parseReal, NULL, offsetof( GlobalData, m_waterExtentY ) }, - { "WaterType", INI::parseInt, NULL, offsetof( GlobalData, m_waterType ) }, - { "FeatherWater", INI::parseInt, NULL, offsetof( GlobalData, m_featherWater ) }, - { "ShowSoftWaterEdge", INI::parseBool, NULL, offsetof( GlobalData, m_showSoftWaterEdge ) }, - - // nasty ick, we need to save this data with a map and not hard code INI values - { "VertexWaterAvailableMaps1", INI::parseAsciiString, NULL, offsetof( GlobalData, m_vertexWaterAvailableMaps[ 0 ] ) }, - { "VertexWaterHeightClampLow1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampLow[ 0 ] ) }, - { "VertexWaterHeightClampHi1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampHi[ 0 ] ) }, - { "VertexWaterAngle1", INI::parseAngleReal, NULL, offsetof( GlobalData, m_vertexWaterAngle[ 0 ] ) }, - { "VertexWaterXPosition1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterXPosition[ 0 ] ) }, - { "VertexWaterYPosition1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterYPosition[ 0 ] ) }, - { "VertexWaterZPosition1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterZPosition[ 0 ] ) }, - { "VertexWaterXGridCells1", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterXGridCells[ 0 ] ) }, - { "VertexWaterYGridCells1", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterYGridCells[ 0 ] ) }, - { "VertexWaterGridSize1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterGridSize[ 0 ] ) }, - { "VertexWaterAttenuationA1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationA[ 0 ] ) }, - { "VertexWaterAttenuationB1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationB[ 0 ] ) }, - { "VertexWaterAttenuationC1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationC[ 0 ] ) }, - { "VertexWaterAttenuationRange1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationRange[ 0 ] ) }, - - // nasty ick, we need to save this data with a map and not hard code INI values - { "VertexWaterAvailableMaps2", INI::parseAsciiString, NULL, offsetof( GlobalData, m_vertexWaterAvailableMaps[ 1 ] ) }, - { "VertexWaterHeightClampLow2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampLow[ 1 ] ) }, - { "VertexWaterHeightClampHi2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampHi[ 1 ] ) }, - { "VertexWaterAngle2", INI::parseAngleReal, NULL, offsetof( GlobalData, m_vertexWaterAngle[ 1 ] ) }, - { "VertexWaterXPosition2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterXPosition[ 1 ] ) }, - { "VertexWaterYPosition2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterYPosition[ 1 ] ) }, - { "VertexWaterZPosition2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterZPosition[ 1 ] ) }, - { "VertexWaterXGridCells2", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterXGridCells[ 1 ] ) }, - { "VertexWaterYGridCells2", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterYGridCells[ 1 ] ) }, - { "VertexWaterGridSize2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterGridSize[ 1 ] ) }, - { "VertexWaterAttenuationA2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationA[ 1 ] ) }, - { "VertexWaterAttenuationB2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationB[ 1 ] ) }, - { "VertexWaterAttenuationC2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationC[ 1 ] ) }, - { "VertexWaterAttenuationRange2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationRange[ 1 ] ) }, - - // nasty ick, we need to save this data with a map and not hard code INI values - { "VertexWaterAvailableMaps3", INI::parseAsciiString, NULL, offsetof( GlobalData, m_vertexWaterAvailableMaps[ 2 ] ) }, - { "VertexWaterHeightClampLow3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampLow[ 2 ] ) }, - { "VertexWaterHeightClampHi3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampHi[ 2 ] ) }, - { "VertexWaterAngle3", INI::parseAngleReal, NULL, offsetof( GlobalData, m_vertexWaterAngle[ 2 ] ) }, - { "VertexWaterXPosition3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterXPosition[ 2 ] ) }, - { "VertexWaterYPosition3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterYPosition[ 2 ] ) }, - { "VertexWaterZPosition3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterZPosition[ 2 ] ) }, - { "VertexWaterXGridCells3", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterXGridCells[ 2 ] ) }, - { "VertexWaterYGridCells3", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterYGridCells[ 2 ] ) }, - { "VertexWaterGridSize3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterGridSize[ 2 ] ) }, - { "VertexWaterAttenuationA3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationA[ 2 ] ) }, - { "VertexWaterAttenuationB3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationB[ 2 ] ) }, - { "VertexWaterAttenuationC3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationC[ 2 ] ) }, - { "VertexWaterAttenuationRange3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationRange[ 2 ] ) }, - - // nasty ick, we need to save this data with a map and not hard code INI values - { "VertexWaterAvailableMaps4", INI::parseAsciiString, NULL, offsetof( GlobalData, m_vertexWaterAvailableMaps[ 3 ] ) }, - { "VertexWaterHeightClampLow4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampLow[ 3 ] ) }, - { "VertexWaterHeightClampHi4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampHi[ 3 ] ) }, - { "VertexWaterAngle4", INI::parseAngleReal, NULL, offsetof( GlobalData, m_vertexWaterAngle[ 3 ] ) }, - { "VertexWaterXPosition4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterXPosition[ 3 ] ) }, - { "VertexWaterYPosition4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterYPosition[ 3 ] ) }, - { "VertexWaterZPosition4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterZPosition[ 3 ] ) }, - { "VertexWaterXGridCells4", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterXGridCells[ 3 ] ) }, - { "VertexWaterYGridCells4", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterYGridCells[ 3 ] ) }, - { "VertexWaterGridSize4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterGridSize[ 3 ] ) }, - { "VertexWaterAttenuationA4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationA[ 3 ] ) }, - { "VertexWaterAttenuationB4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationB[ 3 ] ) }, - { "VertexWaterAttenuationC4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationC[ 3 ] ) }, - { "VertexWaterAttenuationRange4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationRange[ 3 ] ) }, - - { "SkyBoxPositionZ", INI::parseReal, NULL, offsetof( GlobalData, m_skyBoxPositionZ ) }, - { "SkyBoxScale", INI::parseReal, NULL, offsetof( GlobalData, m_skyBoxScale ) }, - { "DrawSkyBox", INI::parseBool, NULL, offsetof( GlobalData, m_drawSkyBox ) }, - { "CameraPitch", INI::parseReal, NULL, offsetof( GlobalData, m_cameraPitch ) }, - { "CameraYaw", INI::parseReal, NULL, offsetof( GlobalData, m_cameraYaw ) }, - { "CameraHeight", INI::parseReal, NULL, offsetof( GlobalData, m_cameraHeight ) }, - { "MaxCameraHeight", INI::parseReal, NULL, offsetof( GlobalData, m_maxCameraHeight ) }, - { "MinCameraHeight", INI::parseReal, NULL, offsetof( GlobalData, m_minCameraHeight ) }, - { "TerrainHeightAtEdgeOfMap", INI::parseReal, NULL, offsetof( GlobalData, m_terrainHeightAtEdgeOfMap ) }, - { "UnitDamagedThreshold", INI::parseReal, NULL, offsetof( GlobalData, m_unitDamagedThresh ) }, - { "UnitReallyDamagedThreshold", INI::parseReal, NULL, offsetof( GlobalData, m_unitReallyDamagedThresh ) }, - { "GroundStiffness", INI::parseReal, NULL, offsetof( GlobalData, m_groundStiffness ) }, - { "StructureStiffness", INI::parseReal, NULL, offsetof( GlobalData, m_structureStiffness ) }, - { "Gravity", INI::parseAccelerationReal, NULL, offsetof( GlobalData, m_gravity ) }, - { "StealthFriendlyOpacity", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_stealthFriendlyOpacity ) }, - { "DefaultOcclusionDelay", INI::parseDurationUnsignedInt, NULL, offsetof( GlobalData, m_defaultOcclusionDelay ) }, - - { "PartitionCellSize", INI::parseReal, NULL, offsetof( GlobalData, m_partitionCellSize ) }, - - { "AmmoPipScaleFactor", INI::parseReal, NULL, offsetof( GlobalData, m_ammoPipScaleFactor ) }, - { "ContainerPipScaleFactor", INI::parseReal, NULL, offsetof( GlobalData, m_containerPipScaleFactor ) }, - { "AmmoPipWorldOffset", INI::parseCoord3D, NULL, offsetof( GlobalData, m_ammoPipWorldOffset ) }, - { "ContainerPipWorldOffset", INI::parseCoord3D, NULL, offsetof( GlobalData, m_containerPipWorldOffset ) }, - { "AmmoPipScreenOffset", INI::parseCoord2D, NULL, offsetof( GlobalData, m_ammoPipScreenOffset ) }, - { "ContainerPipScreenOffset", INI::parseCoord2D, NULL, offsetof( GlobalData, m_containerPipScreenOffset ) }, - - { "HistoricDamageLimit", INI::parseDurationUnsignedInt, NULL, offsetof( GlobalData, m_historicDamageLimit ) }, - - { "MaxTerrainTracks", INI::parseInt, NULL, offsetof( GlobalData, m_maxTerrainTracks ) }, - { "TimeOfDay", INI::parseIndexList, TimeOfDayNames, offsetof( GlobalData, m_timeOfDay ) }, - { "Weather", INI::parseIndexList, WeatherNames, offsetof( GlobalData, m_weather ) }, - { "MakeTrackMarks", INI::parseBool, NULL, offsetof( GlobalData, m_makeTrackMarks ) }, - { "HideGarrisonFlags", INI::parseBool, NULL, offsetof( GlobalData, m_hideGarrisonFlags ) }, - { "ForceModelsToFollowTimeOfDay", INI::parseBool, NULL, offsetof( GlobalData, m_forceModelsToFollowTimeOfDay ) }, - { "ForceModelsToFollowWeather", INI::parseBool, NULL, offsetof( GlobalData, m_forceModelsToFollowWeather ) }, - - { "LevelGainAnimationName", INI::parseAsciiString, NULL, offsetof( GlobalData, m_levelGainAnimationName ) }, - { "LevelGainAnimationTime", INI::parseReal, NULL, offsetof( GlobalData, m_levelGainAnimationDisplayTimeInSeconds ) }, - { "LevelGainAnimationZRise", INI::parseReal, NULL, offsetof( GlobalData, m_levelGainAnimationZRisePerSecond ) }, - - { "GetHealedAnimationName", INI::parseAsciiString, NULL, offsetof( GlobalData, m_getHealedAnimationName ) }, - { "GetHealedAnimationTime", INI::parseReal, NULL, offsetof( GlobalData, m_getHealedAnimationDisplayTimeInSeconds ) }, - { "GetHealedAnimationZRise", INI::parseReal, NULL, offsetof( GlobalData, m_getHealedAnimationZRisePerSecond ) }, - - { "TerrainLightingMorningAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][0].ambient ) }, - { "TerrainLightingMorningDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][0].diffuse ) }, - { "TerrainLightingMorningLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][0].lightPos ) }, - { "TerrainLightingAfternoonAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][0].ambient ) }, - { "TerrainLightingAfternoonDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][0].diffuse ) }, - { "TerrainLightingAfternoonLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][0].lightPos ) }, - { "TerrainLightingEveningAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][0].ambient ) }, - { "TerrainLightingEveningDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][0].diffuse ) }, - { "TerrainLightingEveningLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][0].lightPos ) }, - { "TerrainLightingNightAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][0].ambient ) }, - { "TerrainLightingNightDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][0].diffuse ) }, - { "TerrainLightingNightLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][0].lightPos ) }, - - { "TerrainObjectsLightingMorningAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][0].ambient ) }, - { "TerrainObjectsLightingMorningDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][0].diffuse ) }, - { "TerrainObjectsLightingMorningLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][0].lightPos ) }, - { "TerrainObjectsLightingAfternoonAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][0].ambient ) }, - { "TerrainObjectsLightingAfternoonDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][0].diffuse ) }, - { "TerrainObjectsLightingAfternoonLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][0].lightPos ) }, - { "TerrainObjectsLightingEveningAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][0].ambient ) }, - { "TerrainObjectsLightingEveningDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][0].diffuse ) }, - { "TerrainObjectsLightingEveningLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][0].lightPos ) }, - { "TerrainObjectsLightingNightAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][0].ambient ) }, - { "TerrainObjectsLightingNightDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][0].diffuse ) }, - { "TerrainObjectsLightingNightLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][0].lightPos ) }, - - //Secondary global light - { "TerrainLightingMorningAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][1].ambient ) }, - { "TerrainLightingMorningDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][1].diffuse ) }, - { "TerrainLightingMorningLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][1].lightPos ) }, - { "TerrainLightingAfternoonAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][1].ambient ) }, - { "TerrainLightingAfternoonDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][1].diffuse ) }, - { "TerrainLightingAfternoonLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][1].lightPos ) }, - { "TerrainLightingEveningAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][1].ambient ) }, - { "TerrainLightingEveningDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][1].diffuse ) }, - { "TerrainLightingEveningLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][1].lightPos ) }, - { "TerrainLightingNightAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][1].ambient ) }, - { "TerrainLightingNightDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][1].diffuse ) }, - { "TerrainLightingNightLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][1].lightPos ) }, - - { "TerrainObjectsLightingMorningAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][1].ambient ) }, - { "TerrainObjectsLightingMorningDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][1].diffuse ) }, - { "TerrainObjectsLightingMorningLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][1].lightPos ) }, - { "TerrainObjectsLightingAfternoonAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][1].ambient ) }, - { "TerrainObjectsLightingAfternoonDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][1].diffuse ) }, - { "TerrainObjectsLightingAfternoonLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][1].lightPos ) }, - { "TerrainObjectsLightingEveningAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][1].ambient ) }, - { "TerrainObjectsLightingEveningDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][1].diffuse ) }, - { "TerrainObjectsLightingEveningLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][1].lightPos ) }, - { "TerrainObjectsLightingNightAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][1].ambient ) }, - { "TerrainObjectsLightingNightDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][1].diffuse ) }, - { "TerrainObjectsLightingNightLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][1].lightPos ) }, - - //Third global light - { "TerrainLightingMorningAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][2].ambient ) }, - { "TerrainLightingMorningDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][2].diffuse ) }, - { "TerrainLightingMorningLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][2].lightPos ) }, - { "TerrainLightingAfternoonAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][2].ambient ) }, - { "TerrainLightingAfternoonDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][2].diffuse ) }, - { "TerrainLightingAfternoonLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][2].lightPos ) }, - { "TerrainLightingEveningAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][2].ambient ) }, - { "TerrainLightingEveningDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][2].diffuse ) }, - { "TerrainLightingEveningLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][2].lightPos ) }, - { "TerrainLightingNightAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][2].ambient ) }, - { "TerrainLightingNightDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][2].diffuse ) }, - { "TerrainLightingNightLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][2].lightPos ) }, - - { "TerrainObjectsLightingMorningAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][2].ambient ) }, - { "TerrainObjectsLightingMorningDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][2].diffuse ) }, - { "TerrainObjectsLightingMorningLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][2].lightPos ) }, - { "TerrainObjectsLightingAfternoonAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][2].ambient ) }, - { "TerrainObjectsLightingAfternoonDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][2].diffuse ) }, - { "TerrainObjectsLightingAfternoonLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][2].lightPos ) }, - { "TerrainObjectsLightingEveningAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][2].ambient ) }, - { "TerrainObjectsLightingEveningDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][2].diffuse ) }, - { "TerrainObjectsLightingEveningLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][2].lightPos ) }, - { "TerrainObjectsLightingNightAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][2].ambient ) }, - { "TerrainObjectsLightingNightDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][2].diffuse ) }, - { "TerrainObjectsLightingNightLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][2].lightPos ) }, - - - { "NumberGlobalLights", INI::parseInt, NULL, offsetof( GlobalData, m_numGlobalLights)}, - { "InfantryLightMorningScale", INI::parseReal, NULL, offsetof( GlobalData, m_infantryLightScale[TIME_OF_DAY_MORNING] ) }, - { "InfantryLightAfternoonScale", INI::parseReal, NULL, offsetof( GlobalData, m_infantryLightScale[TIME_OF_DAY_AFTERNOON] ) }, - { "InfantryLightEveningScale", INI::parseReal, NULL, offsetof( GlobalData, m_infantryLightScale[TIME_OF_DAY_EVENING] ) }, - { "InfantryLightNightScale", INI::parseReal, NULL, offsetof( GlobalData, m_infantryLightScale[TIME_OF_DAY_NIGHT] ) }, - - { "MaxTranslucentObjects", INI::parseInt, NULL, offsetof( GlobalData, m_maxVisibleTranslucentObjects) }, - { "OccludedColorLuminanceScale", INI::parseReal, NULL, offsetof( GlobalData, m_occludedLuminanceScale) }, - -/* These are internal use only, they do not need file definitons - { "TerrainAmbientRGB", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainAmbient ) }, - { "TerrainDiffuseRGB", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainDiffuse ) }, - { "TerrainLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLightPos ) }, -*/ - { "MaxRoadSegments", INI::parseInt, NULL, offsetof( GlobalData, m_maxRoadSegments ) }, - { "MaxRoadVertex", INI::parseInt, NULL, offsetof( GlobalData, m_maxRoadVertex ) }, - { "MaxRoadIndex", INI::parseInt, NULL, offsetof( GlobalData, m_maxRoadIndex ) }, - { "MaxRoadTypes", INI::parseInt, NULL, offsetof( GlobalData, m_maxRoadTypes ) }, - - { "ValuePerSupplyBox", INI::parseInt, NULL, offsetof( GlobalData, m_baseValuePerSupplyBox ) }, - - { "AudioOn", INI::parseBool, NULL, offsetof( GlobalData, m_audioOn ) }, - { "MusicOn", INI::parseBool, NULL, offsetof( GlobalData, m_musicOn ) }, - { "SoundsOn", INI::parseBool, NULL, offsetof( GlobalData, m_soundsOn ) }, - { "Sounds3DOn", INI::parseBool, NULL, offsetof( GlobalData, m_sounds3DOn ) }, - { "SpeechOn", INI::parseBool, NULL, offsetof( GlobalData, m_speechOn ) }, - { "VideoOn", INI::parseBool, NULL, offsetof( GlobalData, m_videoOn ) }, - { "DisableCameraMovements", INI::parseBool, NULL, offsetof( GlobalData, m_disableCameraMovement ) }, - -/* These are internal use only, they do not need file definitons - /// @todo remove this hack - { "InGame", INI::parseBool, NULL, offsetof( GlobalData, m_inGame ) }, -*/ - - { "DebugAI", INI::parseBool, NULL, offsetof( GlobalData, m_debugAI ) }, - { "DebugAIObstacles", INI::parseBool, NULL, offsetof( GlobalData, m_debugAIObstacles ) }, - { "ShowClientPhysics", INI::parseBool, NULL, offsetof( GlobalData, m_showClientPhysics ) }, - { "ShowTerrainNormals", INI::parseBool, NULL, offsetof( GlobalData, m_showTerrainNormals ) }, - { "ShowObjectHealth", INI::parseBool, NULL, offsetof( GlobalData, m_showObjectHealth ) }, - - { "ParticleScale", INI::parseReal, NULL, offsetof( GlobalData, m_particleScale ) }, - { "AutoFireParticleSmallPrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleSmallPrefix ) }, - { "AutoFireParticleSmallSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleSmallSystem ) }, - { "AutoFireParticleSmallMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoFireParticleSmallMax ) }, - { "AutoFireParticleMediumPrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleMediumPrefix ) }, - { "AutoFireParticleMediumSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleMediumSystem ) }, - { "AutoFireParticleMediumMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoFireParticleMediumMax ) }, - { "AutoFireParticleLargePrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleLargePrefix ) }, - { "AutoFireParticleLargeSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleLargeSystem ) }, - { "AutoFireParticleLargeMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoFireParticleLargeMax ) }, - { "AutoSmokeParticleSmallPrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleSmallPrefix ) }, - { "AutoSmokeParticleSmallSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleSmallSystem ) }, - { "AutoSmokeParticleSmallMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoSmokeParticleSmallMax ) }, - { "AutoSmokeParticleMediumPrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleMediumPrefix ) }, - { "AutoSmokeParticleMediumSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleMediumSystem ) }, - { "AutoSmokeParticleMediumMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoSmokeParticleMediumMax ) }, - { "AutoSmokeParticleLargePrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleLargePrefix ) }, - { "AutoSmokeParticleLargeSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleLargeSystem ) }, - { "AutoSmokeParticleLargeMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoSmokeParticleLargeMax ) }, - { "AutoAflameParticlePrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoAflameParticlePrefix ) }, - { "AutoAflameParticleSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoAflameParticleSystem ) }, - { "AutoAflameParticleMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoAflameParticleMax ) }, - -/* These are internal use only, they do not need file definitons - { "LatencyAverage", INI::parseInt, NULL, offsetof( GlobalData, m_latencyAverage ) }, - { "LatencyAmplitude", INI::parseInt, NULL, offsetof( GlobalData, m_latencyAmplitude ) }, - { "LatencyPeriod", INI::parseInt, NULL, offsetof( GlobalData, m_latencyPeriod ) }, - { "LatencyNoise", INI::parseInt, NULL, offsetof( GlobalData, m_latencyNoise ) }, - { "PacketLoss", INI::parseInt, NULL, offsetof( GlobalData, m_packetLoss ) }, -*/ - - { "BuildSpeed", INI::parseReal, NULL, offsetof( GlobalData, m_BuildSpeed ) }, - { "MinDistFromEdgeOfMapForBuild", INI::parseReal, NULL, offsetof( GlobalData, m_MinDistFromEdgeOfMapForBuild ) }, - { "SupplyBuildBorder", INI::parseReal, NULL, offsetof( GlobalData, m_SupplyBuildBorder ) }, - { "AllowedHeightVariationForBuilding", INI::parseReal,NULL, offsetof( GlobalData, m_allowedHeightVariationForBuilding ) }, - { "MinLowEnergyProductionSpeed",INI::parseReal, NULL, offsetof( GlobalData, m_MinLowEnergyProductionSpeed ) }, - { "MaxLowEnergyProductionSpeed",INI::parseReal, NULL, offsetof( GlobalData, m_MaxLowEnergyProductionSpeed ) }, - { "LowEnergyPenaltyModifier", INI::parseReal, NULL, offsetof( GlobalData, m_LowEnergyPenaltyModifier ) }, - { "MultipleFactory", INI::parseReal, NULL, offsetof( GlobalData, m_MultipleFactory ) }, - { "RefundPercent", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_RefundPercent ) }, - - { "CommandCenterHealRange", INI::parseReal, NULL, offsetof( GlobalData, m_commandCenterHealRange ) }, - { "CommandCenterHealAmount", INI::parseReal, NULL, offsetof( GlobalData, m_commandCenterHealAmount ) }, - - { "StandardMinefieldDensity", INI::parseReal, NULL, offsetof( GlobalData, m_standardMinefieldDensity ) }, - { "StandardMinefieldDistance", INI::parseReal, NULL, offsetof( GlobalData, m_standardMinefieldDistance ) }, - - { "MaxLineBuildObjects", INI::parseInt, NULL, offsetof( GlobalData, m_maxLineBuildObjects ) }, - { "MaxTunnelCapacity", INI::parseInt, NULL, offsetof( GlobalData, m_maxTunnelCapacity ) }, - - { "MaxParticleCount", INI::parseInt, NULL, offsetof( GlobalData, m_maxParticleCount ) }, - { "MaxFieldParticleCount", INI::parseInt, NULL, offsetof( GlobalData, m_maxFieldParticleCount ) }, - { "HorizontalScrollSpeedFactor",INI::parseReal, NULL, offsetof( GlobalData, m_horizontalScrollSpeedFactor ) }, - { "VerticalScrollSpeedFactor", INI::parseReal, NULL, offsetof( GlobalData, m_verticalScrollSpeedFactor ) }, - { "ScrollAmountCutoff", INI::parseReal, NULL, offsetof( GlobalData, m_scrollAmountCutoff ) }, - { "CameraAdjustSpeed", INI::parseReal, NULL, offsetof( GlobalData, m_cameraAdjustSpeed ) }, - { "EnforceMaxCameraHeight", INI::parseBool, NULL, offsetof( GlobalData, m_enforceMaxCameraHeight ) }, - { "KeyboardScrollSpeedFactor", INI::parseReal, NULL, offsetof( GlobalData, m_keyboardScrollFactor ) }, - { "KeyboardDefaultScrollSpeedFactor", INI::parseReal, NULL, offsetof( GlobalData, m_keyboardDefaultScrollFactor ) }, - { "MovementPenaltyDamageState", INI::parseIndexList, TheBodyDamageTypeNames, offsetof( GlobalData, m_movementPenaltyDamageState ) }, - -// you cannot set this; it always has a value of 100%. -//{ "HealthBonus_Regular", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_healthBonus[LEVEL_REGULAR]) }, - { "HealthBonus_Veteran", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_healthBonus[LEVEL_VETERAN]) }, - { "HealthBonus_Elite", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_healthBonus[LEVEL_ELITE]) }, - { "HealthBonus_Heroic", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_healthBonus[LEVEL_HEROIC]) }, - - { "HumanSoloPlayerHealthBonus_Easy", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_HUMAN][DIFFICULTY_EASY] ) }, - { "HumanSoloPlayerHealthBonus_Normal", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_HUMAN][DIFFICULTY_NORMAL] ) }, - { "HumanSoloPlayerHealthBonus_Hard", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_HUMAN][DIFFICULTY_HARD] ) }, - - { "AISoloPlayerHealthBonus_Easy", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_COMPUTER][DIFFICULTY_EASY] ) }, - { "AISoloPlayerHealthBonus_Normal", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_COMPUTER][DIFFICULTY_NORMAL] ) }, - { "AISoloPlayerHealthBonus_Hard", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_COMPUTER][DIFFICULTY_HARD] ) }, - - { "WeaponBonus", WeaponBonusSet::parseWeaponBonusSetPtr, NULL, offsetof( GlobalData, m_weaponBonusSet ) }, - - { "DefaultStructureRubbleHeight", INI::parseReal, NULL, offsetof( GlobalData, m_defaultStructureRubbleHeight ) }, - - { "FixedSeed", INI::parseInt, NULL, offsetof( GlobalData, m_fixedSeed ) }, - - { "ShellMapName", INI::parseAsciiString,NULL, offsetof( GlobalData, m_shellMapName ) }, - { "ShellMapOn", INI::parseBool, NULL, offsetof( GlobalData, m_shellMapOn ) }, - { "PlayIntro", INI::parseBool, NULL, offsetof( GlobalData, m_playIntro ) }, - - { "FirewallBehavior", INI::parseInt, NULL, offsetof( GlobalData, m_firewallBehavior ) }, - { "FirewallPortOverride", INI::parseInt, NULL, offsetof( GlobalData, m_firewallPortOverride ) }, - { "FirewallPortAllocationDelta",INI::parseInt, NULL, offsetof( GlobalData, m_firewallPortAllocationDelta) }, - - { "GroupSelectMinSelectSize", INI::parseInt, NULL, offsetof( GlobalData, m_groupSelectMinSelectSize ) }, - { "GroupSelectVolumeBase", INI::parseReal, NULL, offsetof( GlobalData, m_groupSelectVolumeBase ) }, - { "GroupSelectVolumeIncrement", INI::parseReal, NULL, offsetof( GlobalData, m_groupSelectVolumeIncrement ) }, - { "MaxUnitSelectSounds", INI::parseInt, NULL, offsetof( GlobalData, m_maxUnitSelectSounds ) }, - - { "SelectionFlashSaturationFactor", INI::parseReal, NULL, offsetof( GlobalData, m_selectionFlashSaturationFactor ) }, - { "SelectionFlashHouseColor", INI::parseBool, NULL, offsetof( GlobalData, m_selectionFlashHouseColor ) }, - - { "CameraAudibleRadius", INI::parseReal, NULL, offsetof( GlobalData, m_cameraAudibleRadius ) }, - { "GroupMoveClickToGatherAreaFactor", INI::parseReal, NULL, offsetof( GlobalData, m_groupMoveClickToGatherFactor ) }, - { "ShakeSubtleIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeSubtleIntensity ) }, - { "ShakeNormalIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeNormalIntensity ) }, - { "ShakeStrongIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeStrongIntensity ) }, - { "ShakeSevereIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeSevereIntensity ) }, - { "ShakeCineExtremeIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeCineExtremeIntensity ) }, - { "ShakeCineInsaneIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeCineInsaneIntensity ) }, - { "MaxShakeIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_maxShakeIntensity ) }, - { "MaxShakeRange", INI::parseReal, NULL, offsetof( GlobalData, m_maxShakeRange) }, - { "SellPercentage", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_sellPercentage ) }, - { "BaseRegenHealthPercentPerSecond", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_baseRegenHealthPercentPerSecond ) }, - { "BaseRegenDelay", INI::parseDurationUnsignedInt, NULL,offsetof( GlobalData, m_baseRegenDelay ) }, - -#ifdef ALLOW_SURRENDER - { "PrisonBountyMultiplier", INI::parseReal, NULL, offsetof( GlobalData, m_prisonBountyMultiplier ) }, - { "PrisonBountyTextColor", INI::parseColorInt, NULL, offsetof( GlobalData, m_prisonBountyTextColor ) }, -#endif - - { "SpecialPowerViewObject", INI::parseAsciiString, NULL, offsetof( GlobalData, m_specialPowerViewObjectName ) }, - - { "StandardPublicBone", INI::parseAsciiStringVectorAppend, NULL, offsetof(GlobalData, m_standardPublicBones) }, - { "ShowMetrics", INI::parseBool, NULL, offsetof( GlobalData, m_showMetrics ) }, - { "DefaultStartingCash", Money::parseMoneyAmount, NULL, offsetof( GlobalData, m_defaultStartingCash ) }, - -// NOTE: m_doubleClickTimeMS is still in use, but we disallow setting it from the GameData.ini file. It is now set in the constructor according to the windows parameter. -// { "DoubleClickTimeMS", INI::parseUnsignedInt, NULL, offsetof( GlobalData, m_doubleClickTimeMS ) }, - - { "ShroudColor", INI::parseRGBColor, NULL, offsetof( GlobalData, m_shroudColor) }, - { "ClearAlpha", INI::parseUnsignedByte, NULL, offsetof( GlobalData, m_clearAlpha) }, - { "FogAlpha", INI::parseUnsignedByte, NULL, offsetof( GlobalData, m_fogAlpha) }, - { "ShroudAlpha", INI::parseUnsignedByte, NULL, offsetof( GlobalData, m_shroudAlpha) }, - - { "HotKeyTextColor", INI::parseColorInt, NULL, offsetof( GlobalData, m_hotKeyTextColor ) }, - - { "PowerBarBase", INI::parseInt, NULL, offsetof( GlobalData, m_powerBarBase) }, - { "PowerBarIntervals", INI::parseReal, NULL, offsetof( GlobalData, m_powerBarIntervals) }, - { "PowerBarYellowRange", INI::parseInt, NULL, offsetof( GlobalData, m_powerBarYellowRange) }, - { "UnlookPersistDuration", INI::parseDurationUnsignedInt, NULL, offsetof( GlobalData, m_unlookPersistDuration) }, - - { "NetworkFPSHistoryLength", INI::parseInt, NULL, offsetof(GlobalData, m_networkFPSHistoryLength) }, - { "NetworkLatencyHistoryLength", INI::parseInt, NULL, offsetof(GlobalData, m_networkLatencyHistoryLength) }, - { "NetworkRunAheadMetricsTime", INI::parseInt, NULL, offsetof(GlobalData, m_networkRunAheadMetricsTime) }, - { "NetworkCushionHistoryLength", INI::parseInt, NULL, offsetof(GlobalData, m_networkCushionHistoryLength) }, - { "NetworkRunAheadSlack", INI::parseInt, NULL, offsetof(GlobalData, m_networkRunAheadSlack) }, - { "NetworkKeepAliveDelay", INI::parseInt, NULL, offsetof(GlobalData, m_networkKeepAliveDelay) }, - { "NetworkDisconnectTime", INI::parseInt, NULL, offsetof(GlobalData, m_networkDisconnectTime) }, - { "NetworkPlayerTimeoutTime", INI::parseInt, NULL, offsetof(GlobalData, m_networkPlayerTimeoutTime) }, - { "NetworkDisconnectScreenNotifyTime", INI::parseInt, NULL, offsetof(GlobalData, m_networkDisconnectScreenNotifyTime) }, - - { "KeyboardCameraRotateSpeed", INI::parseReal, NULL, offsetof( GlobalData, m_keyboardCameraRotateSpeed ) }, - { "PlayStats", INI::parseInt, NULL, offsetof( GlobalData, m_playStats ) }, - -#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) - { "DisableCameraFade", INI::parseBool, NULL, offsetof( GlobalData, m_disableCameraFade ) }, - { "DisableScriptedInputDisabling", INI::parseBool, NULL, offsetof( GlobalData, m_disableScriptedInputDisabling ) }, - { "DisableMilitaryCaption", INI::parseBool, NULL, offsetof( GlobalData, m_disableMilitaryCaption ) }, - { "BenchmarkTimer", INI::parseInt, NULL, offsetof( GlobalData, m_benchmarkTimer ) }, - { "CheckMemoryLeaks", INI::parseBool, NULL, offsetof(GlobalData, m_checkForLeaks) }, - { "Wireframe", INI::parseBool, NULL, offsetof( GlobalData, m_wireframe ) }, - { "StateMachineDebug", INI::parseBool, NULL, offsetof( GlobalData, m_stateMachineDebug ) }, - { "UseCameraConstraints", INI::parseBool, NULL, offsetof( GlobalData, m_useCameraConstraints ) }, - { "ShroudOn", INI::parseBool, NULL, offsetof( GlobalData, m_shroudOn ) }, - { "FogOfWarOn", INI::parseBool, NULL, offsetof( GlobalData, m_fogOfWarOn ) }, - { "ShowCollisionExtents", INI::parseBool, NULL, offsetof( GlobalData, m_showCollisionExtents ) }, - { "ShowAudioLocations", INI::parseBool, NULL, offsetof( GlobalData, m_showAudioLocations ) }, - { "DebugProjectileTileWidth", INI::parseReal, NULL, offsetof( GlobalData, m_debugProjectileTileWidth) }, - { "DebugProjectileTileDuration",INI::parseInt, NULL, offsetof( GlobalData, m_debugProjectileTileDuration) }, - { "DebugProjectileTileColor", INI::parseRGBColor, NULL, offsetof( GlobalData, m_debugProjectileTileColor) }, - { "DebugVisibilityTileCount", INI::parseInt, NULL, offsetof( GlobalData, m_debugVisibilityTileCount) }, - { "DebugVisibilityTileWidth", INI::parseReal, NULL, offsetof( GlobalData, m_debugVisibilityTileWidth) }, - { "DebugVisibilityTileDuration",INI::parseInt, NULL, offsetof( GlobalData, m_debugVisibilityTileDuration) }, - { "DebugVisibilityTileTargettableColor",INI::parseRGBColor, NULL, offsetof( GlobalData, m_debugVisibilityTargettableColor) }, - { "DebugVisibilityTileDeshroudColor", INI::parseRGBColor, NULL, offsetof( GlobalData, m_debugVisibilityDeshroudColor) }, - { "DebugVisibilityTileGapColor", INI::parseRGBColor, NULL, offsetof( GlobalData, m_debugVisibilityGapColor) }, - { "DebugThreatMapTileDuration", INI::parseInt, NULL, offsetof( GlobalData, m_debugThreatMapTileDuration) }, - { "MaxDebugThreatMapValue", INI::parseUnsignedInt, NULL, offsetof( GlobalData, m_maxDebugThreat) }, - { "DebugCashValueMapTileDuration", INI::parseInt, NULL, offsetof( GlobalData, m_debugCashValueMapTileDuration) }, - { "MaxDebugCashValueMapValue", INI::parseUnsignedInt, NULL, offsetof( GlobalData, m_maxDebugValue) }, - { "VTune", INI::parseBool, NULL, offsetof( GlobalData, m_vTune ) }, - { "SaveStats", INI::parseBool, NULL, offsetof( GlobalData, m_saveStats ) }, - { "UseLocalMOTD", INI::parseBool, NULL, offsetof( GlobalData, m_useLocalMOTD ) }, - { "BaseStatsDir", INI::parseAsciiString,NULL, offsetof( GlobalData, m_baseStatsDir ) }, - { "LocalMOTDPath", INI::parseAsciiString,NULL, offsetof( GlobalData, m_MOTDPath ) }, - { "ExtraLogging", INI::parseBool, NULL, offsetof( GlobalData, m_extraLogging ) }, -#endif - - { "UseVanillaDiagonalMoveSpeed", INI::parseBool, NULL, offsetof(GlobalData, m_useOldMoveSpeed) }, - { "TintStatus", GlobalData::parseTintStatusType, NULL, offsetof(GlobalData, m_colorTintTypes) }, - - {"ChronoDamageDisableThreshold", INI::parsePercentToReal, NULL, offsetof(GlobalData, m_chronoDamageDisableThreshold)}, - {"ChronoDamageHealRate", INI::parseDurationUnsignedInt, NULL, offsetof(GlobalData, m_chronoDamageHealRate)}, - {"ChronoDamageHealAmountPercent", INI::parsePercentToReal, NULL, offsetof(GlobalData, m_chronoDamageHealAmount) }, - {"ChronoDamageOpacityStart", INI::parsePercentToReal, NULL, offsetof(GlobalData, m_chronoDisableAlphaStart) }, - {"ChronoDamageOpacityEnd", INI::parsePercentToReal, NULL, offsetof(GlobalData, m_chronoDisableAlphaEnd) }, - - // {"ChronoDamageTintStatusType", TintStatusFlags::parseSingleBitFromINI, NULL, offsetof(GlobalData, m_chronoTintStatusType) }, - {"ChronoDamageParticleSystemLarge", INI::parseAsciiString, NULL, offsetof(GlobalData, m_chronoDisableParticleSystemLarge) }, - {"ChronoDamageParticleSystemMedium", INI::parseAsciiString, NULL, offsetof(GlobalData, m_chronoDisableParticleSystemMedium) }, - {"ChronoDamageParticleSystemSmall", INI::parseAsciiString, NULL, offsetof(GlobalData, m_chronoDisableParticleSystemSmall) }, - - {"DefaultExcludedDeathTypes", INI::parseDeathTypeFlagsList, NULL, offsetof(GlobalData, m_defaultExcludedDeathTypes) }, - - { NULL, NULL, NULL, 0 } // keep this last - -}; - - - -// Helper function -/*static*/ void GlobalData::setColorTintEntry(DrawableColorTint* arr, int index, RGBColor color, RGBColor colorInfantry, UnsignedInt attackFrames, UnsignedInt decayFrames) -{ - arr[index].color = color; - arr[index].colorInfantry = colorInfantry; - arr[index].attackFrames = attackFrames; - arr[index].decayFrames = decayFrames; -} - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -GlobalData::GlobalData() -{ - Int i, j; - - // - // we have now instanced a global data instance, if theOriginal is NULL, this is - // *the* very first instance and it shall be recorded. This way, when we load - // overrides of the global data, we can revert to the common, original data - // in m_theOriginal - // - if( m_theOriginal == NULL ) - m_theOriginal = this; - m_next = NULL; - -#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) || defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE) - m_specialPowerUsesDelay = TRUE; -#endif - m_TiVOFastMode = FALSE; - -#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) - m_wireframe = 0; - m_stateMachineDebug = FALSE; - m_useCameraConstraints = TRUE; - m_shroudOn = TRUE; - m_fogOfWarOn = FALSE; - m_jabberOn = FALSE; - m_munkeeOn = FALSE; - m_showCollisionExtents = FALSE; - m_showAudioLocations = FALSE; - m_debugCamera = FALSE; - m_debugVisibility = FALSE; - m_debugVisibilityTileCount = 32; // default to 32. - m_debugVisibilityTileDuration = LOGICFRAMES_PER_SECOND; - m_debugProjectilePath = FALSE; - m_debugProjectileTileWidth = 10; - m_debugProjectileTileDuration = LOGICFRAMES_PER_SECOND; // Changed By Sadullah Nader - m_debugThreatMap = FALSE; - m_maxDebugThreat = 5000; - m_debugThreatMapTileDuration = LOGICFRAMES_PER_SECOND; // Changed By Sadullah Nader - m_debugCashValueMap = FALSE; - m_maxDebugValue = 10000; - m_debugCashValueMapTileDuration = LOGICFRAMES_PER_SECOND; // Changed By Sadullah Nader - m_vTune = false; - m_checkForLeaks = TRUE; - m_benchmarkTimer = -1; - - - m_allowUnselectableSelection = FALSE; - m_disableCameraFade = false; - m_disableScriptedInputDisabling = false; - m_disableMilitaryCaption = false; - m_latencyAverage = 0; - m_latencyAmplitude = 0; - m_latencyPeriod = 0; - m_latencyNoise = 0; - m_packetLoss = 0; - m_saveStats = FALSE; - m_saveAllStats = FALSE; - m_useLocalMOTD = FALSE; - m_baseStatsDir = ".\\"; - m_MOTDPath = "MOTD.txt"; - m_extraLogging = FALSE; -#endif - -#ifdef DEBUG_CRASHING - m_debugIgnoreAsserts = FALSE; -#endif - -#ifdef DEBUG_STACKTRACE - m_debugIgnoreStackTrace = FALSE; -#endif - - m_playStats = -1; - m_incrementalAGPBuf = FALSE; - m_mapName.clear(); - m_moveHintName.clear(); - m_useTrees = 0; - m_useTreeSway = TRUE; - m_useDrawModuleLOD = FALSE; - m_useHeatEffects = TRUE; - m_useFpsLimit = FALSE; - m_dumpAssetUsage = FALSE; - m_framesPerSecondLimit = 0; - m_chipSetType = 0; - m_windowed = 0; - m_xResolution = 800; - m_yResolution = 600; - m_maxShellScreens = 0; - m_useCloudMap = FALSE; - m_use3WayTerrainBlends = 1; - m_useLightMap = FALSE; - m_bilinearTerrainTex = FALSE; - m_trilinearTerrainTex = FALSE; - m_multiPassTerrain = FALSE; - m_adjustCliffTextures = FALSE; - m_stretchTerrain = FALSE; - m_useHalfHeightMap = FALSE; - m_terrainLOD = TERRAIN_LOD_AUTOMATIC; - m_terrainLODTargetTimeMS = 0; - m_enableDynamicLOD = TRUE; - m_enableStaticLOD = TRUE; - m_rightMouseAlwaysScrolls = FALSE; - m_useWaterPlane = FALSE; - m_useCloudPlane = FALSE; - m_downwindAngle = ( -0.785f );//Northeast! - m_useShadowVolumes = FALSE; - m_useShadowDecals = FALSE; - m_textureReductionFactor = -1; - m_enableBehindBuildingMarkers = TRUE; - m_scriptDebug = FALSE; - m_particleEdit = FALSE; - m_displayDebug = FALSE; - m_winCursors = TRUE; - m_constantDebugUpdate = FALSE; - m_showTeamDot = FALSE; - m_fixedSeed = -1; // disabled - m_horizontalScrollSpeedFactor = 1.0; - m_verticalScrollSpeedFactor = 1.0; - - m_waterPositionX = 0.0f; - m_waterPositionY = 0.0f; - m_waterPositionZ = 0.0f; - m_waterExtentX = 0.0f; - m_waterExtentY = 0.0f; - m_waterType = 0; - m_featherWater = FALSE; - m_showSoftWaterEdge = TRUE; //display soft water edge - m_usingWaterTrackEditor = FALSE; - m_isWorldBuilder = FALSE; - - m_showMetrics = false; - - for( i = 0; i < MAX_WATER_GRID_SETTINGS; i++ ) - { - - m_vertexWaterHeightClampLow[ i ] = 0.0f; - m_vertexWaterHeightClampHi[ i ] = 0.0f; - m_vertexWaterAngle[ i ] = 0.0f; - m_vertexWaterXPosition[ i ] = 0.0f; - m_vertexWaterYPosition[ i ] = 0.0f; - m_vertexWaterZPosition[ i ] = 0.0f; - m_vertexWaterXGridCells[ i ] = 0; - m_vertexWaterYGridCells[ i ] = 0; - m_vertexWaterGridSize[ i ] = 0.0f; - m_vertexWaterAttenuationA[ i ] = 0.0f; - m_vertexWaterAttenuationB[ i ] = 0.0f; - m_vertexWaterAttenuationC[ i ] = 0.0f; - m_vertexWaterAttenuationRange[ i ] = 0.0f; - //Added By Sadullah Nader - //Initializations missing and needed - m_vertexWaterAvailableMaps[i].clear(); - } // end for i - - m_skyBoxPositionZ = 0.0f; - m_drawSkyBox = FALSE; - m_skyBoxScale = 4.5f; - - m_historicDamageLimit = 0; - m_maxTerrainTracks = 0; - - m_levelGainAnimationDisplayTimeInSeconds = 0.0f; - m_levelGainAnimationZRisePerSecond = 0.0f; - - m_getHealedAnimationDisplayTimeInSeconds = 0.0f; - m_getHealedAnimationZRisePerSecond = 0.0f; - - m_maxTankTrackEdges=100; - m_maxTankTrackOpaqueEdges=25; - m_maxTankTrackFadeDelay=300000; - - m_timeOfDay = TIME_OF_DAY_AFTERNOON; - m_weather = WEATHER_NORMAL; - m_makeTrackMarks = FALSE; - m_hideGarrisonFlags = FALSE; - m_forceModelsToFollowTimeOfDay = true; - m_forceModelsToFollowWeather = true; - - m_partitionCellSize = 0.0f; - m_ammoPipScaleFactor = 1.0f; - m_containerPipScaleFactor = 1.0f; - m_ammoPipWorldOffset.zero(); - m_containerPipWorldOffset.zero(); - m_ammoPipScreenOffset.x = m_ammoPipScreenOffset.y = 0; - m_containerPipScreenOffset.x = m_containerPipScreenOffset.y = 0; - - for (i=0; iopenFile(buffer, File::READ | File::BINARY); - if (fp != NULL) { - unsigned char crcBlock[blockSize]; - Int amtRead = 0; - while ( (amtRead=fp->read(crcBlock, blockSize)) > 0 ) - { - exeCRC.computeCRC(crcBlock, amtRead); - } - fp->close(); - fp = NULL; - } - if (TheVersion) - { - UnsignedInt version = TheVersion->getVersionNumber(); - exeCRC.computeCRC( &version, sizeof(UnsignedInt) ); - } - // Add in MP scripts to the EXE CRC, since the game will go out of sync if they change - fp = TheFileSystem->openFile("Data\\Scripts\\SkirmishScripts.scb", File::READ | File::BINARY); - if (fp != NULL) { - unsigned char crcBlock[blockSize]; - Int amtRead = 0; - while ( (amtRead=fp->read(crcBlock, blockSize)) > 0 ) - { - exeCRC.computeCRC(crcBlock, amtRead); - } - fp->close(); - fp = NULL; - } - fp = TheFileSystem->openFile("Data\\Scripts\\MultiplayerScripts.scb", File::READ | File::BINARY); - if (fp != NULL) { - unsigned char crcBlock[blockSize]; - Int amtRead = 0; - while ( (amtRead=fp->read(crcBlock, blockSize)) > 0 ) - { - exeCRC.computeCRC(crcBlock, amtRead); - } - fp->close(); - fp = NULL; - } - - m_exeCRC = exeCRC.get(); - DEBUG_LOG(("EXE CRC: 0x%8.8X\n", m_exeCRC)); - - m_movementPenaltyDamageState = BODY_REALLYDAMAGED; - - m_shouldUpdateTGAToDDS = FALSE; - - // Default DoubleClickTime to System double click time. - m_doubleClickTimeMS = GetDoubleClickTime(); // Note: This is actual MS, not frames. - -#ifdef DUMP_PERF_STATS - m_dumpPerformanceStatistics = FALSE; - m_dumpStatsAtInterval = FALSE; - m_statsInterval = 30; -#endif - - m_forceBenchmark = FALSE; ///> GLOBAL_DATA: m_colorTintTypes[%d] = {(%f, %f, %f), (%f, %f, %f), %d, %d}\n", - i, tc.color.red, tc.color.green, tc.color.blue, tc.colorInfantry.red, tc.colorInfantry.green, tc.colorInfantry.blue, - tc.attackFrames, tc.decayFrames)); - } - // ------------------------------------------------------------------------------ - - m_chronoDamageDisableThreshold = 0.1; - m_chronoDamageHealRate = 15; - m_chronoDamageHealAmount = 0.1; - - m_chronoDisableAlphaStart = 1.0; - m_chronoDisableAlphaEnd = 1.0; - - m_defaultExcludedDeathTypes = DEATH_TYPE_FLAGS_NONE; - - m_chronoDisableParticleSystemLarge.clear(); - m_chronoDisableParticleSystemMedium.clear(); - m_chronoDisableParticleSystemSmall.clear(); - // m_chronoTintStatusType = TINT_STATUS_INVALID; - -} // end GlobalData - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -GlobalData::~GlobalData( void ) -{ - DEBUG_ASSERTCRASH( TheWritableGlobalData->m_next == NULL, ("~GlobalData: theOriginal is not original\n") ); - - if (m_weaponBonusSet) - m_weaponBonusSet->deleteInstance(); - - if( m_theOriginal == this ) { - m_theOriginal = NULL; - TheWritableGlobalData = NULL; - } - -} // end ~GlobalData - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Bool GlobalData::setTimeOfDay( TimeOfDay tod ) -{ - if( tod >= TIME_OF_DAY_COUNT || tod < TIME_OF_DAY_FIRST ) - { - return FALSE; - } - - m_timeOfDay = tod; - for (Int i=0; im_next = TheWritableGlobalData; - - // set this new instance as the 'most current override' where we will access all data from - TheWritableGlobalData = override; - - return override; - -} // end newOveride - -//------------------------------------------------------------------------------------------------- -void GlobalData::init( void ) -{ - // nothing -} - -//------------------------------------------------------------------------------------------------- -/** Reset, remove any override data instances and return to just the initial one - */ -//------------------------------------------------------------------------------------------------- -void GlobalData::reset( void ) -{ - DEBUG_ASSERTCRASH(this == TheWritableGlobalData, ("calling reset on wrong GlobalData")); - - // - // delete any data instances that were loaded as an override and set the original - // global data instance as the singleton TheWritableGlobalData once again - // - while (TheWritableGlobalData != GlobalData::m_theOriginal) - { - - // get next instance - GlobalData* next = TheWritableGlobalData->m_next; - - // delete the head of the global data list (the latest override) - delete TheWritableGlobalData; - - // set next as top - TheWritableGlobalData = next; - - } // end while - - // - // we now have the one single global data in TheWritableGlobalData singleton, lets sanity check - // some of all that - // - DEBUG_ASSERTCRASH( TheWritableGlobalData->m_next == NULL, ("ResetGlobalData: theOriginal is not original\n") ); - DEBUG_ASSERTCRASH( TheWritableGlobalData == GlobalData::m_theOriginal, ("ResetGlobalData: oops\n") ); - -} // end ResetGlobalData - -//------------------------------------------------------------------------------------------------- -/** Parse GameData entry */ -//------------------------------------------------------------------------------------------------- -void GlobalData::parseGameDataDefinition( INI* ini ) -{ - if( TheWritableGlobalData && ini->getLoadType() != INI_LOAD_MULTIFILE) - { - - // - // if the type of loading we're doing creates override data, we need to - // be loading into a new override item - // - if( ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES ) - TheWritableGlobalData->newOverride(); - - } // end if - else if (!TheWritableGlobalData) - { - - // we don't have any global data instance at all yet, create one - TheWritableGlobalData = NEW GlobalData; - - } // end else - // If we're multifile, then continue loading stuff into the Global Data as normal. - - // parse the ini weapon definition - ini->initFromINI( TheWritableGlobalData, s_GlobalDataFieldParseTable ); - - - // override INI values with user preferences - OptionPreferences optionPref; - TheWritableGlobalData->m_useAlternateMouse = optionPref.getAlternateMouseModeEnabled(); - TheWritableGlobalData->m_clientRetaliationModeEnabled = optionPref.getRetaliationModeEnabled(); - TheWritableGlobalData->m_doubleClickAttackMove = optionPref.getDoubleClickAttackMoveEnabled(); - TheWritableGlobalData->m_keyboardScrollFactor = optionPref.getScrollFactor(); - TheWritableGlobalData->m_defaultIP = optionPref.getLANIPAddress(); - TheWritableGlobalData->m_firewallSendDelay = optionPref.getSendDelay(); - TheWritableGlobalData->m_firewallBehavior = optionPref.getFirewallBehavior(); - TheWritableGlobalData->m_firewallPortAllocationDelta = optionPref.getFirewallPortAllocationDelta(); - TheWritableGlobalData->m_firewallPortOverride = optionPref.getFirewallPortOverride(); - - TheWritableGlobalData->m_saveCameraInReplay = optionPref.saveCameraInReplays(); - TheWritableGlobalData->m_useCameraInReplay = optionPref.useCameraInReplays(); - - Int val=optionPref.getGammaValue(); - //generate a value between 0.6 and 2.0. - if (val < 50) - { //darker gamma - if (val <= 0) - TheWritableGlobalData->m_displayGamma = 0.6f; - else - TheWritableGlobalData->m_displayGamma=1.0f-(0.4f) * (Real)(50-val)/50.0f; - } - else - if (val > 50) - TheWritableGlobalData->m_displayGamma=1.0f+(1.0f) * (Real)(val-50)/50.0f; - - Int xres,yres; - optionPref.getResolution(&xres, &yres); - - TheWritableGlobalData->m_xResolution = xres; - TheWritableGlobalData->m_yResolution = yres; -} - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: GlobalData.cpp /////////////////////////////////////////////////////////////////////////// +// The GameLogicData object +// Author: trolfs, Michael Booth, Colin Day, April 2001 +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//#pragma once + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file in the GameEngine + +#define DEFINE_TERRAIN_LOD_NAMES +#define DEFINE_TIME_OF_DAY_NAMES +#define DEFINE_WEATHER_NAMES +#define DEFINE_BODYDAMAGETYPE_NAMES +#define DEFINE_PANNING_NAMES + +#include "Common/crc.h" +#include "Common/file.h" +#include "Common/FileSystem.h" +#include "Common/GameAudio.h" +#include "Common/INI.h" +#include "Common/Registry.h" +#include "Common/UserPreferences.h" +#include "Common/version.h" + +#include "GameLogic/AI.h" +#include "GameLogic/Weapon.h" +#include "GameLogic/Module/BodyModule.h" + +#include "GameClient/Color.h" +#include "GameClient/TerrainVisual.h" +#include "GameClient/TintStatus.h" + +#include "GameNetwork/FirewallHelper.h" + +// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// +GlobalData* TheWritableGlobalData = NULL; ///< The global data singleton + +//------------------------------------------------------------------------------------------------- +GlobalData* GlobalData::m_theOriginal = NULL; + + + +//------------------------------------------------------------------------------------------------- +/*static*/ void GlobalData::parseTintStatusType(INI* ini, void* instance, void* store, const void* userData) +{ + TintStatus tintType = (TintStatus)INI::scanIndexList(ini->getNextToken(), TintStatusFlags::getBitNames()); + + DrawableColorTint* colorTintTypes = (DrawableColorTint*)(store); + DrawableColorTint* tintEntry = &colorTintTypes[tintType]; + + INI::parseRGBColorReal(ini, instance, &tintEntry->color, NULL); + INI::parseRGBColorReal(ini, instance, &tintEntry->colorInfantry, NULL); + + INI::parseUnsignedInt(ini, instance, &tintEntry->attackFrames, NULL); + INI::parseUnsignedInt(ini, instance, &tintEntry->decayFrames, NULL); +} + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +/*static*/ const FieldParse GlobalData::s_GlobalDataFieldParseTable[] = +{ + { "Windowed", INI::parseBool, NULL, offsetof( GlobalData, m_windowed ) }, + { "XResolution", INI::parseInt, NULL, offsetof( GlobalData, m_xResolution ) }, + { "YResolution", INI::parseInt, NULL, offsetof( GlobalData, m_yResolution ) }, + { "MapName", INI::parseAsciiString,NULL, offsetof( GlobalData, m_mapName ) }, + { "MoveHintName", INI::parseAsciiString,NULL, offsetof( GlobalData, m_moveHintName ) }, + { "UseTrees", INI::parseBool, NULL, offsetof( GlobalData, m_useTrees ) }, + { "UseFPSLimit", INI::parseBool, NULL, offsetof( GlobalData, m_useFpsLimit ) }, + { "DumpAssetUsage", INI::parseBool, NULL, offsetof( GlobalData, m_dumpAssetUsage ) }, + { "FramesPerSecondLimit", INI::parseInt, NULL, offsetof( GlobalData, m_framesPerSecondLimit ) }, + { "ChipsetType", INI::parseInt, NULL, offsetof( GlobalData, m_chipSetType ) }, + { "MaxShellScreens", INI::parseInt, NULL, offsetof( GlobalData, m_maxShellScreens ) }, + { "UseCloudMap", INI::parseBool, NULL, offsetof( GlobalData, m_useCloudMap ) }, + { "UseLightMap", INI::parseBool, NULL, offsetof( GlobalData, m_useLightMap ) }, + { "BilinearTerrainTex", INI::parseBool, NULL, offsetof( GlobalData, m_bilinearTerrainTex ) }, + { "TrilinearTerrainTex", INI::parseBool, NULL, offsetof( GlobalData, m_trilinearTerrainTex ) }, + { "MultiPassTerrain", INI::parseBool, NULL, offsetof( GlobalData, m_multiPassTerrain ) }, + { "AdjustCliffTextures", INI::parseBool, NULL, offsetof( GlobalData, m_adjustCliffTextures ) }, + { "Use3WayTerrainBlends", INI::parseInt, NULL, offsetof( GlobalData, m_use3WayTerrainBlends ) }, + { "StretchTerrain", INI::parseBool, NULL, offsetof( GlobalData, m_stretchTerrain ) }, + { "UseHalfHeightMap", INI::parseBool, NULL, offsetof( GlobalData, m_useHalfHeightMap ) }, + + + { "DrawEntireTerrain", INI::parseBool, NULL, offsetof( GlobalData, m_drawEntireTerrain ) }, + { "TerrainLOD", INI::parseIndexList, TerrainLODNames, offsetof( GlobalData, m_terrainLOD ) }, + { "TerrainLODTargetTimeMS", INI::parseInt, NULL, offsetof( GlobalData, m_terrainLODTargetTimeMS ) }, + { "RightMouseAlwaysScrolls", INI::parseBool, NULL, offsetof( GlobalData, m_rightMouseAlwaysScrolls ) }, + { "UseWaterPlane", INI::parseBool, NULL, offsetof( GlobalData, m_useWaterPlane ) }, + { "UseCloudPlane", INI::parseBool, NULL, offsetof( GlobalData, m_useCloudPlane ) }, + { "DownwindAngle", INI::parseReal, NULL, offsetof( GlobalData, m_downwindAngle ) }, + { "UseShadowVolumes", INI::parseBool, NULL, offsetof( GlobalData, m_useShadowVolumes ) }, + { "UseShadowDecals", INI::parseBool, NULL, offsetof( GlobalData, m_useShadowDecals ) }, + { "TextureReductionFactor", INI::parseInt, NULL, offsetof( GlobalData, m_textureReductionFactor ) }, + { "UseBehindBuildingMarker", INI::parseBool, NULL, offsetof( GlobalData, m_enableBehindBuildingMarkers ) }, + { "WaterPositionX", INI::parseReal, NULL, offsetof( GlobalData, m_waterPositionX ) }, + { "WaterPositionY", INI::parseReal, NULL, offsetof( GlobalData, m_waterPositionY ) }, + { "WaterPositionZ", INI::parseReal, NULL, offsetof( GlobalData, m_waterPositionZ ) }, + { "WaterExtentX", INI::parseReal, NULL, offsetof( GlobalData, m_waterExtentX ) }, + { "WaterExtentY", INI::parseReal, NULL, offsetof( GlobalData, m_waterExtentY ) }, + { "WaterType", INI::parseInt, NULL, offsetof( GlobalData, m_waterType ) }, + { "FeatherWater", INI::parseInt, NULL, offsetof( GlobalData, m_featherWater ) }, + { "ShowSoftWaterEdge", INI::parseBool, NULL, offsetof( GlobalData, m_showSoftWaterEdge ) }, + + // nasty ick, we need to save this data with a map and not hard code INI values + { "VertexWaterAvailableMaps1", INI::parseAsciiString, NULL, offsetof( GlobalData, m_vertexWaterAvailableMaps[ 0 ] ) }, + { "VertexWaterHeightClampLow1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampLow[ 0 ] ) }, + { "VertexWaterHeightClampHi1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampHi[ 0 ] ) }, + { "VertexWaterAngle1", INI::parseAngleReal, NULL, offsetof( GlobalData, m_vertexWaterAngle[ 0 ] ) }, + { "VertexWaterXPosition1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterXPosition[ 0 ] ) }, + { "VertexWaterYPosition1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterYPosition[ 0 ] ) }, + { "VertexWaterZPosition1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterZPosition[ 0 ] ) }, + { "VertexWaterXGridCells1", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterXGridCells[ 0 ] ) }, + { "VertexWaterYGridCells1", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterYGridCells[ 0 ] ) }, + { "VertexWaterGridSize1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterGridSize[ 0 ] ) }, + { "VertexWaterAttenuationA1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationA[ 0 ] ) }, + { "VertexWaterAttenuationB1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationB[ 0 ] ) }, + { "VertexWaterAttenuationC1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationC[ 0 ] ) }, + { "VertexWaterAttenuationRange1", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationRange[ 0 ] ) }, + + // nasty ick, we need to save this data with a map and not hard code INI values + { "VertexWaterAvailableMaps2", INI::parseAsciiString, NULL, offsetof( GlobalData, m_vertexWaterAvailableMaps[ 1 ] ) }, + { "VertexWaterHeightClampLow2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampLow[ 1 ] ) }, + { "VertexWaterHeightClampHi2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampHi[ 1 ] ) }, + { "VertexWaterAngle2", INI::parseAngleReal, NULL, offsetof( GlobalData, m_vertexWaterAngle[ 1 ] ) }, + { "VertexWaterXPosition2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterXPosition[ 1 ] ) }, + { "VertexWaterYPosition2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterYPosition[ 1 ] ) }, + { "VertexWaterZPosition2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterZPosition[ 1 ] ) }, + { "VertexWaterXGridCells2", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterXGridCells[ 1 ] ) }, + { "VertexWaterYGridCells2", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterYGridCells[ 1 ] ) }, + { "VertexWaterGridSize2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterGridSize[ 1 ] ) }, + { "VertexWaterAttenuationA2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationA[ 1 ] ) }, + { "VertexWaterAttenuationB2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationB[ 1 ] ) }, + { "VertexWaterAttenuationC2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationC[ 1 ] ) }, + { "VertexWaterAttenuationRange2", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationRange[ 1 ] ) }, + + // nasty ick, we need to save this data with a map and not hard code INI values + { "VertexWaterAvailableMaps3", INI::parseAsciiString, NULL, offsetof( GlobalData, m_vertexWaterAvailableMaps[ 2 ] ) }, + { "VertexWaterHeightClampLow3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampLow[ 2 ] ) }, + { "VertexWaterHeightClampHi3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampHi[ 2 ] ) }, + { "VertexWaterAngle3", INI::parseAngleReal, NULL, offsetof( GlobalData, m_vertexWaterAngle[ 2 ] ) }, + { "VertexWaterXPosition3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterXPosition[ 2 ] ) }, + { "VertexWaterYPosition3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterYPosition[ 2 ] ) }, + { "VertexWaterZPosition3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterZPosition[ 2 ] ) }, + { "VertexWaterXGridCells3", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterXGridCells[ 2 ] ) }, + { "VertexWaterYGridCells3", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterYGridCells[ 2 ] ) }, + { "VertexWaterGridSize3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterGridSize[ 2 ] ) }, + { "VertexWaterAttenuationA3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationA[ 2 ] ) }, + { "VertexWaterAttenuationB3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationB[ 2 ] ) }, + { "VertexWaterAttenuationC3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationC[ 2 ] ) }, + { "VertexWaterAttenuationRange3", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationRange[ 2 ] ) }, + + // nasty ick, we need to save this data with a map and not hard code INI values + { "VertexWaterAvailableMaps4", INI::parseAsciiString, NULL, offsetof( GlobalData, m_vertexWaterAvailableMaps[ 3 ] ) }, + { "VertexWaterHeightClampLow4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampLow[ 3 ] ) }, + { "VertexWaterHeightClampHi4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterHeightClampHi[ 3 ] ) }, + { "VertexWaterAngle4", INI::parseAngleReal, NULL, offsetof( GlobalData, m_vertexWaterAngle[ 3 ] ) }, + { "VertexWaterXPosition4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterXPosition[ 3 ] ) }, + { "VertexWaterYPosition4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterYPosition[ 3 ] ) }, + { "VertexWaterZPosition4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterZPosition[ 3 ] ) }, + { "VertexWaterXGridCells4", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterXGridCells[ 3 ] ) }, + { "VertexWaterYGridCells4", INI::parseInt, NULL, offsetof( GlobalData, m_vertexWaterYGridCells[ 3 ] ) }, + { "VertexWaterGridSize4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterGridSize[ 3 ] ) }, + { "VertexWaterAttenuationA4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationA[ 3 ] ) }, + { "VertexWaterAttenuationB4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationB[ 3 ] ) }, + { "VertexWaterAttenuationC4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationC[ 3 ] ) }, + { "VertexWaterAttenuationRange4", INI::parseReal, NULL, offsetof( GlobalData, m_vertexWaterAttenuationRange[ 3 ] ) }, + + { "SkyBoxPositionZ", INI::parseReal, NULL, offsetof( GlobalData, m_skyBoxPositionZ ) }, + { "SkyBoxScale", INI::parseReal, NULL, offsetof( GlobalData, m_skyBoxScale ) }, + { "DrawSkyBox", INI::parseBool, NULL, offsetof( GlobalData, m_drawSkyBox ) }, + { "CameraPitch", INI::parseReal, NULL, offsetof( GlobalData, m_cameraPitch ) }, + { "CameraYaw", INI::parseReal, NULL, offsetof( GlobalData, m_cameraYaw ) }, + { "CameraHeight", INI::parseReal, NULL, offsetof( GlobalData, m_cameraHeight ) }, + { "MaxCameraHeight", INI::parseReal, NULL, offsetof( GlobalData, m_maxCameraHeight ) }, + { "MinCameraHeight", INI::parseReal, NULL, offsetof( GlobalData, m_minCameraHeight ) }, + { "TerrainHeightAtEdgeOfMap", INI::parseReal, NULL, offsetof( GlobalData, m_terrainHeightAtEdgeOfMap ) }, + { "UnitDamagedThreshold", INI::parseReal, NULL, offsetof( GlobalData, m_unitDamagedThresh ) }, + { "UnitReallyDamagedThreshold", INI::parseReal, NULL, offsetof( GlobalData, m_unitReallyDamagedThresh ) }, + { "GroundStiffness", INI::parseReal, NULL, offsetof( GlobalData, m_groundStiffness ) }, + { "StructureStiffness", INI::parseReal, NULL, offsetof( GlobalData, m_structureStiffness ) }, + { "Gravity", INI::parseAccelerationReal, NULL, offsetof( GlobalData, m_gravity ) }, + { "StealthFriendlyOpacity", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_stealthFriendlyOpacity ) }, + { "DefaultOcclusionDelay", INI::parseDurationUnsignedInt, NULL, offsetof( GlobalData, m_defaultOcclusionDelay ) }, + + { "PartitionCellSize", INI::parseReal, NULL, offsetof( GlobalData, m_partitionCellSize ) }, + + { "AmmoPipScaleFactor", INI::parseReal, NULL, offsetof( GlobalData, m_ammoPipScaleFactor ) }, + { "ContainerPipScaleFactor", INI::parseReal, NULL, offsetof( GlobalData, m_containerPipScaleFactor ) }, + { "AmmoPipWorldOffset", INI::parseCoord3D, NULL, offsetof( GlobalData, m_ammoPipWorldOffset ) }, + { "ContainerPipWorldOffset", INI::parseCoord3D, NULL, offsetof( GlobalData, m_containerPipWorldOffset ) }, + { "AmmoPipScreenOffset", INI::parseCoord2D, NULL, offsetof( GlobalData, m_ammoPipScreenOffset ) }, + { "ContainerPipScreenOffset", INI::parseCoord2D, NULL, offsetof( GlobalData, m_containerPipScreenOffset ) }, + + { "HistoricDamageLimit", INI::parseDurationUnsignedInt, NULL, offsetof( GlobalData, m_historicDamageLimit ) }, + + { "MaxTerrainTracks", INI::parseInt, NULL, offsetof( GlobalData, m_maxTerrainTracks ) }, + { "TimeOfDay", INI::parseIndexList, TimeOfDayNames, offsetof( GlobalData, m_timeOfDay ) }, + { "Weather", INI::parseIndexList, WeatherNames, offsetof( GlobalData, m_weather ) }, + { "MakeTrackMarks", INI::parseBool, NULL, offsetof( GlobalData, m_makeTrackMarks ) }, + { "HideGarrisonFlags", INI::parseBool, NULL, offsetof( GlobalData, m_hideGarrisonFlags ) }, + { "ForceModelsToFollowTimeOfDay", INI::parseBool, NULL, offsetof( GlobalData, m_forceModelsToFollowTimeOfDay ) }, + { "ForceModelsToFollowWeather", INI::parseBool, NULL, offsetof( GlobalData, m_forceModelsToFollowWeather ) }, + + { "LevelGainAnimationName", INI::parseAsciiString, NULL, offsetof( GlobalData, m_levelGainAnimationName ) }, + { "LevelGainAnimationTime", INI::parseReal, NULL, offsetof( GlobalData, m_levelGainAnimationDisplayTimeInSeconds ) }, + { "LevelGainAnimationZRise", INI::parseReal, NULL, offsetof( GlobalData, m_levelGainAnimationZRisePerSecond ) }, + + { "GetHealedAnimationName", INI::parseAsciiString, NULL, offsetof( GlobalData, m_getHealedAnimationName ) }, + { "GetHealedAnimationTime", INI::parseReal, NULL, offsetof( GlobalData, m_getHealedAnimationDisplayTimeInSeconds ) }, + { "GetHealedAnimationZRise", INI::parseReal, NULL, offsetof( GlobalData, m_getHealedAnimationZRisePerSecond ) }, + + { "TerrainLightingMorningAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][0].ambient ) }, + { "TerrainLightingMorningDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][0].diffuse ) }, + { "TerrainLightingMorningLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][0].lightPos ) }, + { "TerrainLightingAfternoonAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][0].ambient ) }, + { "TerrainLightingAfternoonDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][0].diffuse ) }, + { "TerrainLightingAfternoonLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][0].lightPos ) }, + { "TerrainLightingEveningAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][0].ambient ) }, + { "TerrainLightingEveningDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][0].diffuse ) }, + { "TerrainLightingEveningLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][0].lightPos ) }, + { "TerrainLightingNightAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][0].ambient ) }, + { "TerrainLightingNightDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][0].diffuse ) }, + { "TerrainLightingNightLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][0].lightPos ) }, + + { "TerrainObjectsLightingMorningAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][0].ambient ) }, + { "TerrainObjectsLightingMorningDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][0].diffuse ) }, + { "TerrainObjectsLightingMorningLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][0].lightPos ) }, + { "TerrainObjectsLightingAfternoonAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][0].ambient ) }, + { "TerrainObjectsLightingAfternoonDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][0].diffuse ) }, + { "TerrainObjectsLightingAfternoonLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][0].lightPos ) }, + { "TerrainObjectsLightingEveningAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][0].ambient ) }, + { "TerrainObjectsLightingEveningDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][0].diffuse ) }, + { "TerrainObjectsLightingEveningLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][0].lightPos ) }, + { "TerrainObjectsLightingNightAmbient", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][0].ambient ) }, + { "TerrainObjectsLightingNightDiffuse", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][0].diffuse ) }, + { "TerrainObjectsLightingNightLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][0].lightPos ) }, + + //Secondary global light + { "TerrainLightingMorningAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][1].ambient ) }, + { "TerrainLightingMorningDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][1].diffuse ) }, + { "TerrainLightingMorningLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][1].lightPos ) }, + { "TerrainLightingAfternoonAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][1].ambient ) }, + { "TerrainLightingAfternoonDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][1].diffuse ) }, + { "TerrainLightingAfternoonLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][1].lightPos ) }, + { "TerrainLightingEveningAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][1].ambient ) }, + { "TerrainLightingEveningDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][1].diffuse ) }, + { "TerrainLightingEveningLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][1].lightPos ) }, + { "TerrainLightingNightAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][1].ambient ) }, + { "TerrainLightingNightDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][1].diffuse ) }, + { "TerrainLightingNightLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][1].lightPos ) }, + + { "TerrainObjectsLightingMorningAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][1].ambient ) }, + { "TerrainObjectsLightingMorningDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][1].diffuse ) }, + { "TerrainObjectsLightingMorningLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][1].lightPos ) }, + { "TerrainObjectsLightingAfternoonAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][1].ambient ) }, + { "TerrainObjectsLightingAfternoonDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][1].diffuse ) }, + { "TerrainObjectsLightingAfternoonLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][1].lightPos ) }, + { "TerrainObjectsLightingEveningAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][1].ambient ) }, + { "TerrainObjectsLightingEveningDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][1].diffuse ) }, + { "TerrainObjectsLightingEveningLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][1].lightPos ) }, + { "TerrainObjectsLightingNightAmbient2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][1].ambient ) }, + { "TerrainObjectsLightingNightDiffuse2", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][1].diffuse ) }, + { "TerrainObjectsLightingNightLightPos2", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][1].lightPos ) }, + + //Third global light + { "TerrainLightingMorningAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][2].ambient ) }, + { "TerrainLightingMorningDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][2].diffuse ) }, + { "TerrainLightingMorningLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_MORNING ][2].lightPos ) }, + { "TerrainLightingAfternoonAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][2].ambient ) }, + { "TerrainLightingAfternoonDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][2].diffuse ) }, + { "TerrainLightingAfternoonLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_AFTERNOON ][2].lightPos ) }, + { "TerrainLightingEveningAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][2].ambient ) }, + { "TerrainLightingEveningDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][2].diffuse ) }, + { "TerrainLightingEveningLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_EVENING ][2].lightPos ) }, + { "TerrainLightingNightAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][2].ambient ) }, + { "TerrainLightingNightDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][2].diffuse ) }, + { "TerrainLightingNightLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLighting[ TIME_OF_DAY_NIGHT ][2].lightPos ) }, + + { "TerrainObjectsLightingMorningAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][2].ambient ) }, + { "TerrainObjectsLightingMorningDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][2].diffuse ) }, + { "TerrainObjectsLightingMorningLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_MORNING ][2].lightPos ) }, + { "TerrainObjectsLightingAfternoonAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][2].ambient ) }, + { "TerrainObjectsLightingAfternoonDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][2].diffuse ) }, + { "TerrainObjectsLightingAfternoonLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_AFTERNOON ][2].lightPos ) }, + { "TerrainObjectsLightingEveningAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][2].ambient ) }, + { "TerrainObjectsLightingEveningDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][2].diffuse ) }, + { "TerrainObjectsLightingEveningLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_EVENING ][2].lightPos ) }, + { "TerrainObjectsLightingNightAmbient3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][2].ambient ) }, + { "TerrainObjectsLightingNightDiffuse3", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][2].diffuse ) }, + { "TerrainObjectsLightingNightLightPos3", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainObjectsLighting[ TIME_OF_DAY_NIGHT ][2].lightPos ) }, + + + { "NumberGlobalLights", INI::parseInt, NULL, offsetof( GlobalData, m_numGlobalLights)}, + { "InfantryLightMorningScale", INI::parseReal, NULL, offsetof( GlobalData, m_infantryLightScale[TIME_OF_DAY_MORNING] ) }, + { "InfantryLightAfternoonScale", INI::parseReal, NULL, offsetof( GlobalData, m_infantryLightScale[TIME_OF_DAY_AFTERNOON] ) }, + { "InfantryLightEveningScale", INI::parseReal, NULL, offsetof( GlobalData, m_infantryLightScale[TIME_OF_DAY_EVENING] ) }, + { "InfantryLightNightScale", INI::parseReal, NULL, offsetof( GlobalData, m_infantryLightScale[TIME_OF_DAY_NIGHT] ) }, + + { "MaxTranslucentObjects", INI::parseInt, NULL, offsetof( GlobalData, m_maxVisibleTranslucentObjects) }, + { "OccludedColorLuminanceScale", INI::parseReal, NULL, offsetof( GlobalData, m_occludedLuminanceScale) }, + +/* These are internal use only, they do not need file definitons + { "TerrainAmbientRGB", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainAmbient ) }, + { "TerrainDiffuseRGB", INI::parseRGBColor, NULL, offsetof( GlobalData, m_terrainDiffuse ) }, + { "TerrainLightPos", INI::parseCoord3D, NULL, offsetof( GlobalData, m_terrainLightPos ) }, +*/ + { "MaxRoadSegments", INI::parseInt, NULL, offsetof( GlobalData, m_maxRoadSegments ) }, + { "MaxRoadVertex", INI::parseInt, NULL, offsetof( GlobalData, m_maxRoadVertex ) }, + { "MaxRoadIndex", INI::parseInt, NULL, offsetof( GlobalData, m_maxRoadIndex ) }, + { "MaxRoadTypes", INI::parseInt, NULL, offsetof( GlobalData, m_maxRoadTypes ) }, + + { "ValuePerSupplyBox", INI::parseInt, NULL, offsetof( GlobalData, m_baseValuePerSupplyBox ) }, + + { "AudioOn", INI::parseBool, NULL, offsetof( GlobalData, m_audioOn ) }, + { "MusicOn", INI::parseBool, NULL, offsetof( GlobalData, m_musicOn ) }, + { "SoundsOn", INI::parseBool, NULL, offsetof( GlobalData, m_soundsOn ) }, + { "Sounds3DOn", INI::parseBool, NULL, offsetof( GlobalData, m_sounds3DOn ) }, + { "SpeechOn", INI::parseBool, NULL, offsetof( GlobalData, m_speechOn ) }, + { "VideoOn", INI::parseBool, NULL, offsetof( GlobalData, m_videoOn ) }, + { "DisableCameraMovements", INI::parseBool, NULL, offsetof( GlobalData, m_disableCameraMovement ) }, + +/* These are internal use only, they do not need file definitons + /// @todo remove this hack + { "InGame", INI::parseBool, NULL, offsetof( GlobalData, m_inGame ) }, +*/ + + { "DebugAI", INI::parseBool, NULL, offsetof( GlobalData, m_debugAI ) }, + { "DebugAIObstacles", INI::parseBool, NULL, offsetof( GlobalData, m_debugAIObstacles ) }, + { "ShowClientPhysics", INI::parseBool, NULL, offsetof( GlobalData, m_showClientPhysics ) }, + { "ShowTerrainNormals", INI::parseBool, NULL, offsetof( GlobalData, m_showTerrainNormals ) }, + { "ShowObjectHealth", INI::parseBool, NULL, offsetof( GlobalData, m_showObjectHealth ) }, + + { "ParticleScale", INI::parseReal, NULL, offsetof( GlobalData, m_particleScale ) }, + { "AutoFireParticleSmallPrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleSmallPrefix ) }, + { "AutoFireParticleSmallSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleSmallSystem ) }, + { "AutoFireParticleSmallMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoFireParticleSmallMax ) }, + { "AutoFireParticleMediumPrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleMediumPrefix ) }, + { "AutoFireParticleMediumSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleMediumSystem ) }, + { "AutoFireParticleMediumMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoFireParticleMediumMax ) }, + { "AutoFireParticleLargePrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleLargePrefix ) }, + { "AutoFireParticleLargeSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoFireParticleLargeSystem ) }, + { "AutoFireParticleLargeMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoFireParticleLargeMax ) }, + { "AutoSmokeParticleSmallPrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleSmallPrefix ) }, + { "AutoSmokeParticleSmallSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleSmallSystem ) }, + { "AutoSmokeParticleSmallMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoSmokeParticleSmallMax ) }, + { "AutoSmokeParticleMediumPrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleMediumPrefix ) }, + { "AutoSmokeParticleMediumSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleMediumSystem ) }, + { "AutoSmokeParticleMediumMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoSmokeParticleMediumMax ) }, + { "AutoSmokeParticleLargePrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleLargePrefix ) }, + { "AutoSmokeParticleLargeSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoSmokeParticleLargeSystem ) }, + { "AutoSmokeParticleLargeMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoSmokeParticleLargeMax ) }, + { "AutoAflameParticlePrefix", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoAflameParticlePrefix ) }, + { "AutoAflameParticleSystem", INI::parseAsciiString, NULL, offsetof( GlobalData, m_autoAflameParticleSystem ) }, + { "AutoAflameParticleMax", INI::parseInt, NULL, offsetof( GlobalData, m_autoAflameParticleMax ) }, + +/* These are internal use only, they do not need file definitons + { "LatencyAverage", INI::parseInt, NULL, offsetof( GlobalData, m_latencyAverage ) }, + { "LatencyAmplitude", INI::parseInt, NULL, offsetof( GlobalData, m_latencyAmplitude ) }, + { "LatencyPeriod", INI::parseInt, NULL, offsetof( GlobalData, m_latencyPeriod ) }, + { "LatencyNoise", INI::parseInt, NULL, offsetof( GlobalData, m_latencyNoise ) }, + { "PacketLoss", INI::parseInt, NULL, offsetof( GlobalData, m_packetLoss ) }, +*/ + + { "BuildSpeed", INI::parseReal, NULL, offsetof( GlobalData, m_BuildSpeed ) }, + { "MinDistFromEdgeOfMapForBuild", INI::parseReal, NULL, offsetof( GlobalData, m_MinDistFromEdgeOfMapForBuild ) }, + { "SupplyBuildBorder", INI::parseReal, NULL, offsetof( GlobalData, m_SupplyBuildBorder ) }, + { "AllowedHeightVariationForBuilding", INI::parseReal,NULL, offsetof( GlobalData, m_allowedHeightVariationForBuilding ) }, + { "MinLowEnergyProductionSpeed",INI::parseReal, NULL, offsetof( GlobalData, m_MinLowEnergyProductionSpeed ) }, + { "MaxLowEnergyProductionSpeed",INI::parseReal, NULL, offsetof( GlobalData, m_MaxLowEnergyProductionSpeed ) }, + { "LowEnergyPenaltyModifier", INI::parseReal, NULL, offsetof( GlobalData, m_LowEnergyPenaltyModifier ) }, + { "MultipleFactory", INI::parseReal, NULL, offsetof( GlobalData, m_MultipleFactory ) }, + { "RefundPercent", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_RefundPercent ) }, + + { "CommandCenterHealRange", INI::parseReal, NULL, offsetof( GlobalData, m_commandCenterHealRange ) }, + { "CommandCenterHealAmount", INI::parseReal, NULL, offsetof( GlobalData, m_commandCenterHealAmount ) }, + + { "StandardMinefieldDensity", INI::parseReal, NULL, offsetof( GlobalData, m_standardMinefieldDensity ) }, + { "StandardMinefieldDistance", INI::parseReal, NULL, offsetof( GlobalData, m_standardMinefieldDistance ) }, + + { "MaxLineBuildObjects", INI::parseInt, NULL, offsetof( GlobalData, m_maxLineBuildObjects ) }, + { "MaxTunnelCapacity", INI::parseInt, NULL, offsetof( GlobalData, m_maxTunnelCapacity ) }, + + { "MaxParticleCount", INI::parseInt, NULL, offsetof( GlobalData, m_maxParticleCount ) }, + { "MaxFieldParticleCount", INI::parseInt, NULL, offsetof( GlobalData, m_maxFieldParticleCount ) }, + { "HorizontalScrollSpeedFactor",INI::parseReal, NULL, offsetof( GlobalData, m_horizontalScrollSpeedFactor ) }, + { "VerticalScrollSpeedFactor", INI::parseReal, NULL, offsetof( GlobalData, m_verticalScrollSpeedFactor ) }, + { "ScrollAmountCutoff", INI::parseReal, NULL, offsetof( GlobalData, m_scrollAmountCutoff ) }, + { "CameraAdjustSpeed", INI::parseReal, NULL, offsetof( GlobalData, m_cameraAdjustSpeed ) }, + { "EnforceMaxCameraHeight", INI::parseBool, NULL, offsetof( GlobalData, m_enforceMaxCameraHeight ) }, + { "KeyboardScrollSpeedFactor", INI::parseReal, NULL, offsetof( GlobalData, m_keyboardScrollFactor ) }, + { "KeyboardDefaultScrollSpeedFactor", INI::parseReal, NULL, offsetof( GlobalData, m_keyboardDefaultScrollFactor ) }, + { "MovementPenaltyDamageState", INI::parseIndexList, TheBodyDamageTypeNames, offsetof( GlobalData, m_movementPenaltyDamageState ) }, + +// you cannot set this; it always has a value of 100%. +//{ "HealthBonus_Regular", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_healthBonus[LEVEL_REGULAR]) }, + { "HealthBonus_Veteran", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_healthBonus[LEVEL_VETERAN]) }, + { "HealthBonus_Elite", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_healthBonus[LEVEL_ELITE]) }, + { "HealthBonus_Heroic", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_healthBonus[LEVEL_HEROIC]) }, + + { "HumanSoloPlayerHealthBonus_Easy", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_HUMAN][DIFFICULTY_EASY] ) }, + { "HumanSoloPlayerHealthBonus_Normal", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_HUMAN][DIFFICULTY_NORMAL] ) }, + { "HumanSoloPlayerHealthBonus_Hard", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_HUMAN][DIFFICULTY_HARD] ) }, + + { "AISoloPlayerHealthBonus_Easy", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_COMPUTER][DIFFICULTY_EASY] ) }, + { "AISoloPlayerHealthBonus_Normal", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_COMPUTER][DIFFICULTY_NORMAL] ) }, + { "AISoloPlayerHealthBonus_Hard", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_soloPlayerHealthBonusForDifficulty[PLAYER_COMPUTER][DIFFICULTY_HARD] ) }, + + { "WeaponBonus", WeaponBonusSet::parseWeaponBonusSetPtr, NULL, offsetof( GlobalData, m_weaponBonusSet ) }, + + { "DefaultStructureRubbleHeight", INI::parseReal, NULL, offsetof( GlobalData, m_defaultStructureRubbleHeight ) }, + + { "FixedSeed", INI::parseInt, NULL, offsetof( GlobalData, m_fixedSeed ) }, + + { "ShellMapName", INI::parseAsciiString,NULL, offsetof( GlobalData, m_shellMapName ) }, + { "ShellMapOn", INI::parseBool, NULL, offsetof( GlobalData, m_shellMapOn ) }, + { "PlayIntro", INI::parseBool, NULL, offsetof( GlobalData, m_playIntro ) }, + + { "FirewallBehavior", INI::parseInt, NULL, offsetof( GlobalData, m_firewallBehavior ) }, + { "FirewallPortOverride", INI::parseInt, NULL, offsetof( GlobalData, m_firewallPortOverride ) }, + { "FirewallPortAllocationDelta",INI::parseInt, NULL, offsetof( GlobalData, m_firewallPortAllocationDelta) }, + + { "GroupSelectMinSelectSize", INI::parseInt, NULL, offsetof( GlobalData, m_groupSelectMinSelectSize ) }, + { "GroupSelectVolumeBase", INI::parseReal, NULL, offsetof( GlobalData, m_groupSelectVolumeBase ) }, + { "GroupSelectVolumeIncrement", INI::parseReal, NULL, offsetof( GlobalData, m_groupSelectVolumeIncrement ) }, + { "MaxUnitSelectSounds", INI::parseInt, NULL, offsetof( GlobalData, m_maxUnitSelectSounds ) }, + + { "SelectionFlashSaturationFactor", INI::parseReal, NULL, offsetof( GlobalData, m_selectionFlashSaturationFactor ) }, + { "SelectionFlashHouseColor", INI::parseBool, NULL, offsetof( GlobalData, m_selectionFlashHouseColor ) }, + + { "CameraAudibleRadius", INI::parseReal, NULL, offsetof( GlobalData, m_cameraAudibleRadius ) }, + { "GroupMoveClickToGatherAreaFactor", INI::parseReal, NULL, offsetof( GlobalData, m_groupMoveClickToGatherFactor ) }, + { "ShakeSubtleIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeSubtleIntensity ) }, + { "ShakeNormalIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeNormalIntensity ) }, + { "ShakeStrongIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeStrongIntensity ) }, + { "ShakeSevereIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeSevereIntensity ) }, + { "ShakeCineExtremeIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeCineExtremeIntensity ) }, + { "ShakeCineInsaneIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_shakeCineInsaneIntensity ) }, + { "MaxShakeIntensity", INI::parseReal, NULL, offsetof( GlobalData, m_maxShakeIntensity ) }, + { "MaxShakeRange", INI::parseReal, NULL, offsetof( GlobalData, m_maxShakeRange) }, + { "SellPercentage", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_sellPercentage ) }, + { "BaseRegenHealthPercentPerSecond", INI::parsePercentToReal, NULL, offsetof( GlobalData, m_baseRegenHealthPercentPerSecond ) }, + { "BaseRegenDelay", INI::parseDurationUnsignedInt, NULL,offsetof( GlobalData, m_baseRegenDelay ) }, + +#ifdef ALLOW_SURRENDER + { "PrisonBountyMultiplier", INI::parseReal, NULL, offsetof( GlobalData, m_prisonBountyMultiplier ) }, + { "PrisonBountyTextColor", INI::parseColorInt, NULL, offsetof( GlobalData, m_prisonBountyTextColor ) }, +#endif + + { "SpecialPowerViewObject", INI::parseAsciiString, NULL, offsetof( GlobalData, m_specialPowerViewObjectName ) }, + + { "StandardPublicBone", INI::parseAsciiStringVectorAppend, NULL, offsetof(GlobalData, m_standardPublicBones) }, + { "ShowMetrics", INI::parseBool, NULL, offsetof( GlobalData, m_showMetrics ) }, + { "DefaultStartingCash", Money::parseMoneyAmount, NULL, offsetof( GlobalData, m_defaultStartingCash ) }, + +// NOTE: m_doubleClickTimeMS is still in use, but we disallow setting it from the GameData.ini file. It is now set in the constructor according to the windows parameter. +// { "DoubleClickTimeMS", INI::parseUnsignedInt, NULL, offsetof( GlobalData, m_doubleClickTimeMS ) }, + + { "ShroudColor", INI::parseRGBColor, NULL, offsetof( GlobalData, m_shroudColor) }, + { "ClearAlpha", INI::parseUnsignedByte, NULL, offsetof( GlobalData, m_clearAlpha) }, + { "FogAlpha", INI::parseUnsignedByte, NULL, offsetof( GlobalData, m_fogAlpha) }, + { "ShroudAlpha", INI::parseUnsignedByte, NULL, offsetof( GlobalData, m_shroudAlpha) }, + + { "HotKeyTextColor", INI::parseColorInt, NULL, offsetof( GlobalData, m_hotKeyTextColor ) }, + + { "PowerBarBase", INI::parseInt, NULL, offsetof( GlobalData, m_powerBarBase) }, + { "PowerBarIntervals", INI::parseReal, NULL, offsetof( GlobalData, m_powerBarIntervals) }, + { "PowerBarYellowRange", INI::parseInt, NULL, offsetof( GlobalData, m_powerBarYellowRange) }, + { "UnlookPersistDuration", INI::parseDurationUnsignedInt, NULL, offsetof( GlobalData, m_unlookPersistDuration) }, + + { "NetworkFPSHistoryLength", INI::parseInt, NULL, offsetof(GlobalData, m_networkFPSHistoryLength) }, + { "NetworkLatencyHistoryLength", INI::parseInt, NULL, offsetof(GlobalData, m_networkLatencyHistoryLength) }, + { "NetworkRunAheadMetricsTime", INI::parseInt, NULL, offsetof(GlobalData, m_networkRunAheadMetricsTime) }, + { "NetworkCushionHistoryLength", INI::parseInt, NULL, offsetof(GlobalData, m_networkCushionHistoryLength) }, + { "NetworkRunAheadSlack", INI::parseInt, NULL, offsetof(GlobalData, m_networkRunAheadSlack) }, + { "NetworkKeepAliveDelay", INI::parseInt, NULL, offsetof(GlobalData, m_networkKeepAliveDelay) }, + { "NetworkDisconnectTime", INI::parseInt, NULL, offsetof(GlobalData, m_networkDisconnectTime) }, + { "NetworkPlayerTimeoutTime", INI::parseInt, NULL, offsetof(GlobalData, m_networkPlayerTimeoutTime) }, + { "NetworkDisconnectScreenNotifyTime", INI::parseInt, NULL, offsetof(GlobalData, m_networkDisconnectScreenNotifyTime) }, + + { "KeyboardCameraRotateSpeed", INI::parseReal, NULL, offsetof( GlobalData, m_keyboardCameraRotateSpeed ) }, + { "PlayStats", INI::parseInt, NULL, offsetof( GlobalData, m_playStats ) }, + +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + { "DisableCameraFade", INI::parseBool, NULL, offsetof( GlobalData, m_disableCameraFade ) }, + { "DisableScriptedInputDisabling", INI::parseBool, NULL, offsetof( GlobalData, m_disableScriptedInputDisabling ) }, + { "DisableMilitaryCaption", INI::parseBool, NULL, offsetof( GlobalData, m_disableMilitaryCaption ) }, + { "BenchmarkTimer", INI::parseInt, NULL, offsetof( GlobalData, m_benchmarkTimer ) }, + { "CheckMemoryLeaks", INI::parseBool, NULL, offsetof(GlobalData, m_checkForLeaks) }, + { "Wireframe", INI::parseBool, NULL, offsetof( GlobalData, m_wireframe ) }, + { "StateMachineDebug", INI::parseBool, NULL, offsetof( GlobalData, m_stateMachineDebug ) }, + { "UseCameraConstraints", INI::parseBool, NULL, offsetof( GlobalData, m_useCameraConstraints ) }, + { "ShroudOn", INI::parseBool, NULL, offsetof( GlobalData, m_shroudOn ) }, + { "FogOfWarOn", INI::parseBool, NULL, offsetof( GlobalData, m_fogOfWarOn ) }, + { "ShowCollisionExtents", INI::parseBool, NULL, offsetof( GlobalData, m_showCollisionExtents ) }, + { "ShowAudioLocations", INI::parseBool, NULL, offsetof( GlobalData, m_showAudioLocations ) }, + { "DebugProjectileTileWidth", INI::parseReal, NULL, offsetof( GlobalData, m_debugProjectileTileWidth) }, + { "DebugProjectileTileDuration",INI::parseInt, NULL, offsetof( GlobalData, m_debugProjectileTileDuration) }, + { "DebugProjectileTileColor", INI::parseRGBColor, NULL, offsetof( GlobalData, m_debugProjectileTileColor) }, + { "DebugVisibilityTileCount", INI::parseInt, NULL, offsetof( GlobalData, m_debugVisibilityTileCount) }, + { "DebugVisibilityTileWidth", INI::parseReal, NULL, offsetof( GlobalData, m_debugVisibilityTileWidth) }, + { "DebugVisibilityTileDuration",INI::parseInt, NULL, offsetof( GlobalData, m_debugVisibilityTileDuration) }, + { "DebugVisibilityTileTargettableColor",INI::parseRGBColor, NULL, offsetof( GlobalData, m_debugVisibilityTargettableColor) }, + { "DebugVisibilityTileDeshroudColor", INI::parseRGBColor, NULL, offsetof( GlobalData, m_debugVisibilityDeshroudColor) }, + { "DebugVisibilityTileGapColor", INI::parseRGBColor, NULL, offsetof( GlobalData, m_debugVisibilityGapColor) }, + { "DebugThreatMapTileDuration", INI::parseInt, NULL, offsetof( GlobalData, m_debugThreatMapTileDuration) }, + { "MaxDebugThreatMapValue", INI::parseUnsignedInt, NULL, offsetof( GlobalData, m_maxDebugThreat) }, + { "DebugCashValueMapTileDuration", INI::parseInt, NULL, offsetof( GlobalData, m_debugCashValueMapTileDuration) }, + { "MaxDebugCashValueMapValue", INI::parseUnsignedInt, NULL, offsetof( GlobalData, m_maxDebugValue) }, + { "VTune", INI::parseBool, NULL, offsetof( GlobalData, m_vTune ) }, + { "SaveStats", INI::parseBool, NULL, offsetof( GlobalData, m_saveStats ) }, + { "UseLocalMOTD", INI::parseBool, NULL, offsetof( GlobalData, m_useLocalMOTD ) }, + { "BaseStatsDir", INI::parseAsciiString,NULL, offsetof( GlobalData, m_baseStatsDir ) }, + { "LocalMOTDPath", INI::parseAsciiString,NULL, offsetof( GlobalData, m_MOTDPath ) }, + { "ExtraLogging", INI::parseBool, NULL, offsetof( GlobalData, m_extraLogging ) }, +#endif + + { "UseVanillaDiagonalMoveSpeed", INI::parseBool, NULL, offsetof(GlobalData, m_useOldMoveSpeed) }, + { "TintStatus", GlobalData::parseTintStatusType, NULL, offsetof(GlobalData, m_colorTintTypes) }, + + {"ChronoDamageDisableThreshold", INI::parsePercentToReal, NULL, offsetof(GlobalData, m_chronoDamageDisableThreshold)}, + {"ChronoDamageHealRate", INI::parseDurationUnsignedInt, NULL, offsetof(GlobalData, m_chronoDamageHealRate)}, + {"ChronoDamageHealAmountPercent", INI::parsePercentToReal, NULL, offsetof(GlobalData, m_chronoDamageHealAmount) }, + {"ChronoDamageOpacityStart", INI::parsePercentToReal, NULL, offsetof(GlobalData, m_chronoDisableAlphaStart) }, + {"ChronoDamageOpacityEnd", INI::parsePercentToReal, NULL, offsetof(GlobalData, m_chronoDisableAlphaEnd) }, + + // {"ChronoDamageTintStatusType", TintStatusFlags::parseSingleBitFromINI, NULL, offsetof(GlobalData, m_chronoTintStatusType) }, + {"ChronoDamageParticleSystemLarge", INI::parseAsciiString, NULL, offsetof(GlobalData, m_chronoDisableParticleSystemLarge) }, + {"ChronoDamageParticleSystemMedium", INI::parseAsciiString, NULL, offsetof(GlobalData, m_chronoDisableParticleSystemMedium) }, + {"ChronoDamageParticleSystemSmall", INI::parseAsciiString, NULL, offsetof(GlobalData, m_chronoDisableParticleSystemSmall) }, + + {"DefaultExcludedDeathTypes", INI::parseDeathTypeFlagsList, NULL, offsetof(GlobalData, m_defaultExcludedDeathTypes) }, + + { NULL, NULL, NULL, 0 } // keep this last + +}; + + + +// Helper function +/*static*/ void GlobalData::setColorTintEntry(DrawableColorTint* arr, int index, RGBColor color, RGBColor colorInfantry, UnsignedInt attackFrames, UnsignedInt decayFrames) +{ + arr[index].color = color; + arr[index].colorInfantry = colorInfantry; + arr[index].attackFrames = attackFrames; + arr[index].decayFrames = decayFrames; +} + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +GlobalData::GlobalData() +{ + Int i, j; + + // + // we have now instanced a global data instance, if theOriginal is NULL, this is + // *the* very first instance and it shall be recorded. This way, when we load + // overrides of the global data, we can revert to the common, original data + // in m_theOriginal + // + if( m_theOriginal == NULL ) + m_theOriginal = this; + m_next = NULL; + +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) || defined(_ALLOW_DEBUG_CHEATS_IN_RELEASE) + m_specialPowerUsesDelay = TRUE; +#endif + m_TiVOFastMode = FALSE; + +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + m_wireframe = 0; + m_stateMachineDebug = FALSE; + m_useCameraConstraints = TRUE; + m_shroudOn = TRUE; + m_fogOfWarOn = FALSE; + m_jabberOn = FALSE; + m_munkeeOn = FALSE; + m_showCollisionExtents = FALSE; + m_showAudioLocations = FALSE; + m_debugCamera = FALSE; + m_debugVisibility = FALSE; + m_debugVisibilityTileCount = 32; // default to 32. + m_debugVisibilityTileDuration = LOGICFRAMES_PER_SECOND; + m_debugProjectilePath = FALSE; + m_debugProjectileTileWidth = 10; + m_debugProjectileTileDuration = LOGICFRAMES_PER_SECOND; // Changed By Sadullah Nader + m_debugThreatMap = FALSE; + m_maxDebugThreat = 5000; + m_debugThreatMapTileDuration = LOGICFRAMES_PER_SECOND; // Changed By Sadullah Nader + m_debugCashValueMap = FALSE; + m_maxDebugValue = 10000; + m_debugCashValueMapTileDuration = LOGICFRAMES_PER_SECOND; // Changed By Sadullah Nader + m_vTune = false; + m_checkForLeaks = TRUE; + m_benchmarkTimer = -1; + + + m_allowUnselectableSelection = FALSE; + m_disableCameraFade = false; + m_disableScriptedInputDisabling = false; + m_disableMilitaryCaption = false; + m_latencyAverage = 0; + m_latencyAmplitude = 0; + m_latencyPeriod = 0; + m_latencyNoise = 0; + m_packetLoss = 0; + m_saveStats = FALSE; + m_saveAllStats = FALSE; + m_useLocalMOTD = FALSE; + m_baseStatsDir = ".\\"; + m_MOTDPath = "MOTD.txt"; + m_extraLogging = FALSE; +#endif + +#ifdef DEBUG_CRASHING + m_debugIgnoreAsserts = FALSE; +#endif + +#ifdef DEBUG_STACKTRACE + m_debugIgnoreStackTrace = FALSE; +#endif + + m_playStats = -1; + m_incrementalAGPBuf = FALSE; + m_mapName.clear(); + m_moveHintName.clear(); + m_useTrees = 0; + m_useTreeSway = TRUE; + m_useDrawModuleLOD = FALSE; + m_useHeatEffects = TRUE; + m_useFpsLimit = FALSE; + m_dumpAssetUsage = FALSE; + m_framesPerSecondLimit = 0; + m_chipSetType = 0; + m_windowed = 0; + m_xResolution = 800; + m_yResolution = 600; + m_maxShellScreens = 0; + m_useCloudMap = FALSE; + m_use3WayTerrainBlends = 1; + m_useLightMap = FALSE; + m_bilinearTerrainTex = FALSE; + m_trilinearTerrainTex = FALSE; + m_multiPassTerrain = FALSE; + m_adjustCliffTextures = FALSE; + m_stretchTerrain = FALSE; + m_useHalfHeightMap = FALSE; + m_terrainLOD = TERRAIN_LOD_AUTOMATIC; + m_terrainLODTargetTimeMS = 0; + m_enableDynamicLOD = TRUE; + m_enableStaticLOD = TRUE; + m_rightMouseAlwaysScrolls = FALSE; + m_useWaterPlane = FALSE; + m_useCloudPlane = FALSE; + m_downwindAngle = ( -0.785f );//Northeast! + m_useShadowVolumes = FALSE; + m_useShadowDecals = FALSE; + m_textureReductionFactor = -1; + m_enableBehindBuildingMarkers = TRUE; + m_scriptDebug = FALSE; + m_particleEdit = FALSE; + m_displayDebug = FALSE; + m_winCursors = TRUE; + m_constantDebugUpdate = FALSE; + m_showTeamDot = FALSE; + m_fixedSeed = -1; // disabled + m_horizontalScrollSpeedFactor = 1.0; + m_verticalScrollSpeedFactor = 1.0; + + m_waterPositionX = 0.0f; + m_waterPositionY = 0.0f; + m_waterPositionZ = 0.0f; + m_waterExtentX = 0.0f; + m_waterExtentY = 0.0f; + m_waterType = 0; + m_featherWater = FALSE; + m_showSoftWaterEdge = TRUE; //display soft water edge + m_usingWaterTrackEditor = FALSE; + m_isWorldBuilder = FALSE; + + m_showMetrics = false; + + for( i = 0; i < MAX_WATER_GRID_SETTINGS; i++ ) + { + + m_vertexWaterHeightClampLow[ i ] = 0.0f; + m_vertexWaterHeightClampHi[ i ] = 0.0f; + m_vertexWaterAngle[ i ] = 0.0f; + m_vertexWaterXPosition[ i ] = 0.0f; + m_vertexWaterYPosition[ i ] = 0.0f; + m_vertexWaterZPosition[ i ] = 0.0f; + m_vertexWaterXGridCells[ i ] = 0; + m_vertexWaterYGridCells[ i ] = 0; + m_vertexWaterGridSize[ i ] = 0.0f; + m_vertexWaterAttenuationA[ i ] = 0.0f; + m_vertexWaterAttenuationB[ i ] = 0.0f; + m_vertexWaterAttenuationC[ i ] = 0.0f; + m_vertexWaterAttenuationRange[ i ] = 0.0f; + //Added By Sadullah Nader + //Initializations missing and needed + m_vertexWaterAvailableMaps[i].clear(); + } // end for i + + m_skyBoxPositionZ = 0.0f; + m_drawSkyBox = FALSE; + m_skyBoxScale = 4.5f; + + m_historicDamageLimit = 0; + m_maxTerrainTracks = 0; + + m_levelGainAnimationDisplayTimeInSeconds = 0.0f; + m_levelGainAnimationZRisePerSecond = 0.0f; + + m_getHealedAnimationDisplayTimeInSeconds = 0.0f; + m_getHealedAnimationZRisePerSecond = 0.0f; + + m_maxTankTrackEdges=100; + m_maxTankTrackOpaqueEdges=25; + m_maxTankTrackFadeDelay=300000; + + m_timeOfDay = TIME_OF_DAY_AFTERNOON; + m_weather = WEATHER_NORMAL; + m_makeTrackMarks = FALSE; + m_hideGarrisonFlags = FALSE; + m_forceModelsToFollowTimeOfDay = true; + m_forceModelsToFollowWeather = true; + + m_partitionCellSize = 0.0f; + m_ammoPipScaleFactor = 1.0f; + m_containerPipScaleFactor = 1.0f; + m_ammoPipWorldOffset.zero(); + m_containerPipWorldOffset.zero(); + m_ammoPipScreenOffset.x = m_ammoPipScreenOffset.y = 0; + m_containerPipScreenOffset.x = m_containerPipScreenOffset.y = 0; + + for (i=0; iopenFile(buffer, File::READ | File::BINARY); + if (fp != NULL) { + unsigned char crcBlock[blockSize]; + Int amtRead = 0; + while ( (amtRead=fp->read(crcBlock, blockSize)) > 0 ) + { + exeCRC.computeCRC(crcBlock, amtRead); + } + fp->close(); + fp = NULL; + } + if (TheVersion) + { + UnsignedInt version = TheVersion->getVersionNumber(); + exeCRC.computeCRC( &version, sizeof(UnsignedInt) ); + } + // Add in MP scripts to the EXE CRC, since the game will go out of sync if they change + fp = TheFileSystem->openFile("Data\\Scripts\\SkirmishScripts.scb", File::READ | File::BINARY); + if (fp != NULL) { + unsigned char crcBlock[blockSize]; + Int amtRead = 0; + while ( (amtRead=fp->read(crcBlock, blockSize)) > 0 ) + { + exeCRC.computeCRC(crcBlock, amtRead); + } + fp->close(); + fp = NULL; + } + fp = TheFileSystem->openFile("Data\\Scripts\\MultiplayerScripts.scb", File::READ | File::BINARY); + if (fp != NULL) { + unsigned char crcBlock[blockSize]; + Int amtRead = 0; + while ( (amtRead=fp->read(crcBlock, blockSize)) > 0 ) + { + exeCRC.computeCRC(crcBlock, amtRead); + } + fp->close(); + fp = NULL; + } + + m_exeCRC = exeCRC.get(); + DEBUG_LOG(("EXE CRC: 0x%8.8X\n", m_exeCRC)); + + m_movementPenaltyDamageState = BODY_REALLYDAMAGED; + + m_shouldUpdateTGAToDDS = FALSE; + + // Default DoubleClickTime to System double click time. + m_doubleClickTimeMS = GetDoubleClickTime(); // Note: This is actual MS, not frames. + +#ifdef DUMP_PERF_STATS + m_dumpPerformanceStatistics = FALSE; + m_dumpStatsAtInterval = FALSE; + m_statsInterval = 30; +#endif + + m_forceBenchmark = FALSE; ///> GLOBAL_DATA: m_colorTintTypes[%d] = {(%f, %f, %f), (%f, %f, %f), %d, %d}\n", + i, tc.color.red, tc.color.green, tc.color.blue, tc.colorInfantry.red, tc.colorInfantry.green, tc.colorInfantry.blue, + tc.attackFrames, tc.decayFrames)); + } + // ------------------------------------------------------------------------------ + + m_chronoDamageDisableThreshold = 0.1; + m_chronoDamageHealRate = 15; + m_chronoDamageHealAmount = 0.1; + + m_chronoDisableAlphaStart = 1.0; + m_chronoDisableAlphaEnd = 1.0; + + m_defaultExcludedDeathTypes = DEATH_TYPE_FLAGS_NONE; + + m_chronoDisableParticleSystemLarge.clear(); + m_chronoDisableParticleSystemMedium.clear(); + m_chronoDisableParticleSystemSmall.clear(); + // m_chronoTintStatusType = TINT_STATUS_INVALID; + +} // end GlobalData + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +GlobalData::~GlobalData( void ) +{ + DEBUG_ASSERTCRASH( TheWritableGlobalData->m_next == NULL, ("~GlobalData: theOriginal is not original\n") ); + + if (m_weaponBonusSet) + m_weaponBonusSet->deleteInstance(); + + if( m_theOriginal == this ) { + m_theOriginal = NULL; + TheWritableGlobalData = NULL; + } + +} // end ~GlobalData + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool GlobalData::setTimeOfDay( TimeOfDay tod ) +{ + if( tod >= TIME_OF_DAY_COUNT || tod < TIME_OF_DAY_FIRST ) + { + return FALSE; + } + + m_timeOfDay = tod; + for (Int i=0; im_next = TheWritableGlobalData; + + // set this new instance as the 'most current override' where we will access all data from + TheWritableGlobalData = override; + + return override; + +} // end newOveride + +//------------------------------------------------------------------------------------------------- +void GlobalData::init( void ) +{ + // nothing +} + +//------------------------------------------------------------------------------------------------- +/** Reset, remove any override data instances and return to just the initial one + */ +//------------------------------------------------------------------------------------------------- +void GlobalData::reset( void ) +{ + DEBUG_ASSERTCRASH(this == TheWritableGlobalData, ("calling reset on wrong GlobalData")); + + // + // delete any data instances that were loaded as an override and set the original + // global data instance as the singleton TheWritableGlobalData once again + // + while (TheWritableGlobalData != GlobalData::m_theOriginal) + { + + // get next instance + GlobalData* next = TheWritableGlobalData->m_next; + + // delete the head of the global data list (the latest override) + delete TheWritableGlobalData; + + // set next as top + TheWritableGlobalData = next; + + } // end while + + // + // we now have the one single global data in TheWritableGlobalData singleton, lets sanity check + // some of all that + // + DEBUG_ASSERTCRASH( TheWritableGlobalData->m_next == NULL, ("ResetGlobalData: theOriginal is not original\n") ); + DEBUG_ASSERTCRASH( TheWritableGlobalData == GlobalData::m_theOriginal, ("ResetGlobalData: oops\n") ); + +} // end ResetGlobalData + +//------------------------------------------------------------------------------------------------- +/** Parse GameData entry */ +//------------------------------------------------------------------------------------------------- +void GlobalData::parseGameDataDefinition( INI* ini ) +{ + if( TheWritableGlobalData && ini->getLoadType() != INI_LOAD_MULTIFILE) + { + + // + // if the type of loading we're doing creates override data, we need to + // be loading into a new override item + // + if( ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES ) + TheWritableGlobalData->newOverride(); + + } // end if + else if (!TheWritableGlobalData) + { + + // we don't have any global data instance at all yet, create one + TheWritableGlobalData = NEW GlobalData; + + } // end else + // If we're multifile, then continue loading stuff into the Global Data as normal. + + // parse the ini weapon definition + ini->initFromINI( TheWritableGlobalData, s_GlobalDataFieldParseTable ); + + + // override INI values with user preferences + OptionPreferences optionPref; + TheWritableGlobalData->m_useAlternateMouse = optionPref.getAlternateMouseModeEnabled(); + TheWritableGlobalData->m_clientRetaliationModeEnabled = optionPref.getRetaliationModeEnabled(); + TheWritableGlobalData->m_doubleClickAttackMove = optionPref.getDoubleClickAttackMoveEnabled(); + TheWritableGlobalData->m_keyboardScrollFactor = optionPref.getScrollFactor(); + TheWritableGlobalData->m_defaultIP = optionPref.getLANIPAddress(); + TheWritableGlobalData->m_firewallSendDelay = optionPref.getSendDelay(); + TheWritableGlobalData->m_firewallBehavior = optionPref.getFirewallBehavior(); + TheWritableGlobalData->m_firewallPortAllocationDelta = optionPref.getFirewallPortAllocationDelta(); + TheWritableGlobalData->m_firewallPortOverride = optionPref.getFirewallPortOverride(); + + TheWritableGlobalData->m_saveCameraInReplay = optionPref.saveCameraInReplays(); + TheWritableGlobalData->m_useCameraInReplay = optionPref.useCameraInReplays(); + + Int val=optionPref.getGammaValue(); + //generate a value between 0.6 and 2.0. + if (val < 50) + { //darker gamma + if (val <= 0) + TheWritableGlobalData->m_displayGamma = 0.6f; + else + TheWritableGlobalData->m_displayGamma=1.0f-(0.4f) * (Real)(50-val)/50.0f; + } + else + if (val > 50) + TheWritableGlobalData->m_displayGamma=1.0f+(1.0f) * (Real)(val-50)/50.0f; + + Int xres,yres; + optionPref.getResolution(&xres, &yres); + + TheWritableGlobalData->m_xResolution = xres; + TheWritableGlobalData->m_yResolution = yres; +} + diff --git a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp index 127b8d9cbe0..4f873b6b75b 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp @@ -1,2116 +1,2116 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: INI.cpp ////////////////////////////////////////////////////////////////////////////////// -// Author: Colin Day, November 2001 -// Desc: INI Reader -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine -#define DEFINE_DEATH_NAMES -#define DEFINE_WEAPONBONUSCONDITION_NAMES - -#include "Common/INI.h" -#include "Common/INIException.h" - -#include "Common/DamageFX.h" -#include "Common/file.h" -#include "Common/FileSystem.h" -#include "Common/GameAudio.h" -#include "Common/Science.h" -#include "Common/SpecialPower.h" -#include "Common/ThingFactory.h" -#include "Common/ThingTemplate.h" -#include "Common/Upgrade.h" -#include "Common/GlobalData.h" -#include "Common/Xfer.h" -#include "Common/XferCRC.h" - -#include "GameClient/Anim2D.h" -#include "GameClient/Color.h" -#include "GameClient/FXList.h" -#include "GameClient/GameText.h" -#include "GameClient/Image.h" -#include "GameClient/ParticleSys.h" -#include "GameLogic/Armor.h" -#include "GameLogic/ExperienceTracker.h" -#include "GameLogic/FPUControl.h" -#include "GameLogic/ObjectCreationList.h" -#include "GameLogic/ScriptEngine.h" -#include "GameLogic/Weapon.h" - -#ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -static Xfer *s_xfer = NULL; - -//------------------------------------------------------------------------------------------------- -/** This is the table of data types we can have in INI files. To add a new data type - * block make a new entry in this table and add an appropriate parsing function */ -//------------------------------------------------------------------------------------------------- -extern void parseReallyLowMHz( INI* ini); // yeah, so sue me (srj) -struct BlockParse -{ - const char *token; - INIBlockParse parse; -}; -static const BlockParse theTypeTable[] = -{ - { "AIData", INI::parseAIDataDefinition }, - { "Animation", INI::parseAnim2DDefinition }, - { "Armor", INI::parseArmorDefinition }, - { "ArmorExtend", INI::parseArmorExtendDefinition }, - { "AudioEvent", INI::parseAudioEventDefinition }, - { "AudioSettings", INI::parseAudioSettingsDefinition }, - { "Bridge", INI::parseTerrainBridgeDefinition }, - { "Campaign", INI::parseCampaignDefinition }, - { "ChallengeGenerals", INI::parseChallengeModeDefinition }, - { "CommandButton", INI::parseCommandButtonDefinition }, - { "CommandMap", INI::parseMetaMapDefinition }, - { "CommandSet", INI::parseCommandSetDefinition }, - { "ControlBarScheme", INI::parseControlBarSchemeDefinition }, - { "ControlBarResizer", INI::parseControlBarResizerDefinition }, - { "CrateData", INI::parseCrateTemplateDefinition }, - { "Credits", INI::parseCredits}, - { "WindowTransition", INI::parseWindowTransitions}, - { "DamageFX", INI::parseDamageFXDefinition }, - { "DialogEvent", INI::parseDialogDefinition }, - { "DrawGroupInfo", INI::parseDrawGroupNumberDefinition }, - { "EvaEvent", INI::parseEvaEvent }, - { "FXList", INI::parseFXListDefinition }, - { "GameData", INI::parseGameDataDefinition }, - { "InGameUI", INI::parseInGameUIDefinition }, - { "Locomotor", INI::parseLocomotorTemplateDefinition }, - { "Language", INI::parseLanguageDefinition }, - { "MapCache", INI::parseMapCacheDefinition }, - { "MapData", INI::parseMapDataDefinition }, - { "MappedImage", INI::parseMappedImageDefinition }, - { "MiscAudio", INI::parseMiscAudio}, - { "Mouse", INI::parseMouseDefinition }, - { "MouseCursor", INI::parseMouseCursorDefinition }, - { "MultiplayerColor", INI::parseMultiplayerColorDefinition }, - { "MultiplayerStartingMoneyChoice", INI::parseMultiplayerStartingMoneyChoiceDefinition }, - { "OnlineChatColors", INI::parseOnlineChatColorDefinition }, - { "MultiplayerSettings",INI::parseMultiplayerSettingsDefinition }, - { "MusicTrack", INI::parseMusicTrackDefinition }, - { "Object", INI::parseObjectDefinition }, - { "ObjectCreationList", INI::parseObjectCreationListDefinition }, - { "ObjectReskin", INI::parseObjectReskinDefinition }, - { "ObjectExtend", INI::parseObjectExtendDefinition }, - { "ParticleSystem", INI::parseParticleSystemDefinition }, - { "PlayerTemplate", INI::parsePlayerTemplateDefinition }, - { "Road", INI::parseTerrainRoadDefinition }, - { "Science", INI::parseScienceDefinition }, - { "Rank", INI::parseRankDefinition }, - { "SpecialPower", INI::parseSpecialPowerDefinition }, - { "ShellMenuScheme", INI::parseShellMenuSchemeDefinition }, - { "Terrain", INI::parseTerrainDefinition }, - { "Upgrade", INI::parseUpgradeDefinition }, - { "Video", INI::parseVideoDefinition }, - { "WaterSet", INI::parseWaterSettingDefinition }, - { "WaterTransparency", INI::parseWaterTransparencyDefinition}, - { "Weather", INI::parseWeatherDefinition}, - { "Weapon", INI::parseWeaponTemplateDefinition }, - { "WebpageURL", INI::parseWebpageURLDefinition }, - { "HeaderTemplate", INI::parseHeaderTemplateDefinition }, - { "StaticGameLOD", INI::parseStaticGameLODDefinition }, - { "DynamicGameLOD", INI::parseDynamicGameLODDefinition }, - { "LODPreset", INI::parseLODPreset }, - { "BenchProfile", INI::parseBenchProfile }, - { "ReallyLowMHz", parseReallyLowMHz }, - { "ScriptAction", ScriptEngine::parseScriptAction }, - { "ScriptCondition", ScriptEngine::parseScriptCondition }, - - { NULL, NULL }, // keep this last! -}; - - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -Bool INI::isValidINIFilename( const char *filename ) -{ - if( filename == NULL ) - return FALSE; - - Int len = strlen( filename ); - if( len < 3 ) - return FALSE; - - if( filename[ len - 1 ] != 'I' && filename[ len - 1 ] != 'i' ) - return FALSE; - - if( filename[ len - 2 ] != 'N' && filename[ len - 2 ] != 'n' ) - return FALSE; - - if( filename[ len - 3 ] != 'I' && filename[ len - 3 ] != 'i' ) - return FALSE; - - return TRUE; - -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -INI::INI( void ) -{ - - m_file = NULL; - m_readBufferNext=m_readBufferUsed=0; - m_filename = "None"; - m_loadType = INI_LOAD_INVALID; - m_lineNum = 0; - m_seps = " \n\r\t="; ///< make sure you update m_sepsPercent/m_sepsColon as well - m_sepsPercent = " \n\r\t=%%"; - m_sepsColon = " \n\r\t=:"; - m_sepsQuote = "\"\n="; ///< stop at " = EOL - m_blockEndToken = "END"; - m_endOfFile = FALSE; - m_buffer[0] = 0; -#ifdef DEBUG_CRASHING - m_curBlockStart[0] = 0; -#endif - -} // end INI - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -INI::~INI( void ) -{ - -} // end ~INI - -//------------------------------------------------------------------------------------------------- -/** Load all INI files in the specified directory (and subdirectories if indicated). - * If we are to load subdirectories, we will load them *after* we load all the - * files in the current directory */ -//------------------------------------------------------------------------------------------------- -void INI::loadDirectory( AsciiString dirName, Bool subdirs, INILoadType loadType, Xfer *pXfer ) -{ - // sanity - if( dirName.isEmpty() ) - throw INI_INVALID_DIRECTORY; - - try - { - FilenameList filenameList; - dirName.concat('\\'); - TheFileSystem->getFileListInDirectory(dirName, "*.ini", filenameList, TRUE); - // Load the INI files in the dir now, in a sorted order. This keeps things the same between machines - // in a network game. - FilenameList::const_iterator it = filenameList.begin(); - while (it != filenameList.end()) - { - AsciiString tempname; - tempname = (*it).str() + dirName.getLength(); - - if ((tempname.find('\\') == NULL) && (tempname.find('/') == NULL)) { - // this file doesn't reside in a subdirectory, load it first. - load( *it, loadType, pXfer ); - } - ++it; - } - - it = filenameList.begin(); - while (it != filenameList.end()) - { - AsciiString tempname; - tempname = (*it).str() + dirName.getLength(); - - if ((tempname.find('\\') != NULL) || (tempname.find('/') != NULL)) { - load( *it, loadType, pXfer ); - } - ++it; - } - } - catch (...) - { - // propagate the exception - throw; - } - -} // end loadDirectory - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void INI::prepFile( AsciiString filename, INILoadType loadType ) -{ - // if we have a file open already -- we can't do another one - if( m_file != NULL ) - { - - DEBUG_CRASH(( "INI::load, cannot open file '%s', file already open\n", filename.str() )); - throw INI_FILE_ALREADY_OPEN; - - } // end if - - // open the file - m_file = TheFileSystem->openFile(filename.str(), File::READ); - if( m_file == NULL ) - { - - DEBUG_CRASH(( "INI::load, cannot open file '%s'\n", filename.str() )); - throw INI_CANT_OPEN_FILE; - - } // end if - - m_file = m_file->convertToRAMFile(); - - // save our filename - m_filename = filename; - - // save our load time - m_loadType = loadType; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void INI::unPrepFile() -{ - // close the file - m_file->close(); - m_file = NULL; - m_readBufferUsed=m_readBufferNext=0; - m_filename = "None"; - m_loadType = INI_LOAD_INVALID; - m_lineNum = 0; - m_endOfFile = FALSE; - s_xfer = NULL; -} - -//------------------------------------------------------------------------------------------------- -static INIBlockParse findBlockParse(const char* token) -{ - for (const BlockParse* parse = theTypeTable; parse->token; ++parse) - { - if (strcmp( parse->token, token ) == 0) - { - return parse->parse; - } - } - return NULL; -} - -//------------------------------------------------------------------------------------------------- -static INIFieldParseProc findFieldParse(const FieldParse* parseTable, const char* token, int& offset, const void*& userData) -{ - const FieldParse* parse = parseTable; - for (; parse->token; ++parse) - { - if (strcmp( parse->token, token ) == 0) - { - offset = parse->offset; - userData = parse->userData; - return parse->parse; - } - } - - if (!parse->token && parse->parse) - { - offset = parse->offset; - userData = token; - return parse->parse; - } - else - { - return NULL; - } -} - -//------------------------------------------------------------------------------------------------- -/** Load and parse an INI file */ -//------------------------------------------------------------------------------------------------- -void INI::load( AsciiString filename, INILoadType loadType, Xfer *pXfer ) -{ - setFPMode(); // so we have consistent Real values for GameLogic -MDC - - s_xfer = pXfer; - prepFile(filename, loadType); - - try - { - - // read all lines in the file - DEBUG_ASSERTCRASH( m_endOfFile == FALSE, ("INI::load, EOF at the beginning!\n") ); - while( m_endOfFile == FALSE ) - { - // read this line - readLine(); - - AsciiString currentLine = m_buffer; - - // the first word is the type of data we're processing - const char *token = strtok( m_buffer, m_seps ); - if( token ) - { - INIBlockParse parse = findBlockParse(token); - if (parse) - { - #ifdef DEBUG_CRASHING - strcpy(m_curBlockStart, m_buffer); - #endif - try { - (*parse)( this ); - - } catch (...) { - DEBUG_CRASH(("Error parsing block '%s' in INI file '%s'\n", token, m_filename.str()) ); - char buff[1024]; - sprintf(buff, "Error parsing INI file '%s' (Line: '%s')\n", m_filename.str(), currentLine.str()); - - throw INIException(buff); - } - #ifdef DEBUG_CRASHING - strcpy(m_curBlockStart, "NO_BLOCK"); - #endif - } - else - { - DEBUG_ASSERTCRASH( 0, ("[LINE: %d - FILE: '%s'] Unknown block '%s'\n", - getLineNum(), getFilename().str(), token ) ); - throw INI_UNKNOWN_TOKEN; - } - - } // end if - - } // end while - } - catch (...) - { - unPrepFile(); - - // propagate the exception. - throw; - } - - unPrepFile(); - -} // end load - -//------------------------------------------------------------------------------------------------- -/** Read a line from the already open file. Any comments will be remved and - * therefore ignored from any given line */ -//------------------------------------------------------------------------------------------------- -void INI::readLine( void ) -{ - // sanity - DEBUG_ASSERTCRASH( m_file, ("readLine(), file pointer is NULL\n") ); - - if (m_endOfFile) - *m_buffer=0; - else - { - char *p=m_buffer; - while (p!=m_buffer+INI_MAX_CHARS_PER_LINE) - { - // get next character - if (m_readBufferNext==m_readBufferUsed) - { - // refill buffer - m_readBufferNext=0; - m_readBufferUsed=m_file->read(m_readBuffer,INI_READ_BUFFER); - - // EOF? - if (!m_readBufferUsed) - { - m_endOfFile=true; - *p=0; - break; - } - } - *p=m_readBuffer[m_readBufferNext++]; - - // CR? - if (*p=='\n') - { - *p=0; - break; - } - - DEBUG_ASSERTCRASH(*p != '\t', ("tab characters are not allowed in INI files (%s). please check your editor settings. Line Number %d\n",m_filename.str(), getLineNum())); - - // comment? - if (*p==';') - *p=0; - // whitespace? - else if (*p>0&&*p<32) - *p=' '; - p++; - } - *p=0; - - // increase our line count - m_lineNum++; - - // check for at the max - if ( p == m_buffer+INI_MAX_CHARS_PER_LINE ) - { - - DEBUG_ASSERTCRASH( 0, ("Buffer too small (%d) and was truncated, increase INI_MAX_CHARS_PER_LINE\n", - INI_MAX_CHARS_PER_LINE) ); - - } // end if - } - - if (s_xfer) - { - s_xfer->xferUser( m_buffer, sizeof( char ) * strlen( m_buffer ) ); - //DEBUG_LOG(("Xfer val is now 0x%8.8X in %s, line %s\n", ((XferCRC *)s_xfer)->getCRC(), - //m_filename.str(), m_buffer)); - } -} - -//------------------------------------------------------------------------------------------------- -/** Parse UnsignedByte from buffer and assign at location 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parseUnsignedByte( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - Int value = scanInt(token); - if (value < 0 || value > 255) - { - DEBUG_CRASH(("Bad value INI::parseUnsignedByte")); - throw ERROR_BUG; - } - *(Byte *)store = (Byte)value; -} - -//------------------------------------------------------------------------------------------------- -/** Parse signed short from buffer and assign at location 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parseShort( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - Int value = scanInt(token); - if (value < -32768 || value > 32767) - { - DEBUG_CRASH(("Bad value INI::parseShort")); - throw ERROR_BUG; - } - *(Short *)store = (Short)value; -} - -//------------------------------------------------------------------------------------------------- -/** Parse unsigned short from buffer and assign at location 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parseUnsignedShort( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - Int value = scanInt(token); - if (value < 0 || value > 65535) - { - DEBUG_CRASH(("Bad value INI::parseUnsignedShort")); - throw ERROR_BUG; - } - *(UnsignedShort *)store = (UnsignedShort)value; -} - -//------------------------------------------------------------------------------------------------- -/** Parse integer from buffer and assign at location 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parseInt( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - *(Int *)store = scanInt(token); - -} - -//------------------------------------------------------------------------------------------------- -/** Parse unsigned integer from buffer and assign at location 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parseUnsignedInt( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - *(UnsignedInt *)store = scanUnsignedInt(token); - -} - -//------------------------------------------------------------------------------------------------- -/** Parse real from buffer and assign at location 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parseReal( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - *(Real *)store = scanReal(token); - -} - -//------------------------------------------------------------------------------------------------- -/** Parse real from buffer and assign at location 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parsePositiveNonZeroReal( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - *(Real *)store = scanReal(token); - if (*(Real *)store <= 0.0f) - { - DEBUG_CRASH(("invalid Real value %f -- expected > 0\n",*(Real*)store)); - throw INI_INVALID_DATA; - } - -} - -//------------------------------------------------------------------------------------------------- -/** Parse a degree value (0 to 360) and store the radian value of that degree - * in a Real */ -//------------------------------------------------------------------------------------------------- -void INI::parseAngleReal( INI *ini, void * /*instance*/, - void *store, const void *userData ) -{ - const char *token = ini->getNextToken(); - - const Real RADS_PER_DEGREE = PI / 180.0f; - *(Real *)store = scanReal( token ) * RADS_PER_DEGREE; - -} - -//------------------------------------------------------------------------------------------------- -/** Parse an angular velocity in degrees-per-sec and store the rads-per-frame value of that degree - * in a Real */ -//------------------------------------------------------------------------------------------------- -void INI::parseAngularVelocityReal( INI *ini, void * /*instance*/, - void *store, const void *userData ) -{ - const char *token = ini->getNextToken(); - - // scan the int and convert to radian and store as a real - *(Real *)store = ConvertAngularVelocityInDegreesPerSecToRadsPerFrame(scanReal( token )); - -} - -//------------------------------------------------------------------------------------------------- -/** Parse Bool from buffer and assign at location 'store'. The buffer token must - * be in the form of a string "Yes" or "No" (case is ignored) */ -//------------------------------------------------------------------------------------------------- -void INI::parseBool( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - *(Bool*)store = INI::scanBool(ini->getNextToken()); -} - -//------------------------------------------------------------------------------------------------- -/** Parse Bool from buffer; if true, or in MASK, otherwise and out MASK. The buffer token must - * be in the form of a string "Yes" or "No" (case is ignored) */ -//------------------------------------------------------------------------------------------------- -void INI::parseBitInInt32( INI *ini, void *instance, void *store, const void* userData ) -{ - UnsignedInt* s = (UnsignedInt*)store; - UnsignedInt mask = (UnsignedInt)userData; - - if (INI::scanBool(ini->getNextToken())) - *s |= mask; - else - *s &= ~mask; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -/*static*/ Bool INI::scanBool(const char* token) -{ - // translate string yes/no into TRUE/FALSE - if( stricmp( token, "yes" ) == 0 ) - return TRUE; - else if( stricmp( token, "no" ) == 0 ) - return FALSE; - else - { - DEBUG_CRASH(("invalid boolean token %s -- expected Yes or No\n",token)); - throw INI_INVALID_DATA; - return false; // keep compiler happy - } - -} - -//------------------------------------------------------------------------------------------------- -/** Parse an *ASCII* string from buffer and assign at location 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parseAsciiString( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - AsciiString* asciiString = (AsciiString *)store; - *asciiString = ini->getNextAsciiString(); -} - -//------------------------------------------------------------------------------------------------- -/** Parse an *ASCII* string from buffer and assign at location 'store'. Has better support for quoted strings. -We don't really need this function, but parseString() is broken and we want to leave it broken to -maintain existing code. - */ -//------------------------------------------------------------------------------------------------- -void INI::parseQuotedAsciiString( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - AsciiString* asciiString = (AsciiString *)store; - *asciiString = ini->getNextQuotedAsciiString(); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void INI::parseAsciiStringVector( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - std::vector* asv = (std::vector*)store; - asv->clear(); - for (const char *token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) - { - asv->push_back(token); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void INI::parseAsciiStringVectorAppend( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - std::vector* asv = (std::vector*)store; - // nope, don't clear. duh. - // asv->clear(); - for (const char *token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) - { - asv->push_back(token); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -/* static */void INI::parseScienceVector( INI *ini, void * /*instance*/, void *store, const void *userData ) -{ - ScienceVec* asv = (ScienceVec*)store; - asv->clear(); - for (const char *token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) - { - if (stricmp(token, "None") == 0) - { - asv->clear(); - return; - } - asv->push_back(INI::scanScience( token )); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -/* static */void INI::parseWeaponBonusVector( INI *ini, void * /*instance*/, void *store, const void *userData ) -{ - WeaponBonusConditionTypeVec* asv = (WeaponBonusConditionTypeVec*)store; - asv->clear(); - for (const char *token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) - { - if (stricmp(token, "None") == 0) - { - asv->clear(); - return; - } - asv->push_back((WeaponBonusConditionType)INI::scanIndexList(token, TheWeaponBonusNames)); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -/* static */void INI::parseWeaponBonusVectorKeepDefault(INI* ini, void* /*instance*/, void* store, const void* userData) -{ - WeaponBonusConditionTypeVec* asv = (WeaponBonusConditionTypeVec*)store; - // asv->clear(); - for (const char* token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) - { - if (stricmp(token, "None") == 0) - { - asv->clear(); - return; - } - asv->push_back((WeaponBonusConditionType)INI::scanIndexList(token, TheWeaponBonusNames)); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -AsciiString INI::getNextQuotedAsciiString() -{ - AsciiString result; - char buff[INI_MAX_CHARS_PER_LINE]; - - const char *token = getNextTokenOrNull(); // if null, just leave an empty string - if (token != NULL) - { - if (token[0] != '\"') - { - // if token is simply " - result.set( token ); // Start following the " - } - else - { int strLen=0; - Bool done=FALSE; - if ((strLen=strlen(token)) > 1) - { - strcpy(buff, &token[1]); //skip the starting quote - //Check for end of quoted string. Checking here fixes cases where quoted string on same line with other data. - if (buff[strLen-2]=='"') //skip ending quote if present - { buff[strLen-2]='\0'; - done=TRUE; - } - } - - if (!done) - { - token = getNextToken(getSepsQuote()); - - if (strlen(token) > 1 && token[1] != '\t') - { - strcat(buff, " "); - strcat(buff, token); - } - else - { Int buflen=strlen(buff); - if (buff[buflen-1]=='\"') - buff[buflen-1]='\0'; - } - } - result.set(buff); - } - } - return result; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -AsciiString INI::getNextAsciiString() -{ - AsciiString result; - - const char *token = getNextTokenOrNull(); // if null, just leave an empty string - if (token != NULL) - { - if (token[0] != '\"') - { - // if token is simply " - result.set( token ); // Start following the " - } - else - { - static char buff[INI_MAX_CHARS_PER_LINE]; - buff[0] = 0; - if (strlen(token) > 1) - { - strcpy(buff, &token[1]); - } - - token = getNextTokenOrNull(getSepsQuote()); - if (token) { - if (strlen(token) > 1 && token[1] != '\t') - { - strcat(buff, " "); - } - strcat(buff, token); - result.set(buff); - } else { - Int len = strlen(buff); - if (len && buff[len-1] == '"') { // strip off trailing quote jba. [2/12/2003] - buff[len-1] = 0; - } - result.set(buff); - } - } - } - return result; -} - -//------------------------------------------------------------------------------------------------- -/** Parse a string label, get the *translated* actual text from the label and store - * into a *UNICODE* string. */ -//------------------------------------------------------------------------------------------------- -void INI::parseAndTranslateLabel( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - - // translate - UnicodeString translated = TheGameText->fetch( token ); - if( translated.isEmpty() ) - throw INI_INVALID_DATA; - - // save the translated text - UnicodeString *theString = (UnicodeString *)store; - theString->set( translated.str() ); - -} // end parseAndTranslateLabel - -//------------------------------------------------------------------------------------------------- -/** Parse a string label assumed as an image as part of the image collection. Translate - * to an image pointer for storage */ -//------------------------------------------------------------------------------------------------- -void INI::parseMappedImage( INI *ini, void * /*instance*/, void *store, const void *userData ) -{ - const char *token = ini->getNextToken(); - - if( TheMappedImageCollection ) - { - typedef const Image* ConstImagePtr; - *(ConstImagePtr*)store = TheMappedImageCollection->findImageByName( AsciiString( token ) ); - } - - //KM: If we are in the worldbuilder, we want to parse commandbuttons for informational purposes, - //but we don't care about the images -- because we never access them. In RTS/GUIEdit, they always - //exist -- and in those cases, it will never call this code anyways because it'll throw long before. - //else - // throw INI_UNKNOWN_ERROR; - -} // end parseMappedImage - -// ------------------------------------------------------------------------------------------------ -/** Parse a string label assumed as a Anim2D template name. Translate that name to an - * actual template pointer for storage */ -// ------------------------------------------------------------------------------------------------ -/*static*/ void INI::parseAnim2DTemplate( INI *ini, void *instance, void *store, const void *userData ) -{ - const char *token = ini->getNextToken(); - - if( TheAnim2DCollection ) - { - Anim2DTemplate **anim2DTemplate = (Anim2DTemplate **)store; - *anim2DTemplate = TheAnim2DCollection->findTemplate( AsciiString( token ) ); - } // end if - else - { - - DEBUG_CRASH(( "INI::parseAnim2DTemplate - TheAnim2DCollection is NULL\n" )); - throw INI_UNKNOWN_ERROR; - - } // end else - -} // end parseAnim2DTemplate - -//------------------------------------------------------------------------------------------------- -/** Parse a percent in int or real form such as "23%" or "95.4%" and assign - * to location 'store' as a number from 0.0 to 1.0 */ -//------------------------------------------------------------------------------------------------- -void INI::parsePercentToReal( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(ini->getSepsPercent()); - Real *theReal = (Real *)store; - *theReal = scanPercentToReal(token); - -} // end parsePercentToReal - -//------------------------------------------------------------------------------------------------- -/** 'store' points to an 32 bit unsigned integer. We will zero that integer, parse each token - * in the buffer, if the token is in the userData table of strings, we will set the - * according bit flag for it */ -//------------------------------------------------------------------------------------------------- -void INI::parseBitString8( INI* ini, void * /*instance*/, void *store, const void* userData ) -{ - UnsignedInt tmp; - INI::parseBitString32(ini, NULL, &tmp, userData); - if (tmp & 0xffffff00) - { - DEBUG_CRASH(("Bad bitstring list INI::parseBitString8")); - throw ERROR_BUG; - } - *(Byte*)store = (Byte)tmp; -} - -//------------------------------------------------------------------------------------------------- -/** 'store' points to an 32 bit unsigned integer. We will zero that integer, parse each token - * in the buffer, if the token is in the userData table of strings, we will set the - * according bit flag for it */ -//------------------------------------------------------------------------------------------------- -void INI::parseBitString32( INI* ini, void * /*instance*/, void *store, const void* userData ) -{ - ConstCharPtrArray flagList = (ConstCharPtrArray)userData; - UnsignedInt *bits = (UnsignedInt *)store; - - if( flagList == NULL || flagList[ 0 ] == NULL) - { - DEBUG_ASSERTCRASH( flagList, ("INTERNAL ERROR! parseBitString32: No flag list provided!\n") ); - throw INI_INVALID_NAME_LIST; - } - - Bool foundNormal = false; - Bool foundAddOrSub = false; - - // loop through all tokens - for (const char *token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) - { - if (stricmp(token, "NONE") == 0) - { - if (foundNormal || foundAddOrSub) - { - DEBUG_CRASH(("you may not mix normal and +- ops in bitstring lists")); - throw INI_INVALID_NAME_LIST; - } - *bits = 0; - break; - } - - if (token[0] == '+') - { - if (foundNormal) - { - DEBUG_CRASH(("you may not mix normal and +- ops in bitstring lists")); - throw INI_INVALID_NAME_LIST; - } - Int bitIndex = INI::scanIndexList(token+1, flagList); // this throws if the token is not found - *bits |= (1 << bitIndex); - foundAddOrSub = true; - } - else if (token[0] == '-') - { - if (foundNormal) - { - DEBUG_CRASH(("you may not mix normal and +- ops in bitstring lists")); - throw INI_INVALID_NAME_LIST; - } - Int bitIndex = INI::scanIndexList(token+1, flagList); // this throws if the token is not found - *bits &= ~(1 << bitIndex); - foundAddOrSub = true; - } - else - { - if (foundAddOrSub) - { - DEBUG_CRASH(("you may not mix normal and +- ops in bitstring lists")); - throw INI_INVALID_NAME_LIST; - } - - if (!foundNormal) - *bits = 0; - - Int bitIndex = INI::scanIndexList(token, flagList); // this throws if the token is not found - *bits |= (1 << bitIndex); - foundNormal = true; - } - } -} - -//------------------------------------------------------------------------------------------------- -/** Parse a color in the form of - * - * RGB_COLOR = R:100 G:114 B:245 - * and store in "RGBColor" structure pointed to by 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parseRGBColor( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char* names[3] = { "R", "G", "B" }; - Int colors[3]; - for( Int i = 0; i < 3; i++ ) - { - colors[i] = scanInt(ini->getNextSubToken(names[i])); - if( colors[ i ] < 0 ) - throw INI_INVALID_DATA; - if( colors[ i ] > 255 ) - throw INI_INVALID_DATA; - } - - // assign the color components to the "RGBColor" pointer at 'store' - RGBColor *theColor = (RGBColor *)store; - theColor->red = (Real)colors[ 0 ] / 255.0f; - theColor->green = (Real)colors[ 1 ] / 255.0f; - theColor->blue = (Real)colors[ 2 ] / 255.0f; - -} - - -//------------------------------------------------------------------------------------------------- -/** Parse a color in the form of - * - * RGB_COLOR = R:0.5 G:0.3 B:0.6 - * and store in "RGBColor" structure pointed to by 'store' - * Negative numbers, and values greater 1 are allowed! */ - //------------------------------------------------------------------------------------------------- -void INI::parseRGBColorReal(INI* ini, void* /*instance*/, void* store, const void* /*userData*/) -{ - const char* names[3] = { "R", "G", "B" }; - Real colors[3]; - for (Int i = 0; i < 3; i++) - { - colors[i] = scanReal(ini->getNextSubToken(names[i])); - //if (colors[i] < -255) - // throw INI_INVALID_DATA; - //if (colors[i] > 255) - // throw INI_INVALID_DATA; - } - - // assign the color components to the "RGBColor" pointer at 'store' - RGBColor* theColor = (RGBColor*)store; - theColor->red = colors[0]; - theColor->green = colors[1]; - theColor->blue = colors[2]; - -} - - -//------------------------------------------------------------------------------------------------- -/** Parse a color in the form of - * - * RGB_COLOR = R:100 G:114 B:245 [A:233] - * and store in "RGBAColorInt" structure pointed to by 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parseRGBAColorInt( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char* names[4] = { "R", "G", "B", "A" }; - Int colors[4]; - for( Int i = 0; i < 4; i++ ) - { - const char* token = ini->getNextTokenOrNull(ini->getSepsColon()); - if (token == NULL) - { - if (i < 3) - { - throw INI_INVALID_DATA; - } - else - { - // it's ok for A to be omitted. - colors[i] = 255; - } - } - else - { - // if present, the token must match. - if (stricmp(token, names[i]) != 0) - { - throw INI_INVALID_DATA; - } - colors[i] = scanInt(ini->getNextToken(ini->getSepsColon())); - } - if( colors[ i ] < 0 ) - throw INI_INVALID_DATA; - if( colors[ i ] > 255 ) - throw INI_INVALID_DATA; - } - - // - // assign the color components to the "RGBColorInt" pointer at 'store', keep - // the numbers as between 0 and 255 - // - RGBAColorInt *theColor = (RGBAColorInt *)store; - theColor->red = colors[ 0 ]; - theColor->green = colors[ 1 ]; - theColor->blue = colors[ 2 ]; - theColor->alpha = colors[ 3 ]; - -} // end parseRGBAColorInt - -//------------------------------------------------------------------------------------------------- -/** Parse a color in the form of - * - * RGB_COLOR = R:100 G:114 B:245 [A:233] - * and store in "Color" structure pointed to by 'store' */ -//------------------------------------------------------------------------------------------------- -void INI::parseColorInt( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char* names[4] = { "R", "G", "B", "A" }; - Int colors[4]; - for( Int i = 0; i < 4; i++ ) - { - const char* token = ini->getNextTokenOrNull(ini->getSepsColon()); - if (token == NULL) - { - if (i < 3) - { - throw INI_INVALID_DATA; - } - else - { - // it's ok for A to be omitted. - colors[i] = 255; - } - } - else - { - // if present, the token must match. - if (stricmp(token, names[i]) != 0) - { - throw INI_INVALID_DATA; - } - colors[i] = scanInt(ini->getNextToken(ini->getSepsColon())); - } - if( colors[ i ] < 0 ) - throw INI_INVALID_DATA; - if( colors[ i ] > 255 ) - throw INI_INVALID_DATA; - } - - // - // assign the color components to the "Color" pointer at 'store', keep - // the numbers as between 0 and 255 - // - Color *theColor = (Color *)store; - *theColor = GameMakeColor(colors[0], colors[1], colors[2], colors[3]); - -} // end parseColorInt - -//------------------------------------------------------------------------------------------------- -/** Parse a 3D coordinate of reals in the form of: - * FIELD_NAME = X:400 Y:-214.3 Z:8.6 */ -//------------------------------------------------------------------------------------------------- -void INI::parseCoord3D( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - Coord3D *theCoord = (Coord3D *)store; - - theCoord->x = scanReal(ini->getNextSubToken("X")); - theCoord->y = scanReal(ini->getNextSubToken("Y")); - theCoord->z = scanReal(ini->getNextSubToken("Z")); - -} // end parseCoord3D - -//------------------------------------------------------------------------------------------------- -/** Parse a 2D coordinate of reals in the form of: - * FIELD_NAME = X:400 Y:-214.3 */ -//------------------------------------------------------------------------------------------------- -void INI::parseCoord2D( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - Coord2D *theCoord = (Coord2D *)store; - - theCoord->x = scanReal(ini->getNextSubToken("X")); - theCoord->y = scanReal(ini->getNextSubToken("Y")); - -} // end parseCoord2D - -//------------------------------------------------------------------------------------------------- -/** Parse a 2D coordinate of Ints in the form of: - * FIELD_NAME = X:400 Y:-214 */ -//------------------------------------------------------------------------------------------------- -void INI::parseICoord2D( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - ICoord2D *theCoord = (ICoord2D *)store; - - theCoord->x = scanInt(ini->getNextSubToken("X")); - theCoord->y = scanInt(ini->getNextSubToken("Y")); - -} // end parseICoord2D - -//------------------------------------------------------------------------------------------------- -/** Parse an audio event and assign to the 'AudioEventRTS*' at store */ -//------------------------------------------------------------------------------------------------- -void INI::parseDynamicAudioEventRTS( INI *ini, void * /*instance*/, void *store, const void* userData ) -{ - const char *token = ini->getNextToken(); - DynamicAudioEventRTS** theSound = (DynamicAudioEventRTS**)store; - - // translate the string into a sound - if (stricmp(token, "NoSound") == 0) - { - if (*theSound) - { - (*theSound)->deleteInstance(); - *theSound = NULL; - } - } - else - { - if (*theSound == NULL) - *theSound = newInstance(DynamicAudioEventRTS); - (*theSound)->m_event.setEventName(AsciiString(token)); - } - - if (*theSound) - TheAudio->getInfoForAudioEvent(&(*theSound)->m_event); -} - -//------------------------------------------------------------------------------------------------- -/** Parse an audio event and assign to the 'AudioEventRTS*' at store */ -//------------------------------------------------------------------------------------------------- -void INI::parseAudioEventRTS( INI *ini, void * /*instance*/, void *store, const void* userData ) -{ - const char *token = ini->getNextToken(); - - AudioEventRTS *theSound = (AudioEventRTS*)store; - - // translate the string into a sound - if (stricmp(token, "NoSound") != 0) { - theSound->setEventName(AsciiString(token)); - } - - TheAudio->getInfoForAudioEvent(theSound); -} - -//------------------------------------------------------------------------------------------------- -/** Parse an ThingTemplate and assign to the 'ThingTemplate *' at store */ -//------------------------------------------------------------------------------------------------- -void INI::parseThingTemplate( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - - if (!TheThingFactory) - { - DEBUG_CRASH(("TheThingFactory not inited yet")); - throw ERROR_BUG; - } - - typedef const ThingTemplate *ConstThingTemplatePtr; - ConstThingTemplatePtr* theThingTemplate = (ConstThingTemplatePtr*)store; - - if (stricmp(token, "None") == 0) - { - *theThingTemplate = NULL; - } - else - { - const ThingTemplate *tt = TheThingFactory->findTemplate(token); // could be null! - DEBUG_ASSERTCRASH(tt, ("ThingTemplate %s not found!\n",token)); - // assign it, even if null! - *theThingTemplate = tt; - } - -} - -//------------------------------------------------------------------------------------------------- -/** Parse an ArmorTemplate and assign to the 'ArmorTemplate *' at store */ -//------------------------------------------------------------------------------------------------- -void INI::parseArmorTemplate( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - - typedef const ArmorTemplate *ConstArmorTemplatePtr; - ConstArmorTemplatePtr* theArmorTemplate = (ConstArmorTemplatePtr*)store; - - if (stricmp(token, "None") == 0) - { - *theArmorTemplate = NULL; - } - else - { - const ArmorTemplate *tt = TheArmorStore->findArmorTemplate(token); // could be null! - DEBUG_ASSERTCRASH(tt, ("ArmorTemplate %s not found!\n",token)); - // assign it, even if null! - *theArmorTemplate = tt; - } - -} - -//------------------------------------------------------------------------------------------------- -/** Parse an WeaponTemplate and assign to the 'WeaponTemplate *' at store */ -//------------------------------------------------------------------------------------------------- -void INI::parseWeaponTemplate( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - - typedef const WeaponTemplate *ConstWeaponTemplatePtr; - ConstWeaponTemplatePtr* theWeaponTemplate = (ConstWeaponTemplatePtr*)store; - - const WeaponTemplate *tt = TheWeaponStore->findWeaponTemplate(token); // could be null! - DEBUG_ASSERTCRASH(tt || stricmp(token, "None") == 0, ("WeaponTemplate %s not found!\n",token)); - // assign it, even if null! - *theWeaponTemplate = tt; - -} - -//------------------------------------------------------------------------------------------------- -/** Parse an FXList and assign to the 'FXList *' at store */ -//------------------------------------------------------------------------------------------------- -void INI::parseFXList( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - - typedef const FXList *ConstFXListPtr; - ConstFXListPtr* theFXList = (ConstFXListPtr*)store; - - const FXList *fxl = TheFXListStore->findFXList(token); // could be null! - DEBUG_ASSERTCRASH(fxl != NULL || stricmp(token, "None") == 0, ("FXList %s not found!\n",token)); - // assign it, even if null! - *theFXList = fxl; - -} - -//------------------------------------------------------------------------------------------------- -/** Parse a particle system and assign to 'ParticleSystemTemplate *' at store */ -//------------------------------------------------------------------------------------------------- -void INI::parseParticleSystemTemplate( INI *ini, void * /*instance*/, void *store, const void *userData ) -{ - const char *token = ini->getNextToken(); - - const ParticleSystemTemplate *pSystemT = TheParticleSystemManager->findTemplate( AsciiString( token ) ); - DEBUG_ASSERTCRASH( pSystemT || stricmp( token, "None" ) == 0, ("ParticleSystem %s not found!\n",token) ); - - typedef const ParticleSystemTemplate* ConstParticleSystemTemplatePtr; - ConstParticleSystemTemplatePtr* theParticleSystemTemplate = (ConstParticleSystemTemplatePtr*)store; - - *theParticleSystemTemplate = pSystemT; - -} // end parseParticleSystemTemplate - -//------------------------------------------------------------------------------------------------- -/** Parse an DamageFX and assign to the 'DamageFX *' at store */ -//------------------------------------------------------------------------------------------------- -void INI::parseDamageFX( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - - typedef const DamageFX *ConstDamageFXPtr; - ConstDamageFXPtr* theDamageFX = (ConstDamageFXPtr*)store; - - if (stricmp(token, "None") == 0) - { - *theDamageFX = NULL; - } - else - { - const DamageFX *fxl = TheDamageFXStore->findDamageFX(token); // could be null! - DEBUG_ASSERTCRASH(fxl, ("DamageFX %s not found!\n",token)); - // assign it, even if null! - *theDamageFX = fxl; - } - -} - -//------------------------------------------------------------------------------------------------- -/** Parse an ObjectCreationList and assign to the 'ObjectCreationList *' at store */ -//------------------------------------------------------------------------------------------------- -void INI::parseObjectCreationList( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - - typedef const ObjectCreationList *ConstObjectCreationListPtr; - ConstObjectCreationListPtr* theObjectCreationList = (ConstObjectCreationListPtr*)store; - - const ObjectCreationList *ocl = TheObjectCreationListStore->findObjectCreationList(token); // could be null! - DEBUG_ASSERTCRASH(ocl || stricmp(token, "None") == 0, ("ObjectCreationList %s not found!\n",token)); - // assign it, even if null! - *theObjectCreationList = ocl; - -} - -//------------------------------------------------------------------------------------------------- -/** Parse a upgrade template string and store as template pointer */ -//------------------------------------------------------------------------------------------------- -void INI::parseUpgradeTemplate( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - - if (!TheUpgradeCenter) - { - DEBUG_CRASH(("TheUpgradeCenter not inited yet")); - throw ERROR_BUG; - } - - const UpgradeTemplate *uu = TheUpgradeCenter->findUpgrade( AsciiString( token ) ); - DEBUG_ASSERTCRASH( uu || stricmp( token, "None" ) == 0, ("Upgrade %s not found!\n",token) ); - - typedef const UpgradeTemplate* ConstUpgradeTemplatePtr; - ConstUpgradeTemplatePtr* theUpgradeTemplate = (ConstUpgradeTemplatePtr *)store; - *theUpgradeTemplate = uu; -} - -//------------------------------------------------------------------------------------------------- -/** Parse a special power template string and store as template pointer */ -//------------------------------------------------------------------------------------------------- -void INI::parseSpecialPowerTemplate( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - - if (!TheSpecialPowerStore) - { - DEBUG_CRASH(("TheSpecialPowerStore not inited yet")); - throw ERROR_BUG; - } - - const SpecialPowerTemplate *sPowerT = TheSpecialPowerStore->findSpecialPowerTemplate( AsciiString( token ) ); - if( !sPowerT && stricmp( token, "None" ) != 0 ) - { - DEBUG_CRASH( ("[LINE: %d in '%s'] Specialpower %s not found!\n", ini->getLineNum(), ini->getFilename().str(), token) ); - } - - typedef const SpecialPowerTemplate* ConstSpecialPowerTemplatePtr; - ConstSpecialPowerTemplatePtr* theSpecialPowerTemplate = (ConstSpecialPowerTemplatePtr *)store; - *theSpecialPowerTemplate = sPowerT; -} - -//------------------------------------------------------------------------------------------------- -/** Parse a science string and store as science type */ -//------------------------------------------------------------------------------------------------- -/* static */void INI::parseScience( INI *ini, void * /*instance*/, void *store, const void *userData ) -{ - const char *token = ini->getNextToken(); - - if (!TheScienceStore) - { - DEBUG_CRASH(("TheScienceStore not inited yet")); - throw ERROR_BUG; - } - - *((ScienceType *)store) = INI::scanScience(token); - -} - -//------------------------------------------------------------------------------------------------- -/** Parse a single string token, check for that token in the index list - * of names provided and store the index into that list. - * - * NOTE: Is is assumed that we are going to store the index into - * a 4 byte integer. This works well for INT and ENUM definitions */ -//------------------------------------------------------------------------------------------------- -void INI::parseIndexList( INI* ini, void * /*instance*/, void *store, const void* userData ) -{ - ConstCharPtrArray nameList = (ConstCharPtrArray)userData; - *(Int *)store = scanIndexList(ini->getNextToken(), nameList); -} - -//------------------------------------------------------------------------------------------------- -/** returns -1 if "None", otherwise like parseIndexList **/ -//------------------------------------------------------------------------------------------------- -void INI::parseIndexListOrNone(INI* ini, void* /*instance*/, void* store, const void* userData) -{ - const char* token = ini->getNextToken(); - if (stricmp(token, "None") == 0) { - *(Int*)store = -1; - } - else { - //like parseIndexList - ConstCharPtrArray nameList = (ConstCharPtrArray)userData; - *(Int*)store = scanIndexList(token, nameList); - } -} - -//------------------------------------------------------------------------------------------------- -/** Parse a single string token, check for that token in the index list - * of names provided and store the index into that list. - * - * NOTE: Is is assumed that we are going to store the index into - * a 4 byte integer. This works well for INT and ENUM definitions */ -//------------------------------------------------------------------------------------------------- -void INI::parseByteSizedIndexList( INI* ini, void * /*instance*/, void *store, const void* userData ) -{ - ConstCharPtrArray nameList = (ConstCharPtrArray)userData; - Int value = scanIndexList(ini->getNextToken(), nameList); - if (value < 0 || value > 255) - { - DEBUG_CRASH(("Bad index list INI::parseByteSizedIndexList")); - throw ERROR_BUG; - } - *(Byte *)store = (Byte)value; -} - -//------------------------------------------------------------------------------------------------- -/** Parse a single string token, check for that token in the index list - * of names provided and store the associated value into that list. - * - * NOTE: Is is assumed that we are going to store the index into - * a 4 byte integer. This works well for INT and ENUM definitions */ -//------------------------------------------------------------------------------------------------- -void INI::parseLookupList( INI* ini, void * /*instance*/, void *store, const void* userData ) -{ - ConstLookupListRecArray lookupList = (ConstLookupListRecArray)userData; - *(Int *)store = scanLookupList(ini->getNextToken(), lookupList); -} - -//------------------------------------------------------------------------------------------------- -/** Special Handling for None = -2 (Eva_NONE), otherwise like parseIndexList **/ -//------------------------------------------------------------------------------------------------- -void INI::parseEvaNameIndexList(INI* ini, void* /*instance*/, void* store, const void* userData) -{ - const char* token = ini->getNextToken(); - if (stricmp(token, "None") == 0) { - *(Int*)store = -2; - } - else { - //like parseIndexList - ConstCharPtrArray nameList = (ConstCharPtrArray)userData; - *(Int*)store = scanIndexList(token, nameList); - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - - -//------------------------------------------------------------------------------------------------- -void MultiIniFieldParse::add(const FieldParse* f, UnsignedInt e) -{ - if (m_count < MAX_MULTI_FIELDS) - { - m_fieldParse[m_count] = f; - m_extraOffset[m_count] = e; - ++m_count; - } - else - { - DEBUG_CRASH(("too many multi-fields in INI::initFromINIMultiProc")); - throw ERROR_BUG; - } -} - -//------------------------------------------------------------------------------------------------- -void INI::initFromINI( void *what, const FieldParse* parseTable ) -{ - MultiIniFieldParse p; - p.add(parseTable); - initFromINIMulti(what, p); -} - -//------------------------------------------------------------------------------------------------- -void INI::initFromINIMultiProc( void *what, BuildMultiIniFieldProc proc ) -{ - MultiIniFieldParse p; - (*proc)(p); - initFromINIMulti(what, p); -} - -//------------------------------------------------------------------------------------------------- -void INI::initFromINIMulti( void *what, const MultiIniFieldParse& parseTableList ) -{ - Bool done = FALSE; - - if( what == NULL ) - { - DEBUG_ASSERTCRASH( 0, ("INI::initFromINI - Invalid parameters supplied!\n") ); - throw INI_INVALID_PARAMS; - } - - // read each of the data fields - while( !done ) - { - - // read next line - readLine(); - - // check for end token - const char* field = strtok( m_buffer, INI::getSeps() ); - if( field ) - { - - if( stricmp( field, m_blockEndToken ) == 0 ) - { - done = TRUE; - } - else - { - Bool found = false; - for (int ptIdx = 0; ptIdx < parseTableList.getCount(); ++ptIdx) - { - int offset = 0; - const void* userData = 0; - INIFieldParseProc parse = findFieldParse(parseTableList.getNthFieldParse(ptIdx), field, offset, userData); - if (parse) - { - // parse this block and check for parse errors - try { - - (*parse)( this, what, (char *)what + offset + parseTableList.getNthExtraOffset(ptIdx), userData ); - - } catch (...) { - DEBUG_CRASH( ("[LINE: %d - FILE: '%s'] Error reading field '%s' of block '%s'\n", - INI::getLineNum(), INI::getFilename().str(), field, m_curBlockStart) ); - - - char buff[1024]; - sprintf(buff, "[LINE: %d - FILE: '%s'] Error reading field '%s'\n", INI::getLineNum(), INI::getFilename().str(), field); - throw INIException(buff); - } - - found = true; - break; - - } - } - - if (!found) - { - DEBUG_ASSERTCRASH( 0, ("[LINE: %d - FILE: '%s'] Unknown field '%s' in block '%s'\n", - INI::getLineNum(), INI::getFilename().str(), field, m_curBlockStart) ); - throw INI_UNKNOWN_TOKEN; - } - - } // end else - - } // end if - - // sanity check for reaching end of file with no closing end token - if( done == FALSE && INI::isEOF() == TRUE ) - { - - done = TRUE; - DEBUG_ASSERTCRASH( 0, ("Error parsing block '%s', in INI file '%s'. Missing '%s' token\n", - m_curBlockStart, getFilename().str(), m_blockEndToken) ); - throw INI_MISSING_END_TOKEN; - - } // end if - - } // end while - -} - -//------------------------------------------------------------------------------------------------- -/*static*/ const char* INI::getNextToken(const char* seps) -{ - if (!seps) seps = getSeps(); - const char *token = ::strtok(NULL, seps); - if (!token) - throw INI_INVALID_DATA; - return token; -} - -//------------------------------------------------------------------------------------------------- -/*static*/ const char* INI::getNextTokenOrNull(const char* seps) -{ - if (!seps) seps = getSeps(); - const char *token = ::strtok(NULL, seps); - return token; -} - -//------------------------------------------------------------------------------------------------- -/*static*/ ScienceType INI::scanScience(const char* token) -{ - return TheScienceStore->friend_lookupScience( token ); -} - -//------------------------------------------------------------------------------------------------- -/*static*/ Int INI::scanInt(const char* token) -{ - Int value; - if (sscanf( token, "%d", &value ) != 1) - throw INI_INVALID_DATA; - return value; -} - -//------------------------------------------------------------------------------------------------- -/*static*/ UnsignedInt INI::scanUnsignedInt(const char* token) -{ - UnsignedInt value; - if (sscanf( token, "%u", &value ) != 1) // unsigned int is %u, not %d - throw INI_INVALID_DATA; - return value; -} - -//------------------------------------------------------------------------------------------------- -/*static*/ Real INI::scanReal(const char* token) -{ - Real value; - if (sscanf( token, "%f", &value ) != 1) - throw INI_INVALID_DATA; - return value; -} - -//------------------------------------------------------------------------------------------------- -/*static*/ Real INI::scanPercentToReal(const char* token) -{ - Real value; - if (sscanf( token, "%f", &value ) != 1) - throw INI_INVALID_DATA; - return value / 100.0f; -} - -//------------------------------------------------------------------------------------------------- -/*static*/ Int INI::scanIndexList(const char* token, ConstCharPtrArray nameList) -{ - if( nameList == NULL || nameList[ 0 ] == NULL ) - { - - DEBUG_ASSERTCRASH( 0, ("INTERNAL ERROR! scanIndexList, invalid name list\n") ); - throw INI_INVALID_NAME_LIST; - - } - - // search for matching name - Int count = 0; - for(ConstCharPtrArray name = nameList; *name; name++, count++ ) - { - if( stricmp( *name, token ) == 0 ) - { - return count; - } - } - - DEBUG_CRASH(("token %s is not a valid member of the index list\n",token)); - throw INI_INVALID_DATA; - return 0; // never executed, but keeps compiler happy - -} -//------------------------------------------------------------------------------------------------- -/*static*/ Int INI::scanLookupList(const char* token, ConstLookupListRecArray lookupList) -{ - if( lookupList == NULL || lookupList[ 0 ].name == NULL ) - { - DEBUG_ASSERTCRASH( 0, ("INTERNAL ERROR! scanLookupList, invalid name list\n") ); - throw INI_INVALID_NAME_LIST; - } - - // search for matching name - Bool found = false; - for( const LookupListRec* lookup = &lookupList[0]; lookup->name; lookup++ ) - { - if( stricmp( lookup->name, token ) == 0 ) - { - return lookup->value; - found = true; - break; - } - } - - DEBUG_CRASH(("token %s is not a valid member of the lookup list\n",token)); - throw INI_INVALID_DATA; - return 0; // never executed, but keeps compiler happy - -} - -//------------------------------------------------------------------------------------------------- -const char* INI::getNextSubToken(const char* expected) -{ - const char* token = getNextToken(getSepsColon()); - if (stricmp(token, expected) != 0) - throw INI_INVALID_DATA; - return getNextToken(getSepsColon()); -} - -//------------------------------------------------------------------------------------------------- -/** - * Parse a "random variable". - * The format is "FIELD = low high [distribution]". - */ -void INI::parseGameClientRandomVariable( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - GameClientRandomVariable *var = static_cast(store); - - const char* token; - - token = ini->getNextToken(); - Real low = INI::scanReal(token); - - token = ini->getNextToken(); - Real high = INI::scanReal(token); - - // if omitted, assume uniform - GameClientRandomVariable::DistributionType type = GameClientRandomVariable::UNIFORM; - token = ini->getNextTokenOrNull(); - if (token) - type = (GameClientRandomVariable::DistributionType)INI::scanIndexList(token, GameClientRandomVariable::DistributionTypeNames); - - // set the range of the random variable - var->setRange( low, high, type ); -} - -//------------------------------------------------------------------------------------------------- -// parse a duration in msec and convert to duration in frames -void INI::parseDurationReal( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - Real val = scanReal(ini->getNextToken()); - *(Real *)store = ConvertDurationFromMsecsToFrames(val); -} - -//------------------------------------------------------------------------------------------------- -// parse a duration in msec and convert to duration in integral number of frames, (unsignedint) rounding UP -void INI::parseDurationUnsignedInt( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - UnsignedInt val = scanUnsignedInt(ini->getNextToken()); - *(UnsignedInt *)store = (UnsignedInt)ceilf(ConvertDurationFromMsecsToFrames((Real)val)); -} - -// ------------------------------------------------------------------------------------------------ -// parse a duration in msec and convert to duration in integral number of frames, (unsignedshort) rounding UP -void INI::parseDurationUnsignedShort( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - UnsignedInt val = scanUnsignedInt(ini->getNextToken()); - *(UnsignedShort *)store = (UnsignedShort)ceilf(ConvertDurationFromMsecsToFrames((Real)val)); -} - -//------------------------------------------------------------------------------------------------- -// parse acceleration in (dist/sec) and convert to (dist/frame) -void INI::parseVelocityReal( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - Real val = scanReal(token); - *(Real *)store = ConvertVelocityInSecsToFrames(val); -} - -//------------------------------------------------------------------------------------------------- -// parse acceleration in (dist/sec^2) and convert to (dist/frame^2) -void INI::parseAccelerationReal( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - const char *token = ini->getNextToken(); - Real val = scanReal(token); - *(Real *)store = ConvertAccelerationInSecsToFrames(val); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void INI::parseVeterancyLevelFlags(INI* ini, void* /*instance*/, void* store, const void* /*userData*/) -{ - VeterancyLevelFlags flags = VETERANCY_LEVEL_FLAGS_ALL; - for (const char* token = ini->getNextToken(); token; token = ini->getNextTokenOrNull()) - { - if (stricmp(token, "ALL") == 0) - { - flags = VETERANCY_LEVEL_FLAGS_ALL; - continue; - } - else if (stricmp(token, "NONE") == 0) - { - flags = VETERANCY_LEVEL_FLAGS_NONE; - continue; - } - else if (token[0] == '+') - { - VeterancyLevel dt = (VeterancyLevel)INI::scanIndexList(token+1, TheVeterancyNames); - flags = setVeterancyLevelFlag(flags, dt); - continue; - } - else if (token[0] == '-') - { - VeterancyLevel dt = (VeterancyLevel)INI::scanIndexList(token+1, TheVeterancyNames); - flags = clearVeterancyLevelFlag(flags, dt); - continue; - } - else - { - throw INI_UNKNOWN_TOKEN; - } - } - *(VeterancyLevelFlags*)store = flags; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void INI::parseSoundsList( INI* ini, void *instance, void *store, const void* /*userData*/ ) -{ - std::vector *vec = (std::vector*) store; - vec->clear(); - - const char* SEPS = " \t,="; - const char *c = ini->getNextTokenOrNull(SEPS); - while ( c ) - { - vec->push_back( c ); - c = ini->getNextTokenOrNull(SEPS); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void INI::parseDamageTypeFlags(INI* ini, void* /*instance*/, void* store, const void* /*userData*/) -{ - DamageTypeFlags flags = DAMAGE_TYPE_FLAGS_NONE; - flags.flip(); - - for (const char* token = ini->getNextToken(); token; token = ini->getNextTokenOrNull()) - { - if (stricmp(token, "ALL") == 0) - { - flags = DAMAGE_TYPE_FLAGS_NONE; - flags.flip(); - continue; - } - if (stricmp(token, "NONE") == 0) - { - flags = DAMAGE_TYPE_FLAGS_NONE; - continue; - } - if (token[0] == '+') - { - DamageType dt = (DamageType)DamageTypeFlags::getSingleBitFromName(token+1); - flags = setDamageTypeFlag(flags, dt); - continue; - } - if (token[0] == '-') - { - DamageType dt = (DamageType)DamageTypeFlags::getSingleBitFromName(token+1); - flags = clearDamageTypeFlag(flags, dt); - continue; - } - throw INI_UNKNOWN_TOKEN; - } - *(DamageTypeFlags*)store = flags; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void INI::parseDeathTypeFlags(INI* ini, void* /*instance*/, void* store, const void* /*userData*/) -{ - DeathTypeFlags flags = DEATH_TYPE_FLAGS_ALL; - - for (const char* token = ini->getNextToken(); token; token = ini->getNextTokenOrNull()) - { - if (stricmp(token, "ALL") == 0) - { - flags = DEATH_TYPE_FLAGS_ALL; - - if (TheGlobalData) { - flags &= ~TheGlobalData->m_defaultExcludedDeathTypes; - DEBUG_LOG(("INI::parseDeathTypeFlags - flags = %X\n", flags)); - } - else { - DEBUG_LOG(("INI::parseDeathTypeFlags - TheGlobalData is NULL\n")); - } - - continue; - } - if (stricmp(token, "NONE") == 0) - { - flags = DEATH_TYPE_FLAGS_NONE; - continue; - } - if (token[0] == '+') - { - DeathType dt = (DeathType)INI::scanIndexList(token+1, TheDeathNames); - flags = setDeathTypeFlag(flags, dt); - continue; - } - if (token[0] == '-') - { - DeathType dt = (DeathType)INI::scanIndexList(token+1, TheDeathNames); - flags = clearDeathTypeFlag(flags, dt); - continue; - } - throw INI_UNKNOWN_TOKEN; - } - *(DeathTypeFlags*)store = flags; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -// Parse a simple list, no +/- syntax allowed -void INI::parseDeathTypeFlagsList(INI* ini, void* /*instance*/, void* store, const void* /*userData*/) -{ - DeathTypeFlags flags = DEATH_TYPE_FLAGS_NONE; - for (const char* token = ini->getNextToken(); token; token = ini->getNextTokenOrNull()) - { - if (stricmp(token, "ALL") == 0) - { - flags = DEATH_TYPE_FLAGS_ALL; - continue; - } - if (stricmp(token, "NONE") == 0) - { - flags = DEATH_TYPE_FLAGS_NONE; - continue; - } - - DeathType dt = (DeathType)INI::scanIndexList(token, TheDeathNames); - flags = setDeathTypeFlag(flags, dt); - } - *(DeathTypeFlags*)store = flags; -} - -//------------------------------------------------------------------------------------------------- -// parse the line and return whether the given line is a Block declaration of the form -// [whitespace] blockType [whitespace] blockName [EOL] -// both blockType and blockName are case insensitive -Bool INI::isDeclarationOfType( AsciiString blockType, AsciiString blockName, char *bufferToCheck ) -{ - Bool retVal = true; - if (!bufferToCheck || blockType.isEmpty() || blockName.isEmpty()) { - return false; - } - // DO NOT RETURN EARLY FROM THIS FUNCTION. (beyond this point) - // we have to restore the bufferToCheck to its previous state before returning, so - // it is important to get through all the checks. - - char restoreChar; - char *tempBuff = bufferToCheck; - int blockTypeLength = blockType.getLength(); - int blockNameLength = blockName.getLength(); - - while (isspace(*tempBuff)) { - ++tempBuff; - } - - if (strlen(tempBuff) > blockTypeLength) { - restoreChar = tempBuff[blockTypeLength]; - tempBuff[blockTypeLength] = 0; - - if (stricmp(blockType.str(), tempBuff) != 0) { - retVal = false; - } - - tempBuff[blockTypeLength] = restoreChar; - tempBuff = tempBuff + blockTypeLength; - } else { - retVal = false; - } - - while (isspace(*tempBuff)) { - ++tempBuff; - } - - if (strlen(tempBuff) > blockNameLength) { - restoreChar = tempBuff[blockNameLength]; - tempBuff[blockNameLength] = 0; - - if (stricmp(blockName.str(), tempBuff) != 0) { - retVal = false; - } - - tempBuff[blockNameLength] = restoreChar; - tempBuff = tempBuff + blockNameLength; - } else { - retVal = false; - } - - while (strlen(tempBuff)) { - retVal = retVal && isspace(tempBuff[0]); - ++tempBuff; - } - - return retVal; -} - -//------------------------------------------------------------------------------------------------- -// parse the line and return whether the given line is a Block declaration of the form -// [whitespace] end [EOL] -Bool INI::isEndOfBlock( char *bufferToCheck ) -{ - Bool retVal = true; - if (!bufferToCheck) { - return false; - } - - // DO NOT RETURN EARLY FROM THIS FUNCTION (beyond this point) - // we have to restore the bufferToCheck to its previous state before returning, so - // it is important to get through all the checks. - - static const char* endString = "End"; - int endStringLength = strlen(endString); - char restoreChar; - char *tempBuff = bufferToCheck; - - - while (isspace(*tempBuff)) { - ++tempBuff; - } - - if (strlen(tempBuff) > endStringLength) { - restoreChar = tempBuff[endStringLength]; - tempBuff[endStringLength] = 0; - - if (stricmp(endString, tempBuff) != 0) { - retVal = false; - } - - tempBuff[endStringLength] = restoreChar; - tempBuff = tempBuff + endStringLength; - } else { - retVal = false; - } - - while (strlen(tempBuff)) { - retVal = retVal && isspace(tempBuff[0]); - ++tempBuff; - } - - return retVal; -} +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: INI.cpp ////////////////////////////////////////////////////////////////////////////////// +// Author: Colin Day, November 2001 +// Desc: INI Reader +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine +#define DEFINE_DEATH_NAMES +#define DEFINE_WEAPONBONUSCONDITION_NAMES + +#include "Common/INI.h" +#include "Common/INIException.h" + +#include "Common/DamageFX.h" +#include "Common/file.h" +#include "Common/FileSystem.h" +#include "Common/GameAudio.h" +#include "Common/Science.h" +#include "Common/SpecialPower.h" +#include "Common/ThingFactory.h" +#include "Common/ThingTemplate.h" +#include "Common/Upgrade.h" +#include "Common/GlobalData.h" +#include "Common/Xfer.h" +#include "Common/XferCRC.h" + +#include "GameClient/Anim2D.h" +#include "GameClient/Color.h" +#include "GameClient/FXList.h" +#include "GameClient/GameText.h" +#include "GameClient/Image.h" +#include "GameClient/ParticleSys.h" +#include "GameLogic/Armor.h" +#include "GameLogic/ExperienceTracker.h" +#include "GameLogic/FPUControl.h" +#include "GameLogic/ObjectCreationList.h" +#include "GameLogic/ScriptEngine.h" +#include "GameLogic/Weapon.h" + +#ifdef RTS_INTERNAL +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +static Xfer *s_xfer = NULL; + +//------------------------------------------------------------------------------------------------- +/** This is the table of data types we can have in INI files. To add a new data type + * block make a new entry in this table and add an appropriate parsing function */ +//------------------------------------------------------------------------------------------------- +extern void parseReallyLowMHz( INI* ini); // yeah, so sue me (srj) +struct BlockParse +{ + const char *token; + INIBlockParse parse; +}; +static const BlockParse theTypeTable[] = +{ + { "AIData", INI::parseAIDataDefinition }, + { "Animation", INI::parseAnim2DDefinition }, + { "Armor", INI::parseArmorDefinition }, + { "ArmorExtend", INI::parseArmorExtendDefinition }, + { "AudioEvent", INI::parseAudioEventDefinition }, + { "AudioSettings", INI::parseAudioSettingsDefinition }, + { "Bridge", INI::parseTerrainBridgeDefinition }, + { "Campaign", INI::parseCampaignDefinition }, + { "ChallengeGenerals", INI::parseChallengeModeDefinition }, + { "CommandButton", INI::parseCommandButtonDefinition }, + { "CommandMap", INI::parseMetaMapDefinition }, + { "CommandSet", INI::parseCommandSetDefinition }, + { "ControlBarScheme", INI::parseControlBarSchemeDefinition }, + { "ControlBarResizer", INI::parseControlBarResizerDefinition }, + { "CrateData", INI::parseCrateTemplateDefinition }, + { "Credits", INI::parseCredits}, + { "WindowTransition", INI::parseWindowTransitions}, + { "DamageFX", INI::parseDamageFXDefinition }, + { "DialogEvent", INI::parseDialogDefinition }, + { "DrawGroupInfo", INI::parseDrawGroupNumberDefinition }, + { "EvaEvent", INI::parseEvaEvent }, + { "FXList", INI::parseFXListDefinition }, + { "GameData", INI::parseGameDataDefinition }, + { "InGameUI", INI::parseInGameUIDefinition }, + { "Locomotor", INI::parseLocomotorTemplateDefinition }, + { "Language", INI::parseLanguageDefinition }, + { "MapCache", INI::parseMapCacheDefinition }, + { "MapData", INI::parseMapDataDefinition }, + { "MappedImage", INI::parseMappedImageDefinition }, + { "MiscAudio", INI::parseMiscAudio}, + { "Mouse", INI::parseMouseDefinition }, + { "MouseCursor", INI::parseMouseCursorDefinition }, + { "MultiplayerColor", INI::parseMultiplayerColorDefinition }, + { "MultiplayerStartingMoneyChoice", INI::parseMultiplayerStartingMoneyChoiceDefinition }, + { "OnlineChatColors", INI::parseOnlineChatColorDefinition }, + { "MultiplayerSettings",INI::parseMultiplayerSettingsDefinition }, + { "MusicTrack", INI::parseMusicTrackDefinition }, + { "Object", INI::parseObjectDefinition }, + { "ObjectCreationList", INI::parseObjectCreationListDefinition }, + { "ObjectReskin", INI::parseObjectReskinDefinition }, + { "ObjectExtend", INI::parseObjectExtendDefinition }, + { "ParticleSystem", INI::parseParticleSystemDefinition }, + { "PlayerTemplate", INI::parsePlayerTemplateDefinition }, + { "Road", INI::parseTerrainRoadDefinition }, + { "Science", INI::parseScienceDefinition }, + { "Rank", INI::parseRankDefinition }, + { "SpecialPower", INI::parseSpecialPowerDefinition }, + { "ShellMenuScheme", INI::parseShellMenuSchemeDefinition }, + { "Terrain", INI::parseTerrainDefinition }, + { "Upgrade", INI::parseUpgradeDefinition }, + { "Video", INI::parseVideoDefinition }, + { "WaterSet", INI::parseWaterSettingDefinition }, + { "WaterTransparency", INI::parseWaterTransparencyDefinition}, + { "Weather", INI::parseWeatherDefinition}, + { "Weapon", INI::parseWeaponTemplateDefinition }, + { "WebpageURL", INI::parseWebpageURLDefinition }, + { "HeaderTemplate", INI::parseHeaderTemplateDefinition }, + { "StaticGameLOD", INI::parseStaticGameLODDefinition }, + { "DynamicGameLOD", INI::parseDynamicGameLODDefinition }, + { "LODPreset", INI::parseLODPreset }, + { "BenchProfile", INI::parseBenchProfile }, + { "ReallyLowMHz", parseReallyLowMHz }, + { "ScriptAction", ScriptEngine::parseScriptAction }, + { "ScriptCondition", ScriptEngine::parseScriptCondition }, + + { NULL, NULL }, // keep this last! +}; + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +Bool INI::isValidINIFilename( const char *filename ) +{ + if( filename == NULL ) + return FALSE; + + Int len = strlen( filename ); + if( len < 3 ) + return FALSE; + + if( filename[ len - 1 ] != 'I' && filename[ len - 1 ] != 'i' ) + return FALSE; + + if( filename[ len - 2 ] != 'N' && filename[ len - 2 ] != 'n' ) + return FALSE; + + if( filename[ len - 3 ] != 'I' && filename[ len - 3 ] != 'i' ) + return FALSE; + + return TRUE; + +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +INI::INI( void ) +{ + + m_file = NULL; + m_readBufferNext=m_readBufferUsed=0; + m_filename = "None"; + m_loadType = INI_LOAD_INVALID; + m_lineNum = 0; + m_seps = " \n\r\t="; ///< make sure you update m_sepsPercent/m_sepsColon as well + m_sepsPercent = " \n\r\t=%%"; + m_sepsColon = " \n\r\t=:"; + m_sepsQuote = "\"\n="; ///< stop at " = EOL + m_blockEndToken = "END"; + m_endOfFile = FALSE; + m_buffer[0] = 0; +#ifdef DEBUG_CRASHING + m_curBlockStart[0] = 0; +#endif + +} // end INI + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +INI::~INI( void ) +{ + +} // end ~INI + +//------------------------------------------------------------------------------------------------- +/** Load all INI files in the specified directory (and subdirectories if indicated). + * If we are to load subdirectories, we will load them *after* we load all the + * files in the current directory */ +//------------------------------------------------------------------------------------------------- +void INI::loadDirectory( AsciiString dirName, Bool subdirs, INILoadType loadType, Xfer *pXfer ) +{ + // sanity + if( dirName.isEmpty() ) + throw INI_INVALID_DIRECTORY; + + try + { + FilenameList filenameList; + dirName.concat('\\'); + TheFileSystem->getFileListInDirectory(dirName, "*.ini", filenameList, TRUE); + // Load the INI files in the dir now, in a sorted order. This keeps things the same between machines + // in a network game. + FilenameList::const_iterator it = filenameList.begin(); + while (it != filenameList.end()) + { + AsciiString tempname; + tempname = (*it).str() + dirName.getLength(); + + if ((tempname.find('\\') == NULL) && (tempname.find('/') == NULL)) { + // this file doesn't reside in a subdirectory, load it first. + load( *it, loadType, pXfer ); + } + ++it; + } + + it = filenameList.begin(); + while (it != filenameList.end()) + { + AsciiString tempname; + tempname = (*it).str() + dirName.getLength(); + + if ((tempname.find('\\') != NULL) || (tempname.find('/') != NULL)) { + load( *it, loadType, pXfer ); + } + ++it; + } + } + catch (...) + { + // propagate the exception + throw; + } + +} // end loadDirectory + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void INI::prepFile( AsciiString filename, INILoadType loadType ) +{ + // if we have a file open already -- we can't do another one + if( m_file != NULL ) + { + + DEBUG_CRASH(( "INI::load, cannot open file '%s', file already open\n", filename.str() )); + throw INI_FILE_ALREADY_OPEN; + + } // end if + + // open the file + m_file = TheFileSystem->openFile(filename.str(), File::READ); + if( m_file == NULL ) + { + + DEBUG_CRASH(( "INI::load, cannot open file '%s'\n", filename.str() )); + throw INI_CANT_OPEN_FILE; + + } // end if + + m_file = m_file->convertToRAMFile(); + + // save our filename + m_filename = filename; + + // save our load time + m_loadType = loadType; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void INI::unPrepFile() +{ + // close the file + m_file->close(); + m_file = NULL; + m_readBufferUsed=m_readBufferNext=0; + m_filename = "None"; + m_loadType = INI_LOAD_INVALID; + m_lineNum = 0; + m_endOfFile = FALSE; + s_xfer = NULL; +} + +//------------------------------------------------------------------------------------------------- +static INIBlockParse findBlockParse(const char* token) +{ + for (const BlockParse* parse = theTypeTable; parse->token; ++parse) + { + if (strcmp( parse->token, token ) == 0) + { + return parse->parse; + } + } + return NULL; +} + +//------------------------------------------------------------------------------------------------- +static INIFieldParseProc findFieldParse(const FieldParse* parseTable, const char* token, int& offset, const void*& userData) +{ + const FieldParse* parse = parseTable; + for (; parse->token; ++parse) + { + if (strcmp( parse->token, token ) == 0) + { + offset = parse->offset; + userData = parse->userData; + return parse->parse; + } + } + + if (!parse->token && parse->parse) + { + offset = parse->offset; + userData = token; + return parse->parse; + } + else + { + return NULL; + } +} + +//------------------------------------------------------------------------------------------------- +/** Load and parse an INI file */ +//------------------------------------------------------------------------------------------------- +void INI::load( AsciiString filename, INILoadType loadType, Xfer *pXfer ) +{ + setFPMode(); // so we have consistent Real values for GameLogic -MDC + + s_xfer = pXfer; + prepFile(filename, loadType); + + try + { + + // read all lines in the file + DEBUG_ASSERTCRASH( m_endOfFile == FALSE, ("INI::load, EOF at the beginning!\n") ); + while( m_endOfFile == FALSE ) + { + // read this line + readLine(); + + AsciiString currentLine = m_buffer; + + // the first word is the type of data we're processing + const char *token = strtok( m_buffer, m_seps ); + if( token ) + { + INIBlockParse parse = findBlockParse(token); + if (parse) + { + #ifdef DEBUG_CRASHING + strcpy(m_curBlockStart, m_buffer); + #endif + try { + (*parse)( this ); + + } catch (...) { + DEBUG_CRASH(("Error parsing block '%s' in INI file '%s'\n", token, m_filename.str()) ); + char buff[1024]; + sprintf(buff, "Error parsing INI file '%s' (Line: '%s')\n", m_filename.str(), currentLine.str()); + + throw INIException(buff); + } + #ifdef DEBUG_CRASHING + strcpy(m_curBlockStart, "NO_BLOCK"); + #endif + } + else + { + DEBUG_ASSERTCRASH( 0, ("[LINE: %d - FILE: '%s'] Unknown block '%s'\n", + getLineNum(), getFilename().str(), token ) ); + throw INI_UNKNOWN_TOKEN; + } + + } // end if + + } // end while + } + catch (...) + { + unPrepFile(); + + // propagate the exception. + throw; + } + + unPrepFile(); + +} // end load + +//------------------------------------------------------------------------------------------------- +/** Read a line from the already open file. Any comments will be remved and + * therefore ignored from any given line */ +//------------------------------------------------------------------------------------------------- +void INI::readLine( void ) +{ + // sanity + DEBUG_ASSERTCRASH( m_file, ("readLine(), file pointer is NULL\n") ); + + if (m_endOfFile) + *m_buffer=0; + else + { + char *p=m_buffer; + while (p!=m_buffer+INI_MAX_CHARS_PER_LINE) + { + // get next character + if (m_readBufferNext==m_readBufferUsed) + { + // refill buffer + m_readBufferNext=0; + m_readBufferUsed=m_file->read(m_readBuffer,INI_READ_BUFFER); + + // EOF? + if (!m_readBufferUsed) + { + m_endOfFile=true; + *p=0; + break; + } + } + *p=m_readBuffer[m_readBufferNext++]; + + // CR? + if (*p=='\n') + { + *p=0; + break; + } + + DEBUG_ASSERTCRASH(*p != '\t', ("tab characters are not allowed in INI files (%s). please check your editor settings. Line Number %d\n",m_filename.str(), getLineNum())); + + // comment? + if (*p==';') + *p=0; + // whitespace? + else if (*p>0&&*p<32) + *p=' '; + p++; + } + *p=0; + + // increase our line count + m_lineNum++; + + // check for at the max + if ( p == m_buffer+INI_MAX_CHARS_PER_LINE ) + { + + DEBUG_ASSERTCRASH( 0, ("Buffer too small (%d) and was truncated, increase INI_MAX_CHARS_PER_LINE\n", + INI_MAX_CHARS_PER_LINE) ); + + } // end if + } + + if (s_xfer) + { + s_xfer->xferUser( m_buffer, sizeof( char ) * strlen( m_buffer ) ); + //DEBUG_LOG(("Xfer val is now 0x%8.8X in %s, line %s\n", ((XferCRC *)s_xfer)->getCRC(), + //m_filename.str(), m_buffer)); + } +} + +//------------------------------------------------------------------------------------------------- +/** Parse UnsignedByte from buffer and assign at location 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parseUnsignedByte( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + Int value = scanInt(token); + if (value < 0 || value > 255) + { + DEBUG_CRASH(("Bad value INI::parseUnsignedByte")); + throw ERROR_BUG; + } + *(Byte *)store = (Byte)value; +} + +//------------------------------------------------------------------------------------------------- +/** Parse signed short from buffer and assign at location 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parseShort( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + Int value = scanInt(token); + if (value < -32768 || value > 32767) + { + DEBUG_CRASH(("Bad value INI::parseShort")); + throw ERROR_BUG; + } + *(Short *)store = (Short)value; +} + +//------------------------------------------------------------------------------------------------- +/** Parse unsigned short from buffer and assign at location 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parseUnsignedShort( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + Int value = scanInt(token); + if (value < 0 || value > 65535) + { + DEBUG_CRASH(("Bad value INI::parseUnsignedShort")); + throw ERROR_BUG; + } + *(UnsignedShort *)store = (UnsignedShort)value; +} + +//------------------------------------------------------------------------------------------------- +/** Parse integer from buffer and assign at location 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parseInt( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + *(Int *)store = scanInt(token); + +} + +//------------------------------------------------------------------------------------------------- +/** Parse unsigned integer from buffer and assign at location 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parseUnsignedInt( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + *(UnsignedInt *)store = scanUnsignedInt(token); + +} + +//------------------------------------------------------------------------------------------------- +/** Parse real from buffer and assign at location 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parseReal( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + *(Real *)store = scanReal(token); + +} + +//------------------------------------------------------------------------------------------------- +/** Parse real from buffer and assign at location 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parsePositiveNonZeroReal( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + *(Real *)store = scanReal(token); + if (*(Real *)store <= 0.0f) + { + DEBUG_CRASH(("invalid Real value %f -- expected > 0\n",*(Real*)store)); + throw INI_INVALID_DATA; + } + +} + +//------------------------------------------------------------------------------------------------- +/** Parse a degree value (0 to 360) and store the radian value of that degree + * in a Real */ +//------------------------------------------------------------------------------------------------- +void INI::parseAngleReal( INI *ini, void * /*instance*/, + void *store, const void *userData ) +{ + const char *token = ini->getNextToken(); + + const Real RADS_PER_DEGREE = PI / 180.0f; + *(Real *)store = scanReal( token ) * RADS_PER_DEGREE; + +} + +//------------------------------------------------------------------------------------------------- +/** Parse an angular velocity in degrees-per-sec and store the rads-per-frame value of that degree + * in a Real */ +//------------------------------------------------------------------------------------------------- +void INI::parseAngularVelocityReal( INI *ini, void * /*instance*/, + void *store, const void *userData ) +{ + const char *token = ini->getNextToken(); + + // scan the int and convert to radian and store as a real + *(Real *)store = ConvertAngularVelocityInDegreesPerSecToRadsPerFrame(scanReal( token )); + +} + +//------------------------------------------------------------------------------------------------- +/** Parse Bool from buffer and assign at location 'store'. The buffer token must + * be in the form of a string "Yes" or "No" (case is ignored) */ +//------------------------------------------------------------------------------------------------- +void INI::parseBool( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + *(Bool*)store = INI::scanBool(ini->getNextToken()); +} + +//------------------------------------------------------------------------------------------------- +/** Parse Bool from buffer; if true, or in MASK, otherwise and out MASK. The buffer token must + * be in the form of a string "Yes" or "No" (case is ignored) */ +//------------------------------------------------------------------------------------------------- +void INI::parseBitInInt32( INI *ini, void *instance, void *store, const void* userData ) +{ + UnsignedInt* s = (UnsignedInt*)store; + UnsignedInt mask = (UnsignedInt)userData; + + if (INI::scanBool(ini->getNextToken())) + *s |= mask; + else + *s &= ~mask; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +/*static*/ Bool INI::scanBool(const char* token) +{ + // translate string yes/no into TRUE/FALSE + if( stricmp( token, "yes" ) == 0 ) + return TRUE; + else if( stricmp( token, "no" ) == 0 ) + return FALSE; + else + { + DEBUG_CRASH(("invalid boolean token %s -- expected Yes or No\n",token)); + throw INI_INVALID_DATA; + return false; // keep compiler happy + } + +} + +//------------------------------------------------------------------------------------------------- +/** Parse an *ASCII* string from buffer and assign at location 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parseAsciiString( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + AsciiString* asciiString = (AsciiString *)store; + *asciiString = ini->getNextAsciiString(); +} + +//------------------------------------------------------------------------------------------------- +/** Parse an *ASCII* string from buffer and assign at location 'store'. Has better support for quoted strings. +We don't really need this function, but parseString() is broken and we want to leave it broken to +maintain existing code. + */ +//------------------------------------------------------------------------------------------------- +void INI::parseQuotedAsciiString( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + AsciiString* asciiString = (AsciiString *)store; + *asciiString = ini->getNextQuotedAsciiString(); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void INI::parseAsciiStringVector( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + std::vector* asv = (std::vector*)store; + asv->clear(); + for (const char *token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) + { + asv->push_back(token); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void INI::parseAsciiStringVectorAppend( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + std::vector* asv = (std::vector*)store; + // nope, don't clear. duh. + // asv->clear(); + for (const char *token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) + { + asv->push_back(token); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +/* static */void INI::parseScienceVector( INI *ini, void * /*instance*/, void *store, const void *userData ) +{ + ScienceVec* asv = (ScienceVec*)store; + asv->clear(); + for (const char *token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) + { + if (stricmp(token, "None") == 0) + { + asv->clear(); + return; + } + asv->push_back(INI::scanScience( token )); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +/* static */void INI::parseWeaponBonusVector( INI *ini, void * /*instance*/, void *store, const void *userData ) +{ + WeaponBonusConditionTypeVec* asv = (WeaponBonusConditionTypeVec*)store; + asv->clear(); + for (const char *token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) + { + if (stricmp(token, "None") == 0) + { + asv->clear(); + return; + } + asv->push_back((WeaponBonusConditionType)INI::scanIndexList(token, TheWeaponBonusNames)); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +/* static */void INI::parseWeaponBonusVectorKeepDefault(INI* ini, void* /*instance*/, void* store, const void* userData) +{ + WeaponBonusConditionTypeVec* asv = (WeaponBonusConditionTypeVec*)store; + // asv->clear(); + for (const char* token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) + { + if (stricmp(token, "None") == 0) + { + asv->clear(); + return; + } + asv->push_back((WeaponBonusConditionType)INI::scanIndexList(token, TheWeaponBonusNames)); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +AsciiString INI::getNextQuotedAsciiString() +{ + AsciiString result; + char buff[INI_MAX_CHARS_PER_LINE]; + + const char *token = getNextTokenOrNull(); // if null, just leave an empty string + if (token != NULL) + { + if (token[0] != '\"') + { + // if token is simply " + result.set( token ); // Start following the " + } + else + { int strLen=0; + Bool done=FALSE; + if ((strLen=strlen(token)) > 1) + { + strcpy(buff, &token[1]); //skip the starting quote + //Check for end of quoted string. Checking here fixes cases where quoted string on same line with other data. + if (buff[strLen-2]=='"') //skip ending quote if present + { buff[strLen-2]='\0'; + done=TRUE; + } + } + + if (!done) + { + token = getNextToken(getSepsQuote()); + + if (strlen(token) > 1 && token[1] != '\t') + { + strcat(buff, " "); + strcat(buff, token); + } + else + { Int buflen=strlen(buff); + if (buff[buflen-1]=='\"') + buff[buflen-1]='\0'; + } + } + result.set(buff); + } + } + return result; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +AsciiString INI::getNextAsciiString() +{ + AsciiString result; + + const char *token = getNextTokenOrNull(); // if null, just leave an empty string + if (token != NULL) + { + if (token[0] != '\"') + { + // if token is simply " + result.set( token ); // Start following the " + } + else + { + static char buff[INI_MAX_CHARS_PER_LINE]; + buff[0] = 0; + if (strlen(token) > 1) + { + strcpy(buff, &token[1]); + } + + token = getNextTokenOrNull(getSepsQuote()); + if (token) { + if (strlen(token) > 1 && token[1] != '\t') + { + strcat(buff, " "); + } + strcat(buff, token); + result.set(buff); + } else { + Int len = strlen(buff); + if (len && buff[len-1] == '"') { // strip off trailing quote jba. [2/12/2003] + buff[len-1] = 0; + } + result.set(buff); + } + } + } + return result; +} + +//------------------------------------------------------------------------------------------------- +/** Parse a string label, get the *translated* actual text from the label and store + * into a *UNICODE* string. */ +//------------------------------------------------------------------------------------------------- +void INI::parseAndTranslateLabel( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + + // translate + UnicodeString translated = TheGameText->fetch( token ); + if( translated.isEmpty() ) + throw INI_INVALID_DATA; + + // save the translated text + UnicodeString *theString = (UnicodeString *)store; + theString->set( translated.str() ); + +} // end parseAndTranslateLabel + +//------------------------------------------------------------------------------------------------- +/** Parse a string label assumed as an image as part of the image collection. Translate + * to an image pointer for storage */ +//------------------------------------------------------------------------------------------------- +void INI::parseMappedImage( INI *ini, void * /*instance*/, void *store, const void *userData ) +{ + const char *token = ini->getNextToken(); + + if( TheMappedImageCollection ) + { + typedef const Image* ConstImagePtr; + *(ConstImagePtr*)store = TheMappedImageCollection->findImageByName( AsciiString( token ) ); + } + + //KM: If we are in the worldbuilder, we want to parse commandbuttons for informational purposes, + //but we don't care about the images -- because we never access them. In RTS/GUIEdit, they always + //exist -- and in those cases, it will never call this code anyways because it'll throw long before. + //else + // throw INI_UNKNOWN_ERROR; + +} // end parseMappedImage + +// ------------------------------------------------------------------------------------------------ +/** Parse a string label assumed as a Anim2D template name. Translate that name to an + * actual template pointer for storage */ +// ------------------------------------------------------------------------------------------------ +/*static*/ void INI::parseAnim2DTemplate( INI *ini, void *instance, void *store, const void *userData ) +{ + const char *token = ini->getNextToken(); + + if( TheAnim2DCollection ) + { + Anim2DTemplate **anim2DTemplate = (Anim2DTemplate **)store; + *anim2DTemplate = TheAnim2DCollection->findTemplate( AsciiString( token ) ); + } // end if + else + { + + DEBUG_CRASH(( "INI::parseAnim2DTemplate - TheAnim2DCollection is NULL\n" )); + throw INI_UNKNOWN_ERROR; + + } // end else + +} // end parseAnim2DTemplate + +//------------------------------------------------------------------------------------------------- +/** Parse a percent in int or real form such as "23%" or "95.4%" and assign + * to location 'store' as a number from 0.0 to 1.0 */ +//------------------------------------------------------------------------------------------------- +void INI::parsePercentToReal( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(ini->getSepsPercent()); + Real *theReal = (Real *)store; + *theReal = scanPercentToReal(token); + +} // end parsePercentToReal + +//------------------------------------------------------------------------------------------------- +/** 'store' points to an 32 bit unsigned integer. We will zero that integer, parse each token + * in the buffer, if the token is in the userData table of strings, we will set the + * according bit flag for it */ +//------------------------------------------------------------------------------------------------- +void INI::parseBitString8( INI* ini, void * /*instance*/, void *store, const void* userData ) +{ + UnsignedInt tmp; + INI::parseBitString32(ini, NULL, &tmp, userData); + if (tmp & 0xffffff00) + { + DEBUG_CRASH(("Bad bitstring list INI::parseBitString8")); + throw ERROR_BUG; + } + *(Byte*)store = (Byte)tmp; +} + +//------------------------------------------------------------------------------------------------- +/** 'store' points to an 32 bit unsigned integer. We will zero that integer, parse each token + * in the buffer, if the token is in the userData table of strings, we will set the + * according bit flag for it */ +//------------------------------------------------------------------------------------------------- +void INI::parseBitString32( INI* ini, void * /*instance*/, void *store, const void* userData ) +{ + ConstCharPtrArray flagList = (ConstCharPtrArray)userData; + UnsignedInt *bits = (UnsignedInt *)store; + + if( flagList == NULL || flagList[ 0 ] == NULL) + { + DEBUG_ASSERTCRASH( flagList, ("INTERNAL ERROR! parseBitString32: No flag list provided!\n") ); + throw INI_INVALID_NAME_LIST; + } + + Bool foundNormal = false; + Bool foundAddOrSub = false; + + // loop through all tokens + for (const char *token = ini->getNextTokenOrNull(); token != NULL; token = ini->getNextTokenOrNull()) + { + if (stricmp(token, "NONE") == 0) + { + if (foundNormal || foundAddOrSub) + { + DEBUG_CRASH(("you may not mix normal and +- ops in bitstring lists")); + throw INI_INVALID_NAME_LIST; + } + *bits = 0; + break; + } + + if (token[0] == '+') + { + if (foundNormal) + { + DEBUG_CRASH(("you may not mix normal and +- ops in bitstring lists")); + throw INI_INVALID_NAME_LIST; + } + Int bitIndex = INI::scanIndexList(token+1, flagList); // this throws if the token is not found + *bits |= (1 << bitIndex); + foundAddOrSub = true; + } + else if (token[0] == '-') + { + if (foundNormal) + { + DEBUG_CRASH(("you may not mix normal and +- ops in bitstring lists")); + throw INI_INVALID_NAME_LIST; + } + Int bitIndex = INI::scanIndexList(token+1, flagList); // this throws if the token is not found + *bits &= ~(1 << bitIndex); + foundAddOrSub = true; + } + else + { + if (foundAddOrSub) + { + DEBUG_CRASH(("you may not mix normal and +- ops in bitstring lists")); + throw INI_INVALID_NAME_LIST; + } + + if (!foundNormal) + *bits = 0; + + Int bitIndex = INI::scanIndexList(token, flagList); // this throws if the token is not found + *bits |= (1 << bitIndex); + foundNormal = true; + } + } +} + +//------------------------------------------------------------------------------------------------- +/** Parse a color in the form of + * + * RGB_COLOR = R:100 G:114 B:245 + * and store in "RGBColor" structure pointed to by 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parseRGBColor( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char* names[3] = { "R", "G", "B" }; + Int colors[3]; + for( Int i = 0; i < 3; i++ ) + { + colors[i] = scanInt(ini->getNextSubToken(names[i])); + if( colors[ i ] < 0 ) + throw INI_INVALID_DATA; + if( colors[ i ] > 255 ) + throw INI_INVALID_DATA; + } + + // assign the color components to the "RGBColor" pointer at 'store' + RGBColor *theColor = (RGBColor *)store; + theColor->red = (Real)colors[ 0 ] / 255.0f; + theColor->green = (Real)colors[ 1 ] / 255.0f; + theColor->blue = (Real)colors[ 2 ] / 255.0f; + +} + + +//------------------------------------------------------------------------------------------------- +/** Parse a color in the form of + * + * RGB_COLOR = R:0.5 G:0.3 B:0.6 + * and store in "RGBColor" structure pointed to by 'store' + * Negative numbers, and values greater 1 are allowed! */ + //------------------------------------------------------------------------------------------------- +void INI::parseRGBColorReal(INI* ini, void* /*instance*/, void* store, const void* /*userData*/) +{ + const char* names[3] = { "R", "G", "B" }; + Real colors[3]; + for (Int i = 0; i < 3; i++) + { + colors[i] = scanReal(ini->getNextSubToken(names[i])); + //if (colors[i] < -255) + // throw INI_INVALID_DATA; + //if (colors[i] > 255) + // throw INI_INVALID_DATA; + } + + // assign the color components to the "RGBColor" pointer at 'store' + RGBColor* theColor = (RGBColor*)store; + theColor->red = colors[0]; + theColor->green = colors[1]; + theColor->blue = colors[2]; + +} + + +//------------------------------------------------------------------------------------------------- +/** Parse a color in the form of + * + * RGB_COLOR = R:100 G:114 B:245 [A:233] + * and store in "RGBAColorInt" structure pointed to by 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parseRGBAColorInt( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char* names[4] = { "R", "G", "B", "A" }; + Int colors[4]; + for( Int i = 0; i < 4; i++ ) + { + const char* token = ini->getNextTokenOrNull(ini->getSepsColon()); + if (token == NULL) + { + if (i < 3) + { + throw INI_INVALID_DATA; + } + else + { + // it's ok for A to be omitted. + colors[i] = 255; + } + } + else + { + // if present, the token must match. + if (stricmp(token, names[i]) != 0) + { + throw INI_INVALID_DATA; + } + colors[i] = scanInt(ini->getNextToken(ini->getSepsColon())); + } + if( colors[ i ] < 0 ) + throw INI_INVALID_DATA; + if( colors[ i ] > 255 ) + throw INI_INVALID_DATA; + } + + // + // assign the color components to the "RGBColorInt" pointer at 'store', keep + // the numbers as between 0 and 255 + // + RGBAColorInt *theColor = (RGBAColorInt *)store; + theColor->red = colors[ 0 ]; + theColor->green = colors[ 1 ]; + theColor->blue = colors[ 2 ]; + theColor->alpha = colors[ 3 ]; + +} // end parseRGBAColorInt + +//------------------------------------------------------------------------------------------------- +/** Parse a color in the form of + * + * RGB_COLOR = R:100 G:114 B:245 [A:233] + * and store in "Color" structure pointed to by 'store' */ +//------------------------------------------------------------------------------------------------- +void INI::parseColorInt( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char* names[4] = { "R", "G", "B", "A" }; + Int colors[4]; + for( Int i = 0; i < 4; i++ ) + { + const char* token = ini->getNextTokenOrNull(ini->getSepsColon()); + if (token == NULL) + { + if (i < 3) + { + throw INI_INVALID_DATA; + } + else + { + // it's ok for A to be omitted. + colors[i] = 255; + } + } + else + { + // if present, the token must match. + if (stricmp(token, names[i]) != 0) + { + throw INI_INVALID_DATA; + } + colors[i] = scanInt(ini->getNextToken(ini->getSepsColon())); + } + if( colors[ i ] < 0 ) + throw INI_INVALID_DATA; + if( colors[ i ] > 255 ) + throw INI_INVALID_DATA; + } + + // + // assign the color components to the "Color" pointer at 'store', keep + // the numbers as between 0 and 255 + // + Color *theColor = (Color *)store; + *theColor = GameMakeColor(colors[0], colors[1], colors[2], colors[3]); + +} // end parseColorInt + +//------------------------------------------------------------------------------------------------- +/** Parse a 3D coordinate of reals in the form of: + * FIELD_NAME = X:400 Y:-214.3 Z:8.6 */ +//------------------------------------------------------------------------------------------------- +void INI::parseCoord3D( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + Coord3D *theCoord = (Coord3D *)store; + + theCoord->x = scanReal(ini->getNextSubToken("X")); + theCoord->y = scanReal(ini->getNextSubToken("Y")); + theCoord->z = scanReal(ini->getNextSubToken("Z")); + +} // end parseCoord3D + +//------------------------------------------------------------------------------------------------- +/** Parse a 2D coordinate of reals in the form of: + * FIELD_NAME = X:400 Y:-214.3 */ +//------------------------------------------------------------------------------------------------- +void INI::parseCoord2D( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + Coord2D *theCoord = (Coord2D *)store; + + theCoord->x = scanReal(ini->getNextSubToken("X")); + theCoord->y = scanReal(ini->getNextSubToken("Y")); + +} // end parseCoord2D + +//------------------------------------------------------------------------------------------------- +/** Parse a 2D coordinate of Ints in the form of: + * FIELD_NAME = X:400 Y:-214 */ +//------------------------------------------------------------------------------------------------- +void INI::parseICoord2D( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + ICoord2D *theCoord = (ICoord2D *)store; + + theCoord->x = scanInt(ini->getNextSubToken("X")); + theCoord->y = scanInt(ini->getNextSubToken("Y")); + +} // end parseICoord2D + +//------------------------------------------------------------------------------------------------- +/** Parse an audio event and assign to the 'AudioEventRTS*' at store */ +//------------------------------------------------------------------------------------------------- +void INI::parseDynamicAudioEventRTS( INI *ini, void * /*instance*/, void *store, const void* userData ) +{ + const char *token = ini->getNextToken(); + DynamicAudioEventRTS** theSound = (DynamicAudioEventRTS**)store; + + // translate the string into a sound + if (stricmp(token, "NoSound") == 0) + { + if (*theSound) + { + (*theSound)->deleteInstance(); + *theSound = NULL; + } + } + else + { + if (*theSound == NULL) + *theSound = newInstance(DynamicAudioEventRTS); + (*theSound)->m_event.setEventName(AsciiString(token)); + } + + if (*theSound) + TheAudio->getInfoForAudioEvent(&(*theSound)->m_event); +} + +//------------------------------------------------------------------------------------------------- +/** Parse an audio event and assign to the 'AudioEventRTS*' at store */ +//------------------------------------------------------------------------------------------------- +void INI::parseAudioEventRTS( INI *ini, void * /*instance*/, void *store, const void* userData ) +{ + const char *token = ini->getNextToken(); + + AudioEventRTS *theSound = (AudioEventRTS*)store; + + // translate the string into a sound + if (stricmp(token, "NoSound") != 0) { + theSound->setEventName(AsciiString(token)); + } + + TheAudio->getInfoForAudioEvent(theSound); +} + +//------------------------------------------------------------------------------------------------- +/** Parse an ThingTemplate and assign to the 'ThingTemplate *' at store */ +//------------------------------------------------------------------------------------------------- +void INI::parseThingTemplate( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + + if (!TheThingFactory) + { + DEBUG_CRASH(("TheThingFactory not inited yet")); + throw ERROR_BUG; + } + + typedef const ThingTemplate *ConstThingTemplatePtr; + ConstThingTemplatePtr* theThingTemplate = (ConstThingTemplatePtr*)store; + + if (stricmp(token, "None") == 0) + { + *theThingTemplate = NULL; + } + else + { + const ThingTemplate *tt = TheThingFactory->findTemplate(token); // could be null! + DEBUG_ASSERTCRASH(tt, ("ThingTemplate %s not found!\n",token)); + // assign it, even if null! + *theThingTemplate = tt; + } + +} + +//------------------------------------------------------------------------------------------------- +/** Parse an ArmorTemplate and assign to the 'ArmorTemplate *' at store */ +//------------------------------------------------------------------------------------------------- +void INI::parseArmorTemplate( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + + typedef const ArmorTemplate *ConstArmorTemplatePtr; + ConstArmorTemplatePtr* theArmorTemplate = (ConstArmorTemplatePtr*)store; + + if (stricmp(token, "None") == 0) + { + *theArmorTemplate = NULL; + } + else + { + const ArmorTemplate *tt = TheArmorStore->findArmorTemplate(token); // could be null! + DEBUG_ASSERTCRASH(tt, ("ArmorTemplate %s not found!\n",token)); + // assign it, even if null! + *theArmorTemplate = tt; + } + +} + +//------------------------------------------------------------------------------------------------- +/** Parse an WeaponTemplate and assign to the 'WeaponTemplate *' at store */ +//------------------------------------------------------------------------------------------------- +void INI::parseWeaponTemplate( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + + typedef const WeaponTemplate *ConstWeaponTemplatePtr; + ConstWeaponTemplatePtr* theWeaponTemplate = (ConstWeaponTemplatePtr*)store; + + const WeaponTemplate *tt = TheWeaponStore->findWeaponTemplate(token); // could be null! + DEBUG_ASSERTCRASH(tt || stricmp(token, "None") == 0, ("WeaponTemplate %s not found!\n",token)); + // assign it, even if null! + *theWeaponTemplate = tt; + +} + +//------------------------------------------------------------------------------------------------- +/** Parse an FXList and assign to the 'FXList *' at store */ +//------------------------------------------------------------------------------------------------- +void INI::parseFXList( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + + typedef const FXList *ConstFXListPtr; + ConstFXListPtr* theFXList = (ConstFXListPtr*)store; + + const FXList *fxl = TheFXListStore->findFXList(token); // could be null! + DEBUG_ASSERTCRASH(fxl != NULL || stricmp(token, "None") == 0, ("FXList %s not found!\n",token)); + // assign it, even if null! + *theFXList = fxl; + +} + +//------------------------------------------------------------------------------------------------- +/** Parse a particle system and assign to 'ParticleSystemTemplate *' at store */ +//------------------------------------------------------------------------------------------------- +void INI::parseParticleSystemTemplate( INI *ini, void * /*instance*/, void *store, const void *userData ) +{ + const char *token = ini->getNextToken(); + + const ParticleSystemTemplate *pSystemT = TheParticleSystemManager->findTemplate( AsciiString( token ) ); + DEBUG_ASSERTCRASH( pSystemT || stricmp( token, "None" ) == 0, ("ParticleSystem %s not found!\n",token) ); + + typedef const ParticleSystemTemplate* ConstParticleSystemTemplatePtr; + ConstParticleSystemTemplatePtr* theParticleSystemTemplate = (ConstParticleSystemTemplatePtr*)store; + + *theParticleSystemTemplate = pSystemT; + +} // end parseParticleSystemTemplate + +//------------------------------------------------------------------------------------------------- +/** Parse an DamageFX and assign to the 'DamageFX *' at store */ +//------------------------------------------------------------------------------------------------- +void INI::parseDamageFX( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + + typedef const DamageFX *ConstDamageFXPtr; + ConstDamageFXPtr* theDamageFX = (ConstDamageFXPtr*)store; + + if (stricmp(token, "None") == 0) + { + *theDamageFX = NULL; + } + else + { + const DamageFX *fxl = TheDamageFXStore->findDamageFX(token); // could be null! + DEBUG_ASSERTCRASH(fxl, ("DamageFX %s not found!\n",token)); + // assign it, even if null! + *theDamageFX = fxl; + } + +} + +//------------------------------------------------------------------------------------------------- +/** Parse an ObjectCreationList and assign to the 'ObjectCreationList *' at store */ +//------------------------------------------------------------------------------------------------- +void INI::parseObjectCreationList( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + + typedef const ObjectCreationList *ConstObjectCreationListPtr; + ConstObjectCreationListPtr* theObjectCreationList = (ConstObjectCreationListPtr*)store; + + const ObjectCreationList *ocl = TheObjectCreationListStore->findObjectCreationList(token); // could be null! + DEBUG_ASSERTCRASH(ocl || stricmp(token, "None") == 0, ("ObjectCreationList %s not found!\n",token)); + // assign it, even if null! + *theObjectCreationList = ocl; + +} + +//------------------------------------------------------------------------------------------------- +/** Parse a upgrade template string and store as template pointer */ +//------------------------------------------------------------------------------------------------- +void INI::parseUpgradeTemplate( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + + if (!TheUpgradeCenter) + { + DEBUG_CRASH(("TheUpgradeCenter not inited yet")); + throw ERROR_BUG; + } + + const UpgradeTemplate *uu = TheUpgradeCenter->findUpgrade( AsciiString( token ) ); + DEBUG_ASSERTCRASH( uu || stricmp( token, "None" ) == 0, ("Upgrade %s not found!\n",token) ); + + typedef const UpgradeTemplate* ConstUpgradeTemplatePtr; + ConstUpgradeTemplatePtr* theUpgradeTemplate = (ConstUpgradeTemplatePtr *)store; + *theUpgradeTemplate = uu; +} + +//------------------------------------------------------------------------------------------------- +/** Parse a special power template string and store as template pointer */ +//------------------------------------------------------------------------------------------------- +void INI::parseSpecialPowerTemplate( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + + if (!TheSpecialPowerStore) + { + DEBUG_CRASH(("TheSpecialPowerStore not inited yet")); + throw ERROR_BUG; + } + + const SpecialPowerTemplate *sPowerT = TheSpecialPowerStore->findSpecialPowerTemplate( AsciiString( token ) ); + if( !sPowerT && stricmp( token, "None" ) != 0 ) + { + DEBUG_CRASH( ("[LINE: %d in '%s'] Specialpower %s not found!\n", ini->getLineNum(), ini->getFilename().str(), token) ); + } + + typedef const SpecialPowerTemplate* ConstSpecialPowerTemplatePtr; + ConstSpecialPowerTemplatePtr* theSpecialPowerTemplate = (ConstSpecialPowerTemplatePtr *)store; + *theSpecialPowerTemplate = sPowerT; +} + +//------------------------------------------------------------------------------------------------- +/** Parse a science string and store as science type */ +//------------------------------------------------------------------------------------------------- +/* static */void INI::parseScience( INI *ini, void * /*instance*/, void *store, const void *userData ) +{ + const char *token = ini->getNextToken(); + + if (!TheScienceStore) + { + DEBUG_CRASH(("TheScienceStore not inited yet")); + throw ERROR_BUG; + } + + *((ScienceType *)store) = INI::scanScience(token); + +} + +//------------------------------------------------------------------------------------------------- +/** Parse a single string token, check for that token in the index list + * of names provided and store the index into that list. + * + * NOTE: Is is assumed that we are going to store the index into + * a 4 byte integer. This works well for INT and ENUM definitions */ +//------------------------------------------------------------------------------------------------- +void INI::parseIndexList( INI* ini, void * /*instance*/, void *store, const void* userData ) +{ + ConstCharPtrArray nameList = (ConstCharPtrArray)userData; + *(Int *)store = scanIndexList(ini->getNextToken(), nameList); +} + +//------------------------------------------------------------------------------------------------- +/** returns -1 if "None", otherwise like parseIndexList **/ +//------------------------------------------------------------------------------------------------- +void INI::parseIndexListOrNone(INI* ini, void* /*instance*/, void* store, const void* userData) +{ + const char* token = ini->getNextToken(); + if (stricmp(token, "None") == 0) { + *(Int*)store = -1; + } + else { + //like parseIndexList + ConstCharPtrArray nameList = (ConstCharPtrArray)userData; + *(Int*)store = scanIndexList(token, nameList); + } +} + +//------------------------------------------------------------------------------------------------- +/** Parse a single string token, check for that token in the index list + * of names provided and store the index into that list. + * + * NOTE: Is is assumed that we are going to store the index into + * a 4 byte integer. This works well for INT and ENUM definitions */ +//------------------------------------------------------------------------------------------------- +void INI::parseByteSizedIndexList( INI* ini, void * /*instance*/, void *store, const void* userData ) +{ + ConstCharPtrArray nameList = (ConstCharPtrArray)userData; + Int value = scanIndexList(ini->getNextToken(), nameList); + if (value < 0 || value > 255) + { + DEBUG_CRASH(("Bad index list INI::parseByteSizedIndexList")); + throw ERROR_BUG; + } + *(Byte *)store = (Byte)value; +} + +//------------------------------------------------------------------------------------------------- +/** Parse a single string token, check for that token in the index list + * of names provided and store the associated value into that list. + * + * NOTE: Is is assumed that we are going to store the index into + * a 4 byte integer. This works well for INT and ENUM definitions */ +//------------------------------------------------------------------------------------------------- +void INI::parseLookupList( INI* ini, void * /*instance*/, void *store, const void* userData ) +{ + ConstLookupListRecArray lookupList = (ConstLookupListRecArray)userData; + *(Int *)store = scanLookupList(ini->getNextToken(), lookupList); +} + +//------------------------------------------------------------------------------------------------- +/** Special Handling for None = -2 (Eva_NONE), otherwise like parseIndexList **/ +//------------------------------------------------------------------------------------------------- +void INI::parseEvaNameIndexList(INI* ini, void* /*instance*/, void* store, const void* userData) +{ + const char* token = ini->getNextToken(); + if (stricmp(token, "None") == 0) { + *(Int*)store = -2; + } + else { + //like parseIndexList + ConstCharPtrArray nameList = (ConstCharPtrArray)userData; + *(Int*)store = scanIndexList(token, nameList); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + + +//------------------------------------------------------------------------------------------------- +void MultiIniFieldParse::add(const FieldParse* f, UnsignedInt e) +{ + if (m_count < MAX_MULTI_FIELDS) + { + m_fieldParse[m_count] = f; + m_extraOffset[m_count] = e; + ++m_count; + } + else + { + DEBUG_CRASH(("too many multi-fields in INI::initFromINIMultiProc")); + throw ERROR_BUG; + } +} + +//------------------------------------------------------------------------------------------------- +void INI::initFromINI( void *what, const FieldParse* parseTable ) +{ + MultiIniFieldParse p; + p.add(parseTable); + initFromINIMulti(what, p); +} + +//------------------------------------------------------------------------------------------------- +void INI::initFromINIMultiProc( void *what, BuildMultiIniFieldProc proc ) +{ + MultiIniFieldParse p; + (*proc)(p); + initFromINIMulti(what, p); +} + +//------------------------------------------------------------------------------------------------- +void INI::initFromINIMulti( void *what, const MultiIniFieldParse& parseTableList ) +{ + Bool done = FALSE; + + if( what == NULL ) + { + DEBUG_ASSERTCRASH( 0, ("INI::initFromINI - Invalid parameters supplied!\n") ); + throw INI_INVALID_PARAMS; + } + + // read each of the data fields + while( !done ) + { + + // read next line + readLine(); + + // check for end token + const char* field = strtok( m_buffer, INI::getSeps() ); + if( field ) + { + + if( stricmp( field, m_blockEndToken ) == 0 ) + { + done = TRUE; + } + else + { + Bool found = false; + for (int ptIdx = 0; ptIdx < parseTableList.getCount(); ++ptIdx) + { + int offset = 0; + const void* userData = 0; + INIFieldParseProc parse = findFieldParse(parseTableList.getNthFieldParse(ptIdx), field, offset, userData); + if (parse) + { + // parse this block and check for parse errors + try { + + (*parse)( this, what, (char *)what + offset + parseTableList.getNthExtraOffset(ptIdx), userData ); + + } catch (...) { + DEBUG_CRASH( ("[LINE: %d - FILE: '%s'] Error reading field '%s' of block '%s'\n", + INI::getLineNum(), INI::getFilename().str(), field, m_curBlockStart) ); + + + char buff[1024]; + sprintf(buff, "[LINE: %d - FILE: '%s'] Error reading field '%s'\n", INI::getLineNum(), INI::getFilename().str(), field); + throw INIException(buff); + } + + found = true; + break; + + } + } + + if (!found) + { + DEBUG_ASSERTCRASH( 0, ("[LINE: %d - FILE: '%s'] Unknown field '%s' in block '%s'\n", + INI::getLineNum(), INI::getFilename().str(), field, m_curBlockStart) ); + throw INI_UNKNOWN_TOKEN; + } + + } // end else + + } // end if + + // sanity check for reaching end of file with no closing end token + if( done == FALSE && INI::isEOF() == TRUE ) + { + + done = TRUE; + DEBUG_ASSERTCRASH( 0, ("Error parsing block '%s', in INI file '%s'. Missing '%s' token\n", + m_curBlockStart, getFilename().str(), m_blockEndToken) ); + throw INI_MISSING_END_TOKEN; + + } // end if + + } // end while + +} + +//------------------------------------------------------------------------------------------------- +/*static*/ const char* INI::getNextToken(const char* seps) +{ + if (!seps) seps = getSeps(); + const char *token = ::strtok(NULL, seps); + if (!token) + throw INI_INVALID_DATA; + return token; +} + +//------------------------------------------------------------------------------------------------- +/*static*/ const char* INI::getNextTokenOrNull(const char* seps) +{ + if (!seps) seps = getSeps(); + const char *token = ::strtok(NULL, seps); + return token; +} + +//------------------------------------------------------------------------------------------------- +/*static*/ ScienceType INI::scanScience(const char* token) +{ + return TheScienceStore->friend_lookupScience( token ); +} + +//------------------------------------------------------------------------------------------------- +/*static*/ Int INI::scanInt(const char* token) +{ + Int value; + if (sscanf( token, "%d", &value ) != 1) + throw INI_INVALID_DATA; + return value; +} + +//------------------------------------------------------------------------------------------------- +/*static*/ UnsignedInt INI::scanUnsignedInt(const char* token) +{ + UnsignedInt value; + if (sscanf( token, "%u", &value ) != 1) // unsigned int is %u, not %d + throw INI_INVALID_DATA; + return value; +} + +//------------------------------------------------------------------------------------------------- +/*static*/ Real INI::scanReal(const char* token) +{ + Real value; + if (sscanf( token, "%f", &value ) != 1) + throw INI_INVALID_DATA; + return value; +} + +//------------------------------------------------------------------------------------------------- +/*static*/ Real INI::scanPercentToReal(const char* token) +{ + Real value; + if (sscanf( token, "%f", &value ) != 1) + throw INI_INVALID_DATA; + return value / 100.0f; +} + +//------------------------------------------------------------------------------------------------- +/*static*/ Int INI::scanIndexList(const char* token, ConstCharPtrArray nameList) +{ + if( nameList == NULL || nameList[ 0 ] == NULL ) + { + + DEBUG_ASSERTCRASH( 0, ("INTERNAL ERROR! scanIndexList, invalid name list\n") ); + throw INI_INVALID_NAME_LIST; + + } + + // search for matching name + Int count = 0; + for(ConstCharPtrArray name = nameList; *name; name++, count++ ) + { + if( stricmp( *name, token ) == 0 ) + { + return count; + } + } + + DEBUG_CRASH(("token %s is not a valid member of the index list\n",token)); + throw INI_INVALID_DATA; + return 0; // never executed, but keeps compiler happy + +} +//------------------------------------------------------------------------------------------------- +/*static*/ Int INI::scanLookupList(const char* token, ConstLookupListRecArray lookupList) +{ + if( lookupList == NULL || lookupList[ 0 ].name == NULL ) + { + DEBUG_ASSERTCRASH( 0, ("INTERNAL ERROR! scanLookupList, invalid name list\n") ); + throw INI_INVALID_NAME_LIST; + } + + // search for matching name + Bool found = false; + for( const LookupListRec* lookup = &lookupList[0]; lookup->name; lookup++ ) + { + if( stricmp( lookup->name, token ) == 0 ) + { + return lookup->value; + found = true; + break; + } + } + + DEBUG_CRASH(("token %s is not a valid member of the lookup list\n",token)); + throw INI_INVALID_DATA; + return 0; // never executed, but keeps compiler happy + +} + +//------------------------------------------------------------------------------------------------- +const char* INI::getNextSubToken(const char* expected) +{ + const char* token = getNextToken(getSepsColon()); + if (stricmp(token, expected) != 0) + throw INI_INVALID_DATA; + return getNextToken(getSepsColon()); +} + +//------------------------------------------------------------------------------------------------- +/** + * Parse a "random variable". + * The format is "FIELD = low high [distribution]". + */ +void INI::parseGameClientRandomVariable( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + GameClientRandomVariable *var = static_cast(store); + + const char* token; + + token = ini->getNextToken(); + Real low = INI::scanReal(token); + + token = ini->getNextToken(); + Real high = INI::scanReal(token); + + // if omitted, assume uniform + GameClientRandomVariable::DistributionType type = GameClientRandomVariable::UNIFORM; + token = ini->getNextTokenOrNull(); + if (token) + type = (GameClientRandomVariable::DistributionType)INI::scanIndexList(token, GameClientRandomVariable::DistributionTypeNames); + + // set the range of the random variable + var->setRange( low, high, type ); +} + +//------------------------------------------------------------------------------------------------- +// parse a duration in msec and convert to duration in frames +void INI::parseDurationReal( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + Real val = scanReal(ini->getNextToken()); + *(Real *)store = ConvertDurationFromMsecsToFrames(val); +} + +//------------------------------------------------------------------------------------------------- +// parse a duration in msec and convert to duration in integral number of frames, (unsignedint) rounding UP +void INI::parseDurationUnsignedInt( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + UnsignedInt val = scanUnsignedInt(ini->getNextToken()); + *(UnsignedInt *)store = (UnsignedInt)ceilf(ConvertDurationFromMsecsToFrames((Real)val)); +} + +// ------------------------------------------------------------------------------------------------ +// parse a duration in msec and convert to duration in integral number of frames, (unsignedshort) rounding UP +void INI::parseDurationUnsignedShort( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + UnsignedInt val = scanUnsignedInt(ini->getNextToken()); + *(UnsignedShort *)store = (UnsignedShort)ceilf(ConvertDurationFromMsecsToFrames((Real)val)); +} + +//------------------------------------------------------------------------------------------------- +// parse acceleration in (dist/sec) and convert to (dist/frame) +void INI::parseVelocityReal( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + Real val = scanReal(token); + *(Real *)store = ConvertVelocityInSecsToFrames(val); +} + +//------------------------------------------------------------------------------------------------- +// parse acceleration in (dist/sec^2) and convert to (dist/frame^2) +void INI::parseAccelerationReal( INI *ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + const char *token = ini->getNextToken(); + Real val = scanReal(token); + *(Real *)store = ConvertAccelerationInSecsToFrames(val); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void INI::parseVeterancyLevelFlags(INI* ini, void* /*instance*/, void* store, const void* /*userData*/) +{ + VeterancyLevelFlags flags = VETERANCY_LEVEL_FLAGS_ALL; + for (const char* token = ini->getNextToken(); token; token = ini->getNextTokenOrNull()) + { + if (stricmp(token, "ALL") == 0) + { + flags = VETERANCY_LEVEL_FLAGS_ALL; + continue; + } + else if (stricmp(token, "NONE") == 0) + { + flags = VETERANCY_LEVEL_FLAGS_NONE; + continue; + } + else if (token[0] == '+') + { + VeterancyLevel dt = (VeterancyLevel)INI::scanIndexList(token+1, TheVeterancyNames); + flags = setVeterancyLevelFlag(flags, dt); + continue; + } + else if (token[0] == '-') + { + VeterancyLevel dt = (VeterancyLevel)INI::scanIndexList(token+1, TheVeterancyNames); + flags = clearVeterancyLevelFlag(flags, dt); + continue; + } + else + { + throw INI_UNKNOWN_TOKEN; + } + } + *(VeterancyLevelFlags*)store = flags; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void INI::parseSoundsList( INI* ini, void *instance, void *store, const void* /*userData*/ ) +{ + std::vector *vec = (std::vector*) store; + vec->clear(); + + const char* SEPS = " \t,="; + const char *c = ini->getNextTokenOrNull(SEPS); + while ( c ) + { + vec->push_back( c ); + c = ini->getNextTokenOrNull(SEPS); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void INI::parseDamageTypeFlags(INI* ini, void* /*instance*/, void* store, const void* /*userData*/) +{ + DamageTypeFlags flags = DAMAGE_TYPE_FLAGS_NONE; + flags.flip(); + + for (const char* token = ini->getNextToken(); token; token = ini->getNextTokenOrNull()) + { + if (stricmp(token, "ALL") == 0) + { + flags = DAMAGE_TYPE_FLAGS_NONE; + flags.flip(); + continue; + } + if (stricmp(token, "NONE") == 0) + { + flags = DAMAGE_TYPE_FLAGS_NONE; + continue; + } + if (token[0] == '+') + { + DamageType dt = (DamageType)DamageTypeFlags::getSingleBitFromName(token+1); + flags = setDamageTypeFlag(flags, dt); + continue; + } + if (token[0] == '-') + { + DamageType dt = (DamageType)DamageTypeFlags::getSingleBitFromName(token+1); + flags = clearDamageTypeFlag(flags, dt); + continue; + } + throw INI_UNKNOWN_TOKEN; + } + *(DamageTypeFlags*)store = flags; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void INI::parseDeathTypeFlags(INI* ini, void* /*instance*/, void* store, const void* /*userData*/) +{ + DeathTypeFlags flags = DEATH_TYPE_FLAGS_ALL; + + for (const char* token = ini->getNextToken(); token; token = ini->getNextTokenOrNull()) + { + if (stricmp(token, "ALL") == 0) + { + flags = DEATH_TYPE_FLAGS_ALL; + + if (TheGlobalData) { + flags &= ~TheGlobalData->m_defaultExcludedDeathTypes; + DEBUG_LOG(("INI::parseDeathTypeFlags - flags = %X\n", flags)); + } + else { + DEBUG_LOG(("INI::parseDeathTypeFlags - TheGlobalData is NULL\n")); + } + + continue; + } + if (stricmp(token, "NONE") == 0) + { + flags = DEATH_TYPE_FLAGS_NONE; + continue; + } + if (token[0] == '+') + { + DeathType dt = (DeathType)INI::scanIndexList(token+1, TheDeathNames); + flags = setDeathTypeFlag(flags, dt); + continue; + } + if (token[0] == '-') + { + DeathType dt = (DeathType)INI::scanIndexList(token+1, TheDeathNames); + flags = clearDeathTypeFlag(flags, dt); + continue; + } + throw INI_UNKNOWN_TOKEN; + } + *(DeathTypeFlags*)store = flags; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +// Parse a simple list, no +/- syntax allowed +void INI::parseDeathTypeFlagsList(INI* ini, void* /*instance*/, void* store, const void* /*userData*/) +{ + DeathTypeFlags flags = DEATH_TYPE_FLAGS_NONE; + for (const char* token = ini->getNextToken(); token; token = ini->getNextTokenOrNull()) + { + if (stricmp(token, "ALL") == 0) + { + flags = DEATH_TYPE_FLAGS_ALL; + continue; + } + if (stricmp(token, "NONE") == 0) + { + flags = DEATH_TYPE_FLAGS_NONE; + continue; + } + + DeathType dt = (DeathType)INI::scanIndexList(token, TheDeathNames); + flags = setDeathTypeFlag(flags, dt); + } + *(DeathTypeFlags*)store = flags; +} + +//------------------------------------------------------------------------------------------------- +// parse the line and return whether the given line is a Block declaration of the form +// [whitespace] blockType [whitespace] blockName [EOL] +// both blockType and blockName are case insensitive +Bool INI::isDeclarationOfType( AsciiString blockType, AsciiString blockName, char *bufferToCheck ) +{ + Bool retVal = true; + if (!bufferToCheck || blockType.isEmpty() || blockName.isEmpty()) { + return false; + } + // DO NOT RETURN EARLY FROM THIS FUNCTION. (beyond this point) + // we have to restore the bufferToCheck to its previous state before returning, so + // it is important to get through all the checks. + + char restoreChar; + char *tempBuff = bufferToCheck; + int blockTypeLength = blockType.getLength(); + int blockNameLength = blockName.getLength(); + + while (isspace(*tempBuff)) { + ++tempBuff; + } + + if (strlen(tempBuff) > blockTypeLength) { + restoreChar = tempBuff[blockTypeLength]; + tempBuff[blockTypeLength] = 0; + + if (stricmp(blockType.str(), tempBuff) != 0) { + retVal = false; + } + + tempBuff[blockTypeLength] = restoreChar; + tempBuff = tempBuff + blockTypeLength; + } else { + retVal = false; + } + + while (isspace(*tempBuff)) { + ++tempBuff; + } + + if (strlen(tempBuff) > blockNameLength) { + restoreChar = tempBuff[blockNameLength]; + tempBuff[blockNameLength] = 0; + + if (stricmp(blockName.str(), tempBuff) != 0) { + retVal = false; + } + + tempBuff[blockNameLength] = restoreChar; + tempBuff = tempBuff + blockNameLength; + } else { + retVal = false; + } + + while (strlen(tempBuff)) { + retVal = retVal && isspace(tempBuff[0]); + ++tempBuff; + } + + return retVal; +} + +//------------------------------------------------------------------------------------------------- +// parse the line and return whether the given line is a Block declaration of the form +// [whitespace] end [EOL] +Bool INI::isEndOfBlock( char *bufferToCheck ) +{ + Bool retVal = true; + if (!bufferToCheck) { + return false; + } + + // DO NOT RETURN EARLY FROM THIS FUNCTION (beyond this point) + // we have to restore the bufferToCheck to its previous state before returning, so + // it is important to get through all the checks. + + static const char* endString = "End"; + int endStringLength = strlen(endString); + char restoreChar; + char *tempBuff = bufferToCheck; + + + while (isspace(*tempBuff)) { + ++tempBuff; + } + + if (strlen(tempBuff) > endStringLength) { + restoreChar = tempBuff[endStringLength]; + tempBuff[endStringLength] = 0; + + if (stricmp(endString, tempBuff) != 0) { + retVal = false; + } + + tempBuff[endStringLength] = restoreChar; + tempBuff = tempBuff + endStringLength; + } else { + retVal = false; + } + + while (strlen(tempBuff)) { + retVal = retVal && isspace(tempBuff[0]); + ++tempBuff; + } + + return retVal; +} diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/DisabledTypes.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/DisabledTypes.cpp index 5ff439cb583..32ae45f2ea8 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/DisabledTypes.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/DisabledTypes.cpp @@ -1,63 +1,63 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// DisabledTypes.cpp ///////////////////////////////////////////////////////////////////////////////////// -// Kris Morness, September 2002 - -#include "PreRTS.h" - -#include "Common/DisabledTypes.h" -#include "Common/BitFlagsIO.h" - -const char* DisabledMaskType::s_bitNameList[] = -{ - "DEFAULT", - "DISABLED_HACKED", - "DISABLED_EMP", - "DISABLED_HELD", - "DISABLED_PARALYZED", - "DISABLED_UNMANNED", - "DISABLED_UNDERPOWERED", - "DISABLED_FREEFALL", - - "DISABLED_AWESTRUCK", - "DISABLED_BRAINWASHED", - "DISABLED_SUBDUED", - - "DISABLED_SCRIPT_DISABLED", - "DISABLED_SCRIPT_UNDERPOWERED", - - "DISABLED_TELEPORT", - "DISABLED_CHRONO", - - NULL -}; - -DisabledMaskType DISABLEDMASK_NONE; // inits to all zeroes -DisabledMaskType DISABLEDMASK_ALL; - -void initDisabledMasks() -{ - SET_ALL_DISABLEDMASK_BITS( DISABLEDMASK_ALL ); -} +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// DisabledTypes.cpp ///////////////////////////////////////////////////////////////////////////////////// +// Kris Morness, September 2002 + +#include "PreRTS.h" + +#include "Common/DisabledTypes.h" +#include "Common/BitFlagsIO.h" + +const char* DisabledMaskType::s_bitNameList[] = +{ + "DEFAULT", + "DISABLED_HACKED", + "DISABLED_EMP", + "DISABLED_HELD", + "DISABLED_PARALYZED", + "DISABLED_UNMANNED", + "DISABLED_UNDERPOWERED", + "DISABLED_FREEFALL", + + "DISABLED_AWESTRUCK", + "DISABLED_BRAINWASHED", + "DISABLED_SUBDUED", + + "DISABLED_SCRIPT_DISABLED", + "DISABLED_SCRIPT_UNDERPOWERED", + + "DISABLED_TELEPORT", + "DISABLED_CHRONO", + + NULL +}; + +DisabledMaskType DISABLEDMASK_NONE; // inits to all zeroes +DisabledMaskType DISABLEDMASK_ALL; + +void initDisabledMasks() +{ + SET_ALL_DISABLEDMASK_BITS( DISABLEDMASK_ALL ); +} diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp index 12e3f14670f..62cad43ea66 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp @@ -1,818 +1,818 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: MemoryInit.cpp -//----------------------------------------------------------------------------- -// -// Westwood Studios Pacific. -// -// Confidential Information -// Copyright (C) 2001 - All Rights Reserved -// -//----------------------------------------------------------------------------- -// -// Project: RTS3 -// -// File name: MemoryInit.cpp -// -// Created: Steven Johnson, August 2001 -// -// Desc: Memory manager -// -// ---------------------------------------------------------------------------- -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -// SYSTEM INCLUDES - -// USER INCLUDES -#include "Lib/BaseType.h" -#include "Common/GameMemory.h" - -#ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -//----------------------------------------------------------------------------- -void userMemoryManagerGetDmaParms(Int *numSubPools, const PoolInitRec **pParms) -{ - static const PoolInitRec defaultDMA[7] = - { - // name, allocsize, initialcount, overflowcount - { "dmaPool_16", 16, 130000, 10000 }, - { "dmaPool_32", 32, 250000, 10000 }, - { "dmaPool_64", 64, 100000, 10000 }, - { "dmaPool_128", 128, 80000, 10000 }, - { "dmaPool_256", 256, 20000, 5000 }, - { "dmaPool_512", 512, 16000, 5000 }, - { "dmaPool_1024", 1024, 6000, 1024} - }; - - *numSubPools = 7; - *pParms = defaultDMA; -} - -//----------------------------------------------------------------------------- -struct PoolSizeRec -{ - const char* name; - Int initial; - Int overflow; -}; - -//----------------------------------------------------------------------------- -// And please be careful of duplicates. They are not rejected. -// not const -- we might override from INI -static PoolSizeRec sizes[] = -{ - { "PartitionContactListNode", 2048, 512 }, - { "BattleshipUpdate", 32, 32 }, - { "FlyToDestAndDestroyUpdate", 32, 32 }, - { "MusicTrack", 32, 32 }, - { "PositionalSoundPool", 32, 32 }, - { "GameMessage", 2048, 32 }, - { "NameKeyBucketPool", 9000, 1024 }, - { "ObjectSellInfo", 16, 16 }, - { "ProductionPrerequisitePool", 1024, 32 }, - { "RadarObject", 512, 32 }, - { "ResourceGatheringManager", 16, 16 }, - { "SightingInfo", 8192, 2048 },// Looks big, but all 3000 objects used to have 4 just built in. - { "SpecialPowerTemplate", 84, 32 }, - { "StateMachinePool", 32, 32 }, - { "TeamPool", 128, 32 }, // if you increase this, increase player/team relation map pools - { "PlayerRelationMapPool", 128, 32 }, - { "TeamRelationMapPool", 128, 32 }, - { "TeamPrototypePool", 256, 32 }, - { "TerrainType", 256, 32 }, - { "ThingTemplatePool", 2120, 32 }, - { "TunnelTracker", 16, 16 }, - { "Upgrade", 16, 16 }, - { "UpgradeTemplate", 128, 16 }, - { "Anim2D", 32, 32 }, - { "CommandButton", 1024, 256 }, - { "CommandSet", 820, 16 }, - { "DisplayString", 32, 32 }, - { "WebBrowserURL", 16, 16 }, - { "Drawable", 4096, 32 }, - { "Image", 2048, 32 }, - { "ParticlePool", 1400, 1024 }, - { "ParticleSystemTemplatePool", 1100, 32 }, - { "ParticleSystemPool", 1024, 32 }, - { "TerrainRoadType", 100, 32, }, - { "WindowLayoutPool", 32, 32 }, - { "AnimatedParticleSysBoneClientUpdate", 16, 16 }, - { "SwayClientUpdate", 32, 32 }, - { "BeaconClientUpdate", 64, 32 }, - { "AIGroupPool", 64, 32 }, - { "AIDockMachinePool", 256, 32 }, - { "AIGuardMachinePool", 32, 32 }, - { "AIGuardRetaliateMachinePool", 32, 32 }, - { "AITNGuardMachinePool", 32, 32 }, - { "PathNodePool", 8192, 1024 }, - { "PathPool", 256, 16 }, - { "WorkOrder", 32, 32 }, - { "TeamInQueue", 32, 32 }, - { "AIPlayer", 12, 4 }, - { "AISkirmishPlayer", 8, 8 }, - { "AIStateMachine", 600, 32 }, - { "JetAIStateMachine", 64, 32 }, - { "HeliAIStateMachine", 64, 32 }, - { "VtolAIStateMachine", 64, 32 }, - { "AIAttackMoveStateMachine", 2048, 32 }, - { "AIAttackThenIdleStateMachine", 512, 32 }, - { "AttackStateMachine", 512, 32 }, - { "CrateTemplate", 32, 32 }, - { "ExperienceTrackerPool", 2048, 512 }, - { "FiringTrackerPool", 4096, 256 }, - { "ObjectRepulsorHelper", 1024, 256 }, - { "ObjectSMCHelperPool", 2048, 256 }, - { "ObjectWeaponStatusHelperPool", 4096, 256 }, - { "ObjectDefectionHelperPool", 2048, 256 }, - { "StatusDamageHelper", 1500, 256 }, - { "SubdualDamageHelper", 1500, 256 }, - { "ChronoDamageHelper", 1500, 256 }, - { "TempWeaponBonusHelper", 4096, 256 }, - { "Locomotor", 2048, 32 }, - { "LocomotorTemplate", 192, 32 }, - { "ObjectPool", 1500, 256 }, - { "SimpleObjectIteratorPool", 32, 32 }, - { "SimpleObjectIteratorClumpPool", 4096, 32 }, - { "PartitionDataPool", 2048, 512 }, - { "BuildEntry", 32, 32 }, - { "Weapon", 4096, 32 }, - { "WeaponTemplate", 360, 32 }, - { "AIUpdateInterface", 600, 32 }, - { "ActiveBody", 1024, 32 }, - { "ActiveShroudUpgrade", 32, 32 }, - { "AssistedTargetingUpdate", 32, 32 }, - { "AudioEventInfo", 4096, 64 }, - { "AudioRequest", 256, 8 }, - { "AutoHealBehavior", 1024, 256 }, - { "WeaponBonusUpdate", 16, 16 }, - { "GrantStealthBehavior", 4096, 32 }, - { "NeutronBlastBehavior", 4096, 32 }, - { "CountermeasuresBehavior", 256, 32 }, - { "BaseRegenerateUpdate", 128, 32 }, - { "BoneFXDamage", 64, 32 }, - { "BoneFXUpdate", 64, 32 }, - { "BridgeBehavior", 4, 4 }, - { "BridgeTowerBehavior", 32, 32 }, - { "BridgeScaffoldBehavior", 32, 32 }, - { "CaveContain", 16, 16 }, - { "HealContain", 32, 32 }, - { "CreateCrateDie", 256, 128 }, - { "CreateObjectDie", 1024, 32 }, - { "EjectPilotDie", 1024, 32 }, - { "CrushDie", 1024, 32 }, - { "DamDie", 8, 8 }, - { "DeliverPayloadStateMachine", 32, 32 }, - { "DeliverPayloadAIUpdate", 32, 32 }, - { "DeletionUpdate", 128, 32 }, - { "SmartBombTargetHomingUpdate", 8, 8 }, - { "DynamicAudioEventInfo", 16, 256 }, // Note: some levels have none, some have lots. Since all are allocated at level load time, we can set this low for the levels with none. - { "HackInternetStateMachine", 32, 32 }, - { "HackInternetAIUpdate", 32, 32 }, - { "MissileAIUpdate", 512, 32 }, - { "DumbProjectileBehavior", 64, 32 }, - { "FreeFallProjectileBehavior", 32, 32 }, - { "DestroyDie", 1024, 32 }, - { "UpgradeDie", 128, 32 }, - { "KeepObjectDie", 128, 32 }, - { "DozerAIUpdate", 32, 32 }, - { "DynamicGeometryInfoUpdate", 16, 16 }, - { "DynamicShroudClearingRangeUpdate", 128, 16 }, - { "FXListDie", 1024, 32 }, - { "FireSpreadUpdate", 2048, 128 }, - { "FirestormDynamicGeometryInfoUpdate", 16, 16 }, - { "FireWeaponCollide", 2048, 32 }, - { "FireWeaponUpdate", 32, 32 }, - { "FlammableUpdate", 512, 256 }, - { "FloatUpdate", 512, 128 }, - { "TensileFormationUpdate", 256, 32 }, - { "GarrisonContain", 256, 32 }, - { "HealCrateCollide", 32, 32 }, - { "HeightDieUpdate", 32, 32 }, - { "ScatterShotUpdate", 128, 64 }, - { "FireWeaponWhenDamagedBehavior", 32, 32 }, - { "FireWeaponWhenDeadBehavior", 128, 64 }, - { "DelayedUpgradeBehavior", 128, 64 }, - { "GenerateMinefieldBehavior", 32, 32 }, - { "HelicopterSlowDeathBehavior", 64, 32 }, - { "ParkingPlaceBehavior", 32, 32 }, - { "FlightDeckBehavior", 8, 8 }, -#ifdef ALLOW_SURRENDER - { "POWTruckAIUpdate", 32, 32, }, - { "POWTruckBehavior", 32, 32, }, - { "PrisonBehavior", 32, 32 }, - { "PrisonVisual", 32, 32 }, - { "PropagandaCenterBehavior", 16, 16 }, -#endif - { "PropagandaTowerBehavior", 16, 16 }, - { "BunkerBusterBehavior", 16, 16 }, - { "ObjectTracker", 128, 32 }, - { "OCLUpdate", 16, 16 }, - { "BodyParticleSystem", 196, 64 }, - { "HighlanderBody", 2048, 128 }, - { "UndeadBody", 32, 32 }, - { "HordeUpdate", 128, 32 }, - { "ImmortalBody", 128, 256 }, - { "InactiveBody", 2048, 32 }, - { "InstantDeathBehavior", 512, 32 }, - { "ChronoDeathBehavior", 512, 32 }, - { "LaserUpdate", 32, 32 }, - { "PointDefenseLaserUpdate", 32, 32 }, - { "CleanupHazardUpdate", 32, 32 }, - { "AutoFindHealingUpdate", 256, 32 }, - { "CommandButtonHuntUpdate", 512, 8 }, - { "PilotFindVehicleUpdate", 256, 32 }, - { "DemoTrapUpdate", 32, 32 }, - { "ParticleUplinkCannonUpdate", 16, 16 }, - { "SpectreGunshipUpdate", 8, 8 }, - { "SpectreGunshipDeploymentUpdate", 8, 8 }, - { "BaikonurLaunchPower", 4, 4 }, - { "RadiusDecalUpdate", 16, 16 }, - { "RadiusDecalBehavior", 32, 32 }, - { "BattlePlanUpdate", 32, 32 }, - { "LifetimeUpdate", 32, 32 }, - { "LocomotorSetUpgrade", 512, 128 }, - { "LockWeaponCreate", 64, 128 }, - { "AutoDepositUpdate", 256, 32 }, - { "NeutronMissileUpdate", 512, 32 }, - { "MoneyCrateCollide", 48, 16 }, - { "NeutronMissileSlowDeathBehavior", 8, 8 }, - { "OpenContain", 128, 32 }, - { "OverchargeBehavior", 32, 32 }, - { "OverlordContain", 32, 32 }, - { "HelixContain", 32, 32 }, - { "ParachuteContain", 128, 32 }, - { "PhysicsBehavior", 600, 32 }, - { "PoisonedBehavior", 512, 64 }, - { "ProductionEntry", 32, 32 }, - { "ProductionUpdate", 256, 32 }, - { "ProjectileStreamUpdate", 32, 32 }, - { "ProneUpdate", 128, 32 }, - { "QueueProductionExitUpdate", 32, 32 }, - { "RadarUpdate", 16, 16 }, - { "RadarUpgrade", 16, 16 }, - { "AnimationSteeringUpdate", 1024, 32 }, - { "SupplyWarehouseCripplingBehavior", 16, 16 }, - { "CostModifierUpgrade", 32, 32 }, - { "ProductionTimeModifierUpgrade", 32, 32 }, - { "UnitProductionBonusUpgrade", 64, 32 }, - { "CashBountyPower", 32, 32 }, - { "CleanupAreaPower", 32, 32 }, - { "ObjectCreationUpgrade", 196, 32 }, - { "MinefieldBehavior", 256, 32 }, - { "JetSlowDeathBehavior", 64, 32 }, - { "BattleBusSlowDeathBehavior", 64, 32 }, - { "RebuildHoleBehavior", 64, 32 }, - { "RebuildHoleExposeDie", 64, 32 }, - { "RepairDockUpdate", 32, 32 }, -#ifdef ALLOW_SURRENDER - { "PrisonDockUpdate", 32, 32 }, -#endif - { "RailedTransportDockUpdate", 16, 16 }, - { "RailedTransportAIUpdate", 16, 16 }, - { "RailedTransportContain", 16, 16 }, - { "RailroadBehavior", 16, 16 }, - { "SalvageCrateCollide", 32, 32 }, - { "ShroudCrateCollide", 32, 32 }, - { "SlavedUpdate", 64, 32 }, - { "SlowDeathBehavior", 1400, 256 }, - { "SpyVisionUpdate", 16, 16 }, - { "DefaultProductionExitUpdate", 32, 32 }, - { "SpawnPointProductionExitUpdate", 32, 32 }, - { "SpawnBehavior", 32, 32 }, - { "SpecialPowerCompletionDie", 32, 32 }, - { "SpecialPowerCreate", 32, 32 }, - { "PreorderCreate", 32, 32 }, - { "SpecialAbility", 512, 32 }, - { "SpecialAbilityUpdate", 512, 32 }, - { "MissileLauncherBuildingUpdate", 32, 32 }, - { "SquishCollide", 512, 32 }, - { "StructureBody", 512, 64 }, - { "HiveStructureBody", 64, 32 }, //Stinger sites - { "StructureCollapseUpdate", 32, 32 }, - { "StructureToppleUpdate", 32, 32 }, - { "SupplyCenterCreate", 32, 32 }, - { "SupplyCenterDockUpdate", 32, 32 }, - { "SupplyCenterProductionExitUpdate", 32, 32 }, - { "SupplyTruckStateMachine", 256, 32 }, - { "SupplyTruckAIUpdate", 32, 32 }, - { "SupplyWarehouseCreate", 48, 16 }, - { "SupplyWarehouseDockUpdate", 48, 16 }, - { "EnemyNearUpdate", 1024, 32 }, - { "TechBuildingBehavior", 32, 32 }, - { "ToppleUpdate", 256, 128 }, - { "TransitionDamageFX", 384, 128 }, - { "TransportAIUpdate", 64, 32 }, - { "TransportContain", 128, 32 }, - { "RiderChangeContain", 128, 32 }, - { "InternetHackContain", 16, 16 }, - { "TunnelContain", 8, 8 }, - { "TunnelContainDie", 32, 32 }, - { "TunnelCreate", 32, 32 }, - { "TurretAI", 256, 32 }, - { "TurretStateMachine", 128, 32 }, - { "TurretSwapUpgrade", 512, 128 }, - { "UnitCrateCollide", 32, 32 }, - { "UnpauseSpecialPowerUpgrade", 32, 32 }, - { "VeterancyCrateCollide", 32, 32 }, - { "VeterancyGainCreate", 512, 128 }, - { "ConvertToCarBombCrateCollide", 256, 128 }, - { "ConvertToHijackedVehicleCrateCollide", 256, 128 }, - { "SabotageCommandCenterCrateCollide", 256, 128 }, - { "SabotageFakeBuildingCrateCollide", 256, 128 }, - { "SabotageInternetCenterCrateCollide", 256, 128 }, - { "SabotageMilitaryFactoryCrateCollide", 256, 128 }, - { "SabotagePowerPlantCrateCollide", 256, 128 }, - { "SabotageSuperweaponCrateCollide", 256, 128 }, - { "SabotageSupplyCenterCrateCollide", 256, 128 }, - { "SabotageSupplyDropzoneCrateCollide", 256, 128 }, - { "JetAIUpdate", 64, 32 }, - { "ChinookAIUpdate", 32, 32 }, - { "WanderAIUpdate", 32, 32 }, - { "TeleporterAIUpdate", 64, 32 }, - { "WaveGuideUpdate", 16, 16 }, - { "ArmorDamageScalarUpdate", 256, 32 }, - { "WeaponBonusUpgrade", 512, 128 }, - { "WeaponSetUpgrade", 512, 128 }, - { "ArmorUpgrade", 512, 128 }, - { "WorkerAIUpdate", 128, 128 }, - { "WorkerStateMachine", 128, 128 }, - { "ChinookAIStateMachine", 32, 32 }, - { "DeployStyleAIUpdate", 32, 32 }, - { "AssaultTransportAIUpdate", 64, 32 }, - { "StreamingArchiveFile", 8, 8 }, - - { "DozerActionStateMachine", 256, 32 }, - { "DozerPrimaryStateMachine", 256, 32 }, - { "W3DDisplayString", 1400, 128 }, - { "W3DDefaultDraw", 1024, 128 }, - { "W3DDebrisDraw", 128, 128 }, - { "W3DDependencyModelDraw", 64, 64 }, - { "W3DLaserDraw", 32, 32 }, - { "W3DModelDraw", 2048, 512 }, - { "W3DOverlordTankDraw", 64, 64 }, - { "W3DOverlordTruckDraw", 64, 64 }, - { "W3DOverlordAircraftDraw", 64, 64 }, - { "W3DPoliceCarDraw", 32, 32 }, - { "W3DProjectileStreamDraw", 32, 32 }, - { "W3DRopeDraw", 32, 32 }, - { "W3DScienceModelDraw", 32, 32 }, - { "W3DSupplyDraw", 40, 16 }, - { "W3DTankDraw", 256, 32 }, - { "W3DTreeDraw", 16, 16 }, - { "W3DPropDraw", 16, 16 }, - { "W3DTracerDraw", 64, 32 }, - { "W3DTruckDraw", 128, 32 }, - { "W3DTankTruckDraw", 32, 16 }, - { "W3DTreeTextureClass", 4, 4 }, - { "DefaultSpecialPower", 32, 32 }, - { "OCLSpecialPower", 96, 32 }, - { "FireWeaponPower", 32, 32 }, -#ifdef ALLOW_DEMORALIZE - { "DemoralizeSpecialPower", 16, 16, }, -#endif - { "CashHackSpecialPower", 32, 32 }, - { "CommandSetUpgrade", 32, 32 }, - { "PassengersFireUpgrade", 32, 32 }, - { "GrantUpgradeCreate", 256, 32 }, - { "GrantScienceUpgrade", 256, 32 }, - { "ReplaceObjectUpgrade", 32, 32 }, - { "ModelConditionUpgrade", 32, 32 }, - { "UpgradeSpecialPower", 64, 32 }, - { "SpyVisionSpecialPower", 256, 32 }, - { "StealthDetectorUpdate", 256, 32 }, - { "StealthUpdate", 512, 128 }, - { "StealthUpgrade", 256, 32 }, - { "StatusBitsUpgrade", 128, 128 }, - { "SubObjectsUpgrade", 128, 128 }, - { "ExperienceScalarUpgrade", 256, 128 }, - { "MaxHealthUpgrade", 128, 128 }, - { "WeaponBonusUpgrade", 128, 64 }, - { "StickyBombUpdate", 64, 32 }, - { "FireOCLAfterWeaponCooldownUpdate", 64, 32 }, - { "HijackerUpdate", 64, 32 }, - { "ChinaMinesUpgrade", 64, 32 }, - { "PowerPlantUpdate", 48, 16 }, - { "PowerPlantUpgrade", 48, 16 }, - { "DefectorSpecialPower", 16, 16 }, - { "CheckpointUpdate", 16, 16 }, - { "MobNexusContain", 128, 32 }, - { "MobMemberSlavedUpdate", 64, 32 }, - { "EMPUpdate", 64, 32 }, - { "LeafletDropBehavior", 64, 32 }, - { "Overridable", 32, 32 }, - - { "W3DGameWindow", 700, 256 }, - { "SuccessState", 32, 32 }, - { "FailureState", 32, 32 }, - { "ContinueState", 32, 32 }, - { "SleepState", 32, 32 }, - - { "AIDockWaitForClearanceState", 256, 32 }, - { "AIDockProcessDockState", 256, 32 }, - { "AIGuardInnerState", 32, 32 }, - { "AIGuardIdleState", 32, 32 }, - { "AIGuardOuterState", 32, 32 }, - { "AIGuardReturnState", 32, 32 }, - { "AIGuardPickUpCrateState", 32, 32 }, - { "AIGuardAttackAggressorState", 32, 32 }, - { "AIGuardRetaliateInnerState", 32, 32 }, - { "AIGuardRetaliateIdleState", 32, 32 }, - { "AIGuardRetaliateOuterState", 32, 32 }, - { "AIGuardRetaliateReturnState", 32, 32 }, - { "AIGuardRetaliatePickUpCrateState", 32, 32 }, - { "AIGuardRetaliateAttackAggressorState", 32, 32 }, - { "AITNGuardInnerState", 32, 32 }, - { "AITNGuardIdleState", 32, 32 }, - { "AITNGuardOuterState", 32, 32 }, - { "AITNGuardReturnState", 32, 32 }, - { "AITNGuardPickUpCrateState", 32, 32 }, - { "AITNGuardAttackAggressorState", 32, 32 }, - { "AIIdleState", 2400, 32 }, - { "AIRappelState", 600, 32 }, - { "AIBusyState", 600, 32 }, - { "AIWaitState", 600, 32 }, - { "AIAttackState", 4096, 32 }, - { "AIAttackSquadState", 600, 32 }, - { "AIDeadState", 600, 32 }, - { "AIDockState", 600, 32 }, - { "AIExitState", 600, 32 }, - { "AIExitInstantlyState", 600, 32 }, - { "AIGuardState", 600, 32 }, - { "AIGuardRetaliateState", 600, 32 }, - { "AITunnelNetworkGuardState", 600, 32 }, - { "AIHuntState", 600, 32 }, - { "AIAttackAreaState", 600, 32 }, - { "AIFaceState", 1200, 32 }, - { "ApproachState", 600, 32 }, - { "DeliveringState", 600, 32 }, - { "ConsiderNewApproachState", 600, 32 }, - { "RecoverFromOffMapState", 600, 32 }, - { "HeadOffMapState", 600, 32 }, - { "CleanUpState", 600, 32 }, - { "HackInternetState", 600, 32 }, - { "PackingState", 600, 32 }, - { "UnpackingState", 600, 32 }, - { "SupplyTruckWantsToPickUpOrDeliverBoxesState", 600, 32 }, - { "RegroupingState", 600, 32 }, - { "DockingState", 600, 32 }, - { "ChinookEvacuateState", 32, 32 }, - { "ChinookHeadOffMapState", 32, 32 }, - { "ChinookTakeoffOrLandingState", 32, 32 }, - { "ChinookCombatDropState", 32, 32 }, - { "DozerActionPickActionPosState", 256, 32 }, - { "DozerActionMoveToActionPosState", 256, 32 }, - { "DozerActionDoActionState", 256, 32 }, - { "DozerPrimaryIdleState", 256, 32 }, - { "DozerActionState", 256, 32 }, - { "DozerPrimaryGoingHomeState", 256, 32 }, - { "JetAwaitingRunwayState", 64, 32 }, - { "JetOrHeliCirclingDeadAirfieldState", 64, 32 }, - { "HeliTakeoffOrLandingState", 64, 32 }, - { "VtolTakeoffOrLandingState", 64, 32 }, - { "JetOrHeliParkOrientState", 64, 32 }, - { "VtolParkOrientState", 64, 32 }, - { "JetOrHeliReloadAmmoState", 64, 32 }, - { "SupplyTruckBusyState", 600, 32 }, - { "SupplyTruckIdleState", 600, 32 }, - { "ActAsDozerState", 600, 32 }, - { "ActAsSupplyTruckState", 600, 32 }, - { "AIDockApproachState", 256, 32 }, - { "AIDockAdvancePositionState", 256, 32 }, - { "AIDockMoveToEntryState", 256, 32 }, - { "AIDockMoveToDockState", 256, 32 }, - { "AIDockMoveToExitState", 256, 32 }, - { "AIDockMoveToRallyState", 256, 32 }, - { "AIMoveToState", 600, 32 }, - { "AIMoveOutOfTheWayState", 600, 32 }, - { "AIMoveAndTightenState", 600, 32 }, - { "AIMoveAwayFromRepulsorsState", 600, 32 }, - { "AIAttackApproachTargetState", 96, 32 }, - { "AIAttackPursueTargetState", 96, 32 }, - { "AIAttackAimAtTargetState", 96, 32 }, - { "AIAttackFireWeaponState", 256, 32 }, - { "AIPickUpCrateState", 4096, 32 }, - { "AIFollowWaypointPathState", 1200, 32 }, - { "AIFollowWaypointPathExactState", 1200, 32 }, - { "AIWanderInPlaceState", 600, 32 }, - { "AIFollowPathState", 1200, 32 }, - { "AIMoveAndEvacuateState", 1200, 32 }, - { "AIMoveAndDeleteState", 600, 32 }, - { "AIEnterState", 600, 32 }, - { "JetOrHeliReturningToDeadAirfieldState", 64, 32 }, - { "JetOrHeliReturnForLandingState", 64, 32 }, - { "TurretAIIdleState", 600, 32 }, - { "TurretAIIdleScanState", 600, 32 }, - { "TurretAIAimTurretState", 600, 32 }, - { "TurretAIRecenterTurretState", 600, 32 }, - { "TurretAIHoldTurretState", 600, 32 }, - { "JetOrHeliTaxiState", 64, 32 }, - { "JetTakeoffOrLandingState", 64, 32 }, - { "JetPauseBeforeTakeoffState", 64, 32 }, - { "AIAttackMoveToState", 600, 32 }, - { "AIAttackFollowWaypointPathState", 1200, 32 }, - { "AIWanderState", 600, 32 }, - { "AIPanicState", 600, 32 }, - { "ChinookMoveToBldgState", 32, 32 }, - { "ChinookRecordCreationState", 32, 32 }, - { "ScienceInfo", 96, 32 }, - { "RankInfo", 32, 32 }, - - { "FireWeaponNugget", 32, 32 }, - { "AttackNugget", 32, 32 }, - { "DeliverPayloadNugget", 48, 32 }, - { "ApplyRandomForceNugget", 32, 32 }, - { "GenericObjectCreationNugget", 632, 32 }, - { "SoundFXNugget", 320, 32 }, - { "TracerFXNugget", 32, 32 }, - { "RayEffectFXNugget", 32, 32 }, - { "LightPulseFXNugget", 68, 32 }, - { "ViewShakeFXNugget", 140, 32 }, - { "TerrainScorchFXNugget", 48, 32 }, - { "ParticleSystemFXNugget", 832, 32 }, - { "FXListAtBonePosFXNugget", 32, 32 }, - { "Squad", 256, 32 }, - { "BuildListInfo", 400, 64 }, - - { "ScriptGroup", 128, 32 }, - { "OrCondition", 1024, 256 }, - { "ScriptAction", 2600, 512 }, - { "Script", 1024, 256 }, - { "Parameter", 8192, 1024 }, - { "Condition", 2048, 256 }, - { "Template", 32, 32 }, - { "ScriptList", 32, 32 }, - { "AttackPriorityInfo", 32, 32 }, - { "SequentialScript", 32, 32 }, - { "Win32LocalFile", 1024, 256 }, - { "StdLocalFile", 1024, 256 }, - { "RAMFile", 32, 32 }, - { "BattlePlanBonuses", 32, 32 }, - { "KindOfPercentProductionChange", 32, 32 }, - { "UserParser", 4096, 256 }, - { "XferBlockData", 32, 32 }, - { "EvaCheckInfo", 52, 16 }, - { "SuperweaponInfo", 32, 32 }, - { "NamedTimerInfo", 32, 32 }, - { "PopupMessageData", 32, 32 }, - { "FloatingTextData", 32, 32 }, - { "MapObject", 5000, 1024 }, - { "Waypoint", 1024, 32 }, - { "PolygonTrigger", 64, 64 }, - { "Bridge", 32, 32 }, - { "Mapping", 384, 64 }, - { "OutputChunk", 32, 32 }, - { "InputChunk", 32, 32 }, - { "AnimateWindow", 32, 32 }, - { "GameFont", 32, 32 }, - { "NetCommandRef", 256, 32 }, - { "GameMessageArgument", 1024, 256 }, - { "GameMessageParserArgumentType", 32, 32 }, - { "GameMessageParser", 32, 32 }, - { "WeaponBonusSet", 96, 32 }, - { "Campaign", 32, 32 }, - { "Mission", 88, 32 }, - { "ModalWindow", 32, 32 }, - { "NetPacket", 32, 32 }, - { "AISideInfo", 32, 32 }, - { "AISideBuildList", 32, 32 }, - { "MetaMapRec", 256, 32 }, - { "TransportStatus", 32, 32 }, - { "Anim2DTemplate", 32, 32 }, - { "ObjectTypes", 32, 32 }, - { "NetCommandList", 512, 32 }, - { "TurretAIData", 256, 32 }, - { "NetCommandMsg", 32, 32 }, - { "NetGameCommandMsg", 64, 32 }, - { "NetAckBothCommandMsg", 32, 32 }, - { "NetAckStage1CommandMsg", 32, 32 }, - { "NetAckStage2CommandMsg", 32, 32 }, - { "NetFrameCommandMsg", 32, 32 }, - { "NetPlayerLeaveCommandMsg", 32, 32 }, - { "NetRunAheadMetricsCommandMsg", 32, 32 }, - { "NetRunAheadCommandMsg", 32, 32 }, - { "NetDestroyPlayerCommandMsg", 32, 32 }, - { "NetDisconnectFrameCommandMsg", 32, 32 }, - { "NetDisconnectScreenOffCommandMsg", 32, 32 }, - { "NetFrameResendRequestCommandMsg", 32, 32 }, - { "NetKeepAliveCommandMsg", 32, 32 }, - { "NetDisconnectKeepAliveCommandMsg", 32, 32 }, - { "NetDisconnectPlayerCommandMsg", 32, 32 }, - { "NetPacketRouterQueryCommandMsg", 32, 32 }, - { "NetPacketRouterAckCommandMsg", 32, 32 }, - { "NetDisconnectChatCommandMsg", 32, 32 }, - { "NetChatCommandMsg", 32, 32 }, - { "NetDisconnectVoteCommandMsg", 32, 32 }, - { "NetProgressCommandMsg", 32, 32 }, - { "NetWrapperCommandMsg", 32, 32 }, - { "NetFileCommandMsg", 32, 32 }, - { "NetFileAnnounceCommandMsg", 32, 32 }, - { "NetFileProgressCommandMsg", 32, 32 }, - { "NetCommandWrapperListNode", 32, 32 }, - { "NetCommandWrapperList", 32, 32 }, - { "Connection", 32, 32 }, - { "User", 32, 32 }, - { "FrameDataManager", 32, 32 }, - { "DrawableIconInfo", 32, 32 }, - { "TintEnvelope", 128, 32 }, - { "DynamicAudioEventRTS", 4000, 256 }, - { "DrawableLocoInfo", 128, 32 }, - { "W3DPrototypeClass", 512, 256 }, - { "EnumeratedIP", 32, 32 }, - { "WaterTransparencySetting", 4, 4 }, - { "WeatherSetting", 4, 4 }, - - // W3D pools! - { "BoxPrototypeClass", 128, 128 }, - { "SpherePrototypeClass", 32, 32 }, - { "SoundRenderObjPrototypeClass", 32, 32 }, - { "RingPrototypeClass", 32, 32 }, - { "PrimitivePrototypeClass", 8192, 32 }, - { "HModelPrototypeClass", 256, 32 }, - { "ParticleEmitterPrototypeClass", 32, 32 }, - { "NullPrototypeClass", 32, 32 }, - { "HLodPrototypeClass", 700, 128 }, - { "HLodDefClass", 700, 128 }, - { "DistLODPrototypeClass", 32, 32 }, - { "DazzlePrototypeClass", 32, 32 }, - { "CollectionPrototypeClass", 32, 32 }, - { "BoxPrototypeClass", 256, 32 }, - { "AggregatePrototypeClass", 32, 32 }, - { "OBBoxRenderObjClass", 512, 128 }, - { "AABoxRenderObjClass", 32, 32 }, - { "VertexMaterialClass", 6000, 2048 }, - { "TextureClass", 1200, 256 }, - { "CloudMapTerrainTextureClass", 4, 4 }, - { "ScorchTextureClass", 4, 4 }, - { "LightMapTerrainTextureClass", 4, 4 }, - { "AlphaEdgeTextureClass", 4, 4 }, - { "AlphaTerrainTextureClass", 4, 4 }, - { "TerrainTextureClass", 4, 4 }, - { "MeshClass", 14000, 2000 }, - { "HTreeClass", 2048, 512 }, - { "HLodClass", 2048, 512 }, - { "MeshModelClass", 8192, 32 }, - { "ShareBufferClass", 32768, 1024 }, - { "AABTreeClass", 300, 128 }, - { "MotionChannelClass", 16384, 32 }, - { "BitChannelClass", 84, 32 }, - { "TimeCodedMotionChannelClass", 116, 32 }, - { "AdaptiveDeltaMotionChannelClass", 32, 32 }, - { "TimeCodedBitChannelClass", 32, 32 }, - { "UVBufferClass", 8192, 32 }, - { "TexBufferClass", 384, 128 }, - { "MatBufferClass", 256, 128 }, - { "MatrixMapperClass", 32, 32 }, - { "ScaleTextureMapperClass", 32, 32 }, - { "LinearOffsetTextureMapperClass", 96, 32 }, - { "GridTextureMapperClass", 32, 32 }, - { "RotateTextureMapperClass", 32, 32 }, - { "SineLinearOffsetTextureMapperClass", 32, 32 }, - { "StepLinearOffsetTextureMapperClass", 32, 32 }, - { "ZigZagLinearOffsetTextureMapperClass", 32, 32 }, - { "ClassicEnvironmentMapperClass", 32, 32 }, - { "EnvironmentMapperClass", 256, 32 }, - { "EdgeMapperClass", 32, 32 }, - { "WSClassicEnvironmentMapperClass", 32, 32 }, - { "WSEnvironmentMapperClass", 32, 32 }, - { "GridClassicEnvironmentMapperClass", 32, 32 }, - { "GridEnvironmentMapperClass", 32, 32 }, - { "ScreenMapperClass", 32, 32 }, - { "RandomTextureMapperClass", 32, 32 }, - { "BumpEnvTextureMapperClass", 32, 32 }, - { "MeshLoadContextClass", 4, 4 }, - { "MaterialInfoClass", 8192, 32 }, - { "MeshMatDescClass", 8192, 32 }, - { "TextureLoadTaskClass", 256, 32 }, - { "SortingNodeStruct", 288, 32 }, - { "ProxyArrayClass", 32, 32 }, - { "Line3DClass", 8, 8 }, - { "Render2DClass", 64, 32 }, - { "SurfaceClass", 128, 32 }, - { "FontCharsClassCharDataStruct", 1024, 32 }, - { "FontCharsBuffer", 16, 4 }, - { "FVFInfoClass", 152, 64 }, - { "TerrainTracksRenderObjClass", 128, 32 }, - { "DynamicIBAccessClass", 32, 32 }, - { "DX8IndexBufferClass", 128, 32 }, - { "SortingIndexBufferClass", 32, 32 }, - { "DX8VertexBufferClass", 128, 32 }, - { "SortingVertexBufferClass", 32, 32 }, - { "DynD3DMATERIAL8", 8192, 32 }, - { "DynamicMatrix3D", 512, 32 }, - { "MeshGeometryClass", 32, 32 }, - { "DynamicMeshModel", 32, 32 }, - { "GapFillerClass", 32, 32 }, - { "FontCharsClass", 64, 32 }, - { "ThumbnailManagerClass", 32, 32}, - { "SmudgeSet", 32, 32}, - { "Smudge", 128, 32}, - { 0, 0, 0 } -}; - -//----------------------------------------------------------------------------- -void userMemoryAdjustPoolSize(const char *poolName, Int& initialAllocationCount, Int& overflowAllocationCount) -{ - if (initialAllocationCount > 0) - return; - - for (const PoolSizeRec* p = sizes; p->name != NULL; ++p) - { - if (strcmp(p->name, poolName) == 0) - { - initialAllocationCount = p->initial; - overflowAllocationCount = p->overflow; - return; - } - } - - DEBUG_CRASH(("Initial size for pool %s not found -- you should add it to MemoryInit.cpp\n",poolName)); -} - -//----------------------------------------------------------------------------- -static Int roundUpMemBound(Int i) -{ - const int MEM_BOUND_ALIGNMENT = 4; - - if (i < MEM_BOUND_ALIGNMENT) - return MEM_BOUND_ALIGNMENT; - else - return (i + (MEM_BOUND_ALIGNMENT-1)) & ~(MEM_BOUND_ALIGNMENT-1); -} - -//----------------------------------------------------------------------------- -void userMemoryManagerInitPools() -{ - // note that we MUST use stdio stuff here, and not the normal game file system - // (with bigfile support, etc), because that relies on memory pools, which - // aren't yet initialized properly! so rely ONLY on straight stdio stuff here. - // (not even AsciiString. thanks.) - - // since we're called prior to main, the cur dir might not be what - // we expect. so do it the hard way. - char buf[_MAX_PATH]; - ::GetModuleFileName(NULL, buf, sizeof(buf)); - char* pEnd = buf + strlen(buf); - while (pEnd != buf) - { - if (*pEnd == '\\') - { - *pEnd = 0; - break; - } - --pEnd; - } - strcat(buf, "\\Data\\INI\\MemoryPools.ini"); - - FILE* fp = fopen(buf, "r"); - if (fp) - { - char poolName[256]; - int initial, overflow; - while (fgets(buf, _MAX_PATH, fp)) - { - if (buf[0] == ';') - continue; - if (sscanf(buf, "%s %d %d", poolName, &initial, &overflow ) == 3) - { - for (PoolSizeRec* p = sizes; p->name != NULL; ++p) - { - if (stricmp(p->name, poolName) == 0) - { - // currently, these must be multiples of 4. so round up. - p->initial = roundUpMemBound(initial); - p->overflow = roundUpMemBound(overflow); - break; // from for-p - } - } - } - } - fclose(fp); - } -} - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: MemoryInit.cpp +//----------------------------------------------------------------------------- +// +// Westwood Studios Pacific. +// +// Confidential Information +// Copyright (C) 2001 - All Rights Reserved +// +//----------------------------------------------------------------------------- +// +// Project: RTS3 +// +// File name: MemoryInit.cpp +// +// Created: Steven Johnson, August 2001 +// +// Desc: Memory manager +// +// ---------------------------------------------------------------------------- +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +// SYSTEM INCLUDES + +// USER INCLUDES +#include "Lib/BaseType.h" +#include "Common/GameMemory.h" + +#ifdef RTS_INTERNAL +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +//----------------------------------------------------------------------------- +void userMemoryManagerGetDmaParms(Int *numSubPools, const PoolInitRec **pParms) +{ + static const PoolInitRec defaultDMA[7] = + { + // name, allocsize, initialcount, overflowcount + { "dmaPool_16", 16, 130000, 10000 }, + { "dmaPool_32", 32, 250000, 10000 }, + { "dmaPool_64", 64, 100000, 10000 }, + { "dmaPool_128", 128, 80000, 10000 }, + { "dmaPool_256", 256, 20000, 5000 }, + { "dmaPool_512", 512, 16000, 5000 }, + { "dmaPool_1024", 1024, 6000, 1024} + }; + + *numSubPools = 7; + *pParms = defaultDMA; +} + +//----------------------------------------------------------------------------- +struct PoolSizeRec +{ + const char* name; + Int initial; + Int overflow; +}; + +//----------------------------------------------------------------------------- +// And please be careful of duplicates. They are not rejected. +// not const -- we might override from INI +static PoolSizeRec sizes[] = +{ + { "PartitionContactListNode", 2048, 512 }, + { "BattleshipUpdate", 32, 32 }, + { "FlyToDestAndDestroyUpdate", 32, 32 }, + { "MusicTrack", 32, 32 }, + { "PositionalSoundPool", 32, 32 }, + { "GameMessage", 2048, 32 }, + { "NameKeyBucketPool", 9000, 1024 }, + { "ObjectSellInfo", 16, 16 }, + { "ProductionPrerequisitePool", 1024, 32 }, + { "RadarObject", 512, 32 }, + { "ResourceGatheringManager", 16, 16 }, + { "SightingInfo", 8192, 2048 },// Looks big, but all 3000 objects used to have 4 just built in. + { "SpecialPowerTemplate", 84, 32 }, + { "StateMachinePool", 32, 32 }, + { "TeamPool", 128, 32 }, // if you increase this, increase player/team relation map pools + { "PlayerRelationMapPool", 128, 32 }, + { "TeamRelationMapPool", 128, 32 }, + { "TeamPrototypePool", 256, 32 }, + { "TerrainType", 256, 32 }, + { "ThingTemplatePool", 2120, 32 }, + { "TunnelTracker", 16, 16 }, + { "Upgrade", 16, 16 }, + { "UpgradeTemplate", 128, 16 }, + { "Anim2D", 32, 32 }, + { "CommandButton", 1024, 256 }, + { "CommandSet", 820, 16 }, + { "DisplayString", 32, 32 }, + { "WebBrowserURL", 16, 16 }, + { "Drawable", 4096, 32 }, + { "Image", 2048, 32 }, + { "ParticlePool", 1400, 1024 }, + { "ParticleSystemTemplatePool", 1100, 32 }, + { "ParticleSystemPool", 1024, 32 }, + { "TerrainRoadType", 100, 32, }, + { "WindowLayoutPool", 32, 32 }, + { "AnimatedParticleSysBoneClientUpdate", 16, 16 }, + { "SwayClientUpdate", 32, 32 }, + { "BeaconClientUpdate", 64, 32 }, + { "AIGroupPool", 64, 32 }, + { "AIDockMachinePool", 256, 32 }, + { "AIGuardMachinePool", 32, 32 }, + { "AIGuardRetaliateMachinePool", 32, 32 }, + { "AITNGuardMachinePool", 32, 32 }, + { "PathNodePool", 8192, 1024 }, + { "PathPool", 256, 16 }, + { "WorkOrder", 32, 32 }, + { "TeamInQueue", 32, 32 }, + { "AIPlayer", 12, 4 }, + { "AISkirmishPlayer", 8, 8 }, + { "AIStateMachine", 600, 32 }, + { "JetAIStateMachine", 64, 32 }, + { "HeliAIStateMachine", 64, 32 }, + { "VtolAIStateMachine", 64, 32 }, + { "AIAttackMoveStateMachine", 2048, 32 }, + { "AIAttackThenIdleStateMachine", 512, 32 }, + { "AttackStateMachine", 512, 32 }, + { "CrateTemplate", 32, 32 }, + { "ExperienceTrackerPool", 2048, 512 }, + { "FiringTrackerPool", 4096, 256 }, + { "ObjectRepulsorHelper", 1024, 256 }, + { "ObjectSMCHelperPool", 2048, 256 }, + { "ObjectWeaponStatusHelperPool", 4096, 256 }, + { "ObjectDefectionHelperPool", 2048, 256 }, + { "StatusDamageHelper", 1500, 256 }, + { "SubdualDamageHelper", 1500, 256 }, + { "ChronoDamageHelper", 1500, 256 }, + { "TempWeaponBonusHelper", 4096, 256 }, + { "Locomotor", 2048, 32 }, + { "LocomotorTemplate", 192, 32 }, + { "ObjectPool", 1500, 256 }, + { "SimpleObjectIteratorPool", 32, 32 }, + { "SimpleObjectIteratorClumpPool", 4096, 32 }, + { "PartitionDataPool", 2048, 512 }, + { "BuildEntry", 32, 32 }, + { "Weapon", 4096, 32 }, + { "WeaponTemplate", 360, 32 }, + { "AIUpdateInterface", 600, 32 }, + { "ActiveBody", 1024, 32 }, + { "ActiveShroudUpgrade", 32, 32 }, + { "AssistedTargetingUpdate", 32, 32 }, + { "AudioEventInfo", 4096, 64 }, + { "AudioRequest", 256, 8 }, + { "AutoHealBehavior", 1024, 256 }, + { "WeaponBonusUpdate", 16, 16 }, + { "GrantStealthBehavior", 4096, 32 }, + { "NeutronBlastBehavior", 4096, 32 }, + { "CountermeasuresBehavior", 256, 32 }, + { "BaseRegenerateUpdate", 128, 32 }, + { "BoneFXDamage", 64, 32 }, + { "BoneFXUpdate", 64, 32 }, + { "BridgeBehavior", 4, 4 }, + { "BridgeTowerBehavior", 32, 32 }, + { "BridgeScaffoldBehavior", 32, 32 }, + { "CaveContain", 16, 16 }, + { "HealContain", 32, 32 }, + { "CreateCrateDie", 256, 128 }, + { "CreateObjectDie", 1024, 32 }, + { "EjectPilotDie", 1024, 32 }, + { "CrushDie", 1024, 32 }, + { "DamDie", 8, 8 }, + { "DeliverPayloadStateMachine", 32, 32 }, + { "DeliverPayloadAIUpdate", 32, 32 }, + { "DeletionUpdate", 128, 32 }, + { "SmartBombTargetHomingUpdate", 8, 8 }, + { "DynamicAudioEventInfo", 16, 256 }, // Note: some levels have none, some have lots. Since all are allocated at level load time, we can set this low for the levels with none. + { "HackInternetStateMachine", 32, 32 }, + { "HackInternetAIUpdate", 32, 32 }, + { "MissileAIUpdate", 512, 32 }, + { "DumbProjectileBehavior", 64, 32 }, + { "FreeFallProjectileBehavior", 32, 32 }, + { "DestroyDie", 1024, 32 }, + { "UpgradeDie", 128, 32 }, + { "KeepObjectDie", 128, 32 }, + { "DozerAIUpdate", 32, 32 }, + { "DynamicGeometryInfoUpdate", 16, 16 }, + { "DynamicShroudClearingRangeUpdate", 128, 16 }, + { "FXListDie", 1024, 32 }, + { "FireSpreadUpdate", 2048, 128 }, + { "FirestormDynamicGeometryInfoUpdate", 16, 16 }, + { "FireWeaponCollide", 2048, 32 }, + { "FireWeaponUpdate", 32, 32 }, + { "FlammableUpdate", 512, 256 }, + { "FloatUpdate", 512, 128 }, + { "TensileFormationUpdate", 256, 32 }, + { "GarrisonContain", 256, 32 }, + { "HealCrateCollide", 32, 32 }, + { "HeightDieUpdate", 32, 32 }, + { "ScatterShotUpdate", 128, 64 }, + { "FireWeaponWhenDamagedBehavior", 32, 32 }, + { "FireWeaponWhenDeadBehavior", 128, 64 }, + { "DelayedUpgradeBehavior", 128, 64 }, + { "GenerateMinefieldBehavior", 32, 32 }, + { "HelicopterSlowDeathBehavior", 64, 32 }, + { "ParkingPlaceBehavior", 32, 32 }, + { "FlightDeckBehavior", 8, 8 }, +#ifdef ALLOW_SURRENDER + { "POWTruckAIUpdate", 32, 32, }, + { "POWTruckBehavior", 32, 32, }, + { "PrisonBehavior", 32, 32 }, + { "PrisonVisual", 32, 32 }, + { "PropagandaCenterBehavior", 16, 16 }, +#endif + { "PropagandaTowerBehavior", 16, 16 }, + { "BunkerBusterBehavior", 16, 16 }, + { "ObjectTracker", 128, 32 }, + { "OCLUpdate", 16, 16 }, + { "BodyParticleSystem", 196, 64 }, + { "HighlanderBody", 2048, 128 }, + { "UndeadBody", 32, 32 }, + { "HordeUpdate", 128, 32 }, + { "ImmortalBody", 128, 256 }, + { "InactiveBody", 2048, 32 }, + { "InstantDeathBehavior", 512, 32 }, + { "ChronoDeathBehavior", 512, 32 }, + { "LaserUpdate", 32, 32 }, + { "PointDefenseLaserUpdate", 32, 32 }, + { "CleanupHazardUpdate", 32, 32 }, + { "AutoFindHealingUpdate", 256, 32 }, + { "CommandButtonHuntUpdate", 512, 8 }, + { "PilotFindVehicleUpdate", 256, 32 }, + { "DemoTrapUpdate", 32, 32 }, + { "ParticleUplinkCannonUpdate", 16, 16 }, + { "SpectreGunshipUpdate", 8, 8 }, + { "SpectreGunshipDeploymentUpdate", 8, 8 }, + { "BaikonurLaunchPower", 4, 4 }, + { "RadiusDecalUpdate", 16, 16 }, + { "RadiusDecalBehavior", 32, 32 }, + { "BattlePlanUpdate", 32, 32 }, + { "LifetimeUpdate", 32, 32 }, + { "LocomotorSetUpgrade", 512, 128 }, + { "LockWeaponCreate", 64, 128 }, + { "AutoDepositUpdate", 256, 32 }, + { "NeutronMissileUpdate", 512, 32 }, + { "MoneyCrateCollide", 48, 16 }, + { "NeutronMissileSlowDeathBehavior", 8, 8 }, + { "OpenContain", 128, 32 }, + { "OverchargeBehavior", 32, 32 }, + { "OverlordContain", 32, 32 }, + { "HelixContain", 32, 32 }, + { "ParachuteContain", 128, 32 }, + { "PhysicsBehavior", 600, 32 }, + { "PoisonedBehavior", 512, 64 }, + { "ProductionEntry", 32, 32 }, + { "ProductionUpdate", 256, 32 }, + { "ProjectileStreamUpdate", 32, 32 }, + { "ProneUpdate", 128, 32 }, + { "QueueProductionExitUpdate", 32, 32 }, + { "RadarUpdate", 16, 16 }, + { "RadarUpgrade", 16, 16 }, + { "AnimationSteeringUpdate", 1024, 32 }, + { "SupplyWarehouseCripplingBehavior", 16, 16 }, + { "CostModifierUpgrade", 32, 32 }, + { "ProductionTimeModifierUpgrade", 32, 32 }, + { "UnitProductionBonusUpgrade", 64, 32 }, + { "CashBountyPower", 32, 32 }, + { "CleanupAreaPower", 32, 32 }, + { "ObjectCreationUpgrade", 196, 32 }, + { "MinefieldBehavior", 256, 32 }, + { "JetSlowDeathBehavior", 64, 32 }, + { "BattleBusSlowDeathBehavior", 64, 32 }, + { "RebuildHoleBehavior", 64, 32 }, + { "RebuildHoleExposeDie", 64, 32 }, + { "RepairDockUpdate", 32, 32 }, +#ifdef ALLOW_SURRENDER + { "PrisonDockUpdate", 32, 32 }, +#endif + { "RailedTransportDockUpdate", 16, 16 }, + { "RailedTransportAIUpdate", 16, 16 }, + { "RailedTransportContain", 16, 16 }, + { "RailroadBehavior", 16, 16 }, + { "SalvageCrateCollide", 32, 32 }, + { "ShroudCrateCollide", 32, 32 }, + { "SlavedUpdate", 64, 32 }, + { "SlowDeathBehavior", 1400, 256 }, + { "SpyVisionUpdate", 16, 16 }, + { "DefaultProductionExitUpdate", 32, 32 }, + { "SpawnPointProductionExitUpdate", 32, 32 }, + { "SpawnBehavior", 32, 32 }, + { "SpecialPowerCompletionDie", 32, 32 }, + { "SpecialPowerCreate", 32, 32 }, + { "PreorderCreate", 32, 32 }, + { "SpecialAbility", 512, 32 }, + { "SpecialAbilityUpdate", 512, 32 }, + { "MissileLauncherBuildingUpdate", 32, 32 }, + { "SquishCollide", 512, 32 }, + { "StructureBody", 512, 64 }, + { "HiveStructureBody", 64, 32 }, //Stinger sites + { "StructureCollapseUpdate", 32, 32 }, + { "StructureToppleUpdate", 32, 32 }, + { "SupplyCenterCreate", 32, 32 }, + { "SupplyCenterDockUpdate", 32, 32 }, + { "SupplyCenterProductionExitUpdate", 32, 32 }, + { "SupplyTruckStateMachine", 256, 32 }, + { "SupplyTruckAIUpdate", 32, 32 }, + { "SupplyWarehouseCreate", 48, 16 }, + { "SupplyWarehouseDockUpdate", 48, 16 }, + { "EnemyNearUpdate", 1024, 32 }, + { "TechBuildingBehavior", 32, 32 }, + { "ToppleUpdate", 256, 128 }, + { "TransitionDamageFX", 384, 128 }, + { "TransportAIUpdate", 64, 32 }, + { "TransportContain", 128, 32 }, + { "RiderChangeContain", 128, 32 }, + { "InternetHackContain", 16, 16 }, + { "TunnelContain", 8, 8 }, + { "TunnelContainDie", 32, 32 }, + { "TunnelCreate", 32, 32 }, + { "TurretAI", 256, 32 }, + { "TurretStateMachine", 128, 32 }, + { "TurretSwapUpgrade", 512, 128 }, + { "UnitCrateCollide", 32, 32 }, + { "UnpauseSpecialPowerUpgrade", 32, 32 }, + { "VeterancyCrateCollide", 32, 32 }, + { "VeterancyGainCreate", 512, 128 }, + { "ConvertToCarBombCrateCollide", 256, 128 }, + { "ConvertToHijackedVehicleCrateCollide", 256, 128 }, + { "SabotageCommandCenterCrateCollide", 256, 128 }, + { "SabotageFakeBuildingCrateCollide", 256, 128 }, + { "SabotageInternetCenterCrateCollide", 256, 128 }, + { "SabotageMilitaryFactoryCrateCollide", 256, 128 }, + { "SabotagePowerPlantCrateCollide", 256, 128 }, + { "SabotageSuperweaponCrateCollide", 256, 128 }, + { "SabotageSupplyCenterCrateCollide", 256, 128 }, + { "SabotageSupplyDropzoneCrateCollide", 256, 128 }, + { "JetAIUpdate", 64, 32 }, + { "ChinookAIUpdate", 32, 32 }, + { "WanderAIUpdate", 32, 32 }, + { "TeleporterAIUpdate", 64, 32 }, + { "WaveGuideUpdate", 16, 16 }, + { "ArmorDamageScalarUpdate", 256, 32 }, + { "WeaponBonusUpgrade", 512, 128 }, + { "WeaponSetUpgrade", 512, 128 }, + { "ArmorUpgrade", 512, 128 }, + { "WorkerAIUpdate", 128, 128 }, + { "WorkerStateMachine", 128, 128 }, + { "ChinookAIStateMachine", 32, 32 }, + { "DeployStyleAIUpdate", 32, 32 }, + { "AssaultTransportAIUpdate", 64, 32 }, + { "StreamingArchiveFile", 8, 8 }, + + { "DozerActionStateMachine", 256, 32 }, + { "DozerPrimaryStateMachine", 256, 32 }, + { "W3DDisplayString", 1400, 128 }, + { "W3DDefaultDraw", 1024, 128 }, + { "W3DDebrisDraw", 128, 128 }, + { "W3DDependencyModelDraw", 64, 64 }, + { "W3DLaserDraw", 32, 32 }, + { "W3DModelDraw", 2048, 512 }, + { "W3DOverlordTankDraw", 64, 64 }, + { "W3DOverlordTruckDraw", 64, 64 }, + { "W3DOverlordAircraftDraw", 64, 64 }, + { "W3DPoliceCarDraw", 32, 32 }, + { "W3DProjectileStreamDraw", 32, 32 }, + { "W3DRopeDraw", 32, 32 }, + { "W3DScienceModelDraw", 32, 32 }, + { "W3DSupplyDraw", 40, 16 }, + { "W3DTankDraw", 256, 32 }, + { "W3DTreeDraw", 16, 16 }, + { "W3DPropDraw", 16, 16 }, + { "W3DTracerDraw", 64, 32 }, + { "W3DTruckDraw", 128, 32 }, + { "W3DTankTruckDraw", 32, 16 }, + { "W3DTreeTextureClass", 4, 4 }, + { "DefaultSpecialPower", 32, 32 }, + { "OCLSpecialPower", 96, 32 }, + { "FireWeaponPower", 32, 32 }, +#ifdef ALLOW_DEMORALIZE + { "DemoralizeSpecialPower", 16, 16, }, +#endif + { "CashHackSpecialPower", 32, 32 }, + { "CommandSetUpgrade", 32, 32 }, + { "PassengersFireUpgrade", 32, 32 }, + { "GrantUpgradeCreate", 256, 32 }, + { "GrantScienceUpgrade", 256, 32 }, + { "ReplaceObjectUpgrade", 32, 32 }, + { "ModelConditionUpgrade", 32, 32 }, + { "UpgradeSpecialPower", 64, 32 }, + { "SpyVisionSpecialPower", 256, 32 }, + { "StealthDetectorUpdate", 256, 32 }, + { "StealthUpdate", 512, 128 }, + { "StealthUpgrade", 256, 32 }, + { "StatusBitsUpgrade", 128, 128 }, + { "SubObjectsUpgrade", 128, 128 }, + { "ExperienceScalarUpgrade", 256, 128 }, + { "MaxHealthUpgrade", 128, 128 }, + { "WeaponBonusUpgrade", 128, 64 }, + { "StickyBombUpdate", 64, 32 }, + { "FireOCLAfterWeaponCooldownUpdate", 64, 32 }, + { "HijackerUpdate", 64, 32 }, + { "ChinaMinesUpgrade", 64, 32 }, + { "PowerPlantUpdate", 48, 16 }, + { "PowerPlantUpgrade", 48, 16 }, + { "DefectorSpecialPower", 16, 16 }, + { "CheckpointUpdate", 16, 16 }, + { "MobNexusContain", 128, 32 }, + { "MobMemberSlavedUpdate", 64, 32 }, + { "EMPUpdate", 64, 32 }, + { "LeafletDropBehavior", 64, 32 }, + { "Overridable", 32, 32 }, + + { "W3DGameWindow", 700, 256 }, + { "SuccessState", 32, 32 }, + { "FailureState", 32, 32 }, + { "ContinueState", 32, 32 }, + { "SleepState", 32, 32 }, + + { "AIDockWaitForClearanceState", 256, 32 }, + { "AIDockProcessDockState", 256, 32 }, + { "AIGuardInnerState", 32, 32 }, + { "AIGuardIdleState", 32, 32 }, + { "AIGuardOuterState", 32, 32 }, + { "AIGuardReturnState", 32, 32 }, + { "AIGuardPickUpCrateState", 32, 32 }, + { "AIGuardAttackAggressorState", 32, 32 }, + { "AIGuardRetaliateInnerState", 32, 32 }, + { "AIGuardRetaliateIdleState", 32, 32 }, + { "AIGuardRetaliateOuterState", 32, 32 }, + { "AIGuardRetaliateReturnState", 32, 32 }, + { "AIGuardRetaliatePickUpCrateState", 32, 32 }, + { "AIGuardRetaliateAttackAggressorState", 32, 32 }, + { "AITNGuardInnerState", 32, 32 }, + { "AITNGuardIdleState", 32, 32 }, + { "AITNGuardOuterState", 32, 32 }, + { "AITNGuardReturnState", 32, 32 }, + { "AITNGuardPickUpCrateState", 32, 32 }, + { "AITNGuardAttackAggressorState", 32, 32 }, + { "AIIdleState", 2400, 32 }, + { "AIRappelState", 600, 32 }, + { "AIBusyState", 600, 32 }, + { "AIWaitState", 600, 32 }, + { "AIAttackState", 4096, 32 }, + { "AIAttackSquadState", 600, 32 }, + { "AIDeadState", 600, 32 }, + { "AIDockState", 600, 32 }, + { "AIExitState", 600, 32 }, + { "AIExitInstantlyState", 600, 32 }, + { "AIGuardState", 600, 32 }, + { "AIGuardRetaliateState", 600, 32 }, + { "AITunnelNetworkGuardState", 600, 32 }, + { "AIHuntState", 600, 32 }, + { "AIAttackAreaState", 600, 32 }, + { "AIFaceState", 1200, 32 }, + { "ApproachState", 600, 32 }, + { "DeliveringState", 600, 32 }, + { "ConsiderNewApproachState", 600, 32 }, + { "RecoverFromOffMapState", 600, 32 }, + { "HeadOffMapState", 600, 32 }, + { "CleanUpState", 600, 32 }, + { "HackInternetState", 600, 32 }, + { "PackingState", 600, 32 }, + { "UnpackingState", 600, 32 }, + { "SupplyTruckWantsToPickUpOrDeliverBoxesState", 600, 32 }, + { "RegroupingState", 600, 32 }, + { "DockingState", 600, 32 }, + { "ChinookEvacuateState", 32, 32 }, + { "ChinookHeadOffMapState", 32, 32 }, + { "ChinookTakeoffOrLandingState", 32, 32 }, + { "ChinookCombatDropState", 32, 32 }, + { "DozerActionPickActionPosState", 256, 32 }, + { "DozerActionMoveToActionPosState", 256, 32 }, + { "DozerActionDoActionState", 256, 32 }, + { "DozerPrimaryIdleState", 256, 32 }, + { "DozerActionState", 256, 32 }, + { "DozerPrimaryGoingHomeState", 256, 32 }, + { "JetAwaitingRunwayState", 64, 32 }, + { "JetOrHeliCirclingDeadAirfieldState", 64, 32 }, + { "HeliTakeoffOrLandingState", 64, 32 }, + { "VtolTakeoffOrLandingState", 64, 32 }, + { "JetOrHeliParkOrientState", 64, 32 }, + { "VtolParkOrientState", 64, 32 }, + { "JetOrHeliReloadAmmoState", 64, 32 }, + { "SupplyTruckBusyState", 600, 32 }, + { "SupplyTruckIdleState", 600, 32 }, + { "ActAsDozerState", 600, 32 }, + { "ActAsSupplyTruckState", 600, 32 }, + { "AIDockApproachState", 256, 32 }, + { "AIDockAdvancePositionState", 256, 32 }, + { "AIDockMoveToEntryState", 256, 32 }, + { "AIDockMoveToDockState", 256, 32 }, + { "AIDockMoveToExitState", 256, 32 }, + { "AIDockMoveToRallyState", 256, 32 }, + { "AIMoveToState", 600, 32 }, + { "AIMoveOutOfTheWayState", 600, 32 }, + { "AIMoveAndTightenState", 600, 32 }, + { "AIMoveAwayFromRepulsorsState", 600, 32 }, + { "AIAttackApproachTargetState", 96, 32 }, + { "AIAttackPursueTargetState", 96, 32 }, + { "AIAttackAimAtTargetState", 96, 32 }, + { "AIAttackFireWeaponState", 256, 32 }, + { "AIPickUpCrateState", 4096, 32 }, + { "AIFollowWaypointPathState", 1200, 32 }, + { "AIFollowWaypointPathExactState", 1200, 32 }, + { "AIWanderInPlaceState", 600, 32 }, + { "AIFollowPathState", 1200, 32 }, + { "AIMoveAndEvacuateState", 1200, 32 }, + { "AIMoveAndDeleteState", 600, 32 }, + { "AIEnterState", 600, 32 }, + { "JetOrHeliReturningToDeadAirfieldState", 64, 32 }, + { "JetOrHeliReturnForLandingState", 64, 32 }, + { "TurretAIIdleState", 600, 32 }, + { "TurretAIIdleScanState", 600, 32 }, + { "TurretAIAimTurretState", 600, 32 }, + { "TurretAIRecenterTurretState", 600, 32 }, + { "TurretAIHoldTurretState", 600, 32 }, + { "JetOrHeliTaxiState", 64, 32 }, + { "JetTakeoffOrLandingState", 64, 32 }, + { "JetPauseBeforeTakeoffState", 64, 32 }, + { "AIAttackMoveToState", 600, 32 }, + { "AIAttackFollowWaypointPathState", 1200, 32 }, + { "AIWanderState", 600, 32 }, + { "AIPanicState", 600, 32 }, + { "ChinookMoveToBldgState", 32, 32 }, + { "ChinookRecordCreationState", 32, 32 }, + { "ScienceInfo", 96, 32 }, + { "RankInfo", 32, 32 }, + + { "FireWeaponNugget", 32, 32 }, + { "AttackNugget", 32, 32 }, + { "DeliverPayloadNugget", 48, 32 }, + { "ApplyRandomForceNugget", 32, 32 }, + { "GenericObjectCreationNugget", 632, 32 }, + { "SoundFXNugget", 320, 32 }, + { "TracerFXNugget", 32, 32 }, + { "RayEffectFXNugget", 32, 32 }, + { "LightPulseFXNugget", 68, 32 }, + { "ViewShakeFXNugget", 140, 32 }, + { "TerrainScorchFXNugget", 48, 32 }, + { "ParticleSystemFXNugget", 832, 32 }, + { "FXListAtBonePosFXNugget", 32, 32 }, + { "Squad", 256, 32 }, + { "BuildListInfo", 400, 64 }, + + { "ScriptGroup", 128, 32 }, + { "OrCondition", 1024, 256 }, + { "ScriptAction", 2600, 512 }, + { "Script", 1024, 256 }, + { "Parameter", 8192, 1024 }, + { "Condition", 2048, 256 }, + { "Template", 32, 32 }, + { "ScriptList", 32, 32 }, + { "AttackPriorityInfo", 32, 32 }, + { "SequentialScript", 32, 32 }, + { "Win32LocalFile", 1024, 256 }, + { "StdLocalFile", 1024, 256 }, + { "RAMFile", 32, 32 }, + { "BattlePlanBonuses", 32, 32 }, + { "KindOfPercentProductionChange", 32, 32 }, + { "UserParser", 4096, 256 }, + { "XferBlockData", 32, 32 }, + { "EvaCheckInfo", 52, 16 }, + { "SuperweaponInfo", 32, 32 }, + { "NamedTimerInfo", 32, 32 }, + { "PopupMessageData", 32, 32 }, + { "FloatingTextData", 32, 32 }, + { "MapObject", 5000, 1024 }, + { "Waypoint", 1024, 32 }, + { "PolygonTrigger", 64, 64 }, + { "Bridge", 32, 32 }, + { "Mapping", 384, 64 }, + { "OutputChunk", 32, 32 }, + { "InputChunk", 32, 32 }, + { "AnimateWindow", 32, 32 }, + { "GameFont", 32, 32 }, + { "NetCommandRef", 256, 32 }, + { "GameMessageArgument", 1024, 256 }, + { "GameMessageParserArgumentType", 32, 32 }, + { "GameMessageParser", 32, 32 }, + { "WeaponBonusSet", 96, 32 }, + { "Campaign", 32, 32 }, + { "Mission", 88, 32 }, + { "ModalWindow", 32, 32 }, + { "NetPacket", 32, 32 }, + { "AISideInfo", 32, 32 }, + { "AISideBuildList", 32, 32 }, + { "MetaMapRec", 256, 32 }, + { "TransportStatus", 32, 32 }, + { "Anim2DTemplate", 32, 32 }, + { "ObjectTypes", 32, 32 }, + { "NetCommandList", 512, 32 }, + { "TurretAIData", 256, 32 }, + { "NetCommandMsg", 32, 32 }, + { "NetGameCommandMsg", 64, 32 }, + { "NetAckBothCommandMsg", 32, 32 }, + { "NetAckStage1CommandMsg", 32, 32 }, + { "NetAckStage2CommandMsg", 32, 32 }, + { "NetFrameCommandMsg", 32, 32 }, + { "NetPlayerLeaveCommandMsg", 32, 32 }, + { "NetRunAheadMetricsCommandMsg", 32, 32 }, + { "NetRunAheadCommandMsg", 32, 32 }, + { "NetDestroyPlayerCommandMsg", 32, 32 }, + { "NetDisconnectFrameCommandMsg", 32, 32 }, + { "NetDisconnectScreenOffCommandMsg", 32, 32 }, + { "NetFrameResendRequestCommandMsg", 32, 32 }, + { "NetKeepAliveCommandMsg", 32, 32 }, + { "NetDisconnectKeepAliveCommandMsg", 32, 32 }, + { "NetDisconnectPlayerCommandMsg", 32, 32 }, + { "NetPacketRouterQueryCommandMsg", 32, 32 }, + { "NetPacketRouterAckCommandMsg", 32, 32 }, + { "NetDisconnectChatCommandMsg", 32, 32 }, + { "NetChatCommandMsg", 32, 32 }, + { "NetDisconnectVoteCommandMsg", 32, 32 }, + { "NetProgressCommandMsg", 32, 32 }, + { "NetWrapperCommandMsg", 32, 32 }, + { "NetFileCommandMsg", 32, 32 }, + { "NetFileAnnounceCommandMsg", 32, 32 }, + { "NetFileProgressCommandMsg", 32, 32 }, + { "NetCommandWrapperListNode", 32, 32 }, + { "NetCommandWrapperList", 32, 32 }, + { "Connection", 32, 32 }, + { "User", 32, 32 }, + { "FrameDataManager", 32, 32 }, + { "DrawableIconInfo", 32, 32 }, + { "TintEnvelope", 128, 32 }, + { "DynamicAudioEventRTS", 4000, 256 }, + { "DrawableLocoInfo", 128, 32 }, + { "W3DPrototypeClass", 512, 256 }, + { "EnumeratedIP", 32, 32 }, + { "WaterTransparencySetting", 4, 4 }, + { "WeatherSetting", 4, 4 }, + + // W3D pools! + { "BoxPrototypeClass", 128, 128 }, + { "SpherePrototypeClass", 32, 32 }, + { "SoundRenderObjPrototypeClass", 32, 32 }, + { "RingPrototypeClass", 32, 32 }, + { "PrimitivePrototypeClass", 8192, 32 }, + { "HModelPrototypeClass", 256, 32 }, + { "ParticleEmitterPrototypeClass", 32, 32 }, + { "NullPrototypeClass", 32, 32 }, + { "HLodPrototypeClass", 700, 128 }, + { "HLodDefClass", 700, 128 }, + { "DistLODPrototypeClass", 32, 32 }, + { "DazzlePrototypeClass", 32, 32 }, + { "CollectionPrototypeClass", 32, 32 }, + { "BoxPrototypeClass", 256, 32 }, + { "AggregatePrototypeClass", 32, 32 }, + { "OBBoxRenderObjClass", 512, 128 }, + { "AABoxRenderObjClass", 32, 32 }, + { "VertexMaterialClass", 6000, 2048 }, + { "TextureClass", 1200, 256 }, + { "CloudMapTerrainTextureClass", 4, 4 }, + { "ScorchTextureClass", 4, 4 }, + { "LightMapTerrainTextureClass", 4, 4 }, + { "AlphaEdgeTextureClass", 4, 4 }, + { "AlphaTerrainTextureClass", 4, 4 }, + { "TerrainTextureClass", 4, 4 }, + { "MeshClass", 14000, 2000 }, + { "HTreeClass", 2048, 512 }, + { "HLodClass", 2048, 512 }, + { "MeshModelClass", 8192, 32 }, + { "ShareBufferClass", 32768, 1024 }, + { "AABTreeClass", 300, 128 }, + { "MotionChannelClass", 16384, 32 }, + { "BitChannelClass", 84, 32 }, + { "TimeCodedMotionChannelClass", 116, 32 }, + { "AdaptiveDeltaMotionChannelClass", 32, 32 }, + { "TimeCodedBitChannelClass", 32, 32 }, + { "UVBufferClass", 8192, 32 }, + { "TexBufferClass", 384, 128 }, + { "MatBufferClass", 256, 128 }, + { "MatrixMapperClass", 32, 32 }, + { "ScaleTextureMapperClass", 32, 32 }, + { "LinearOffsetTextureMapperClass", 96, 32 }, + { "GridTextureMapperClass", 32, 32 }, + { "RotateTextureMapperClass", 32, 32 }, + { "SineLinearOffsetTextureMapperClass", 32, 32 }, + { "StepLinearOffsetTextureMapperClass", 32, 32 }, + { "ZigZagLinearOffsetTextureMapperClass", 32, 32 }, + { "ClassicEnvironmentMapperClass", 32, 32 }, + { "EnvironmentMapperClass", 256, 32 }, + { "EdgeMapperClass", 32, 32 }, + { "WSClassicEnvironmentMapperClass", 32, 32 }, + { "WSEnvironmentMapperClass", 32, 32 }, + { "GridClassicEnvironmentMapperClass", 32, 32 }, + { "GridEnvironmentMapperClass", 32, 32 }, + { "ScreenMapperClass", 32, 32 }, + { "RandomTextureMapperClass", 32, 32 }, + { "BumpEnvTextureMapperClass", 32, 32 }, + { "MeshLoadContextClass", 4, 4 }, + { "MaterialInfoClass", 8192, 32 }, + { "MeshMatDescClass", 8192, 32 }, + { "TextureLoadTaskClass", 256, 32 }, + { "SortingNodeStruct", 288, 32 }, + { "ProxyArrayClass", 32, 32 }, + { "Line3DClass", 8, 8 }, + { "Render2DClass", 64, 32 }, + { "SurfaceClass", 128, 32 }, + { "FontCharsClassCharDataStruct", 1024, 32 }, + { "FontCharsBuffer", 16, 4 }, + { "FVFInfoClass", 152, 64 }, + { "TerrainTracksRenderObjClass", 128, 32 }, + { "DynamicIBAccessClass", 32, 32 }, + { "DX8IndexBufferClass", 128, 32 }, + { "SortingIndexBufferClass", 32, 32 }, + { "DX8VertexBufferClass", 128, 32 }, + { "SortingVertexBufferClass", 32, 32 }, + { "DynD3DMATERIAL8", 8192, 32 }, + { "DynamicMatrix3D", 512, 32 }, + { "MeshGeometryClass", 32, 32 }, + { "DynamicMeshModel", 32, 32 }, + { "GapFillerClass", 32, 32 }, + { "FontCharsClass", 64, 32 }, + { "ThumbnailManagerClass", 32, 32}, + { "SmudgeSet", 32, 32}, + { "Smudge", 128, 32}, + { 0, 0, 0 } +}; + +//----------------------------------------------------------------------------- +void userMemoryAdjustPoolSize(const char *poolName, Int& initialAllocationCount, Int& overflowAllocationCount) +{ + if (initialAllocationCount > 0) + return; + + for (const PoolSizeRec* p = sizes; p->name != NULL; ++p) + { + if (strcmp(p->name, poolName) == 0) + { + initialAllocationCount = p->initial; + overflowAllocationCount = p->overflow; + return; + } + } + + DEBUG_CRASH(("Initial size for pool %s not found -- you should add it to MemoryInit.cpp\n",poolName)); +} + +//----------------------------------------------------------------------------- +static Int roundUpMemBound(Int i) +{ + const int MEM_BOUND_ALIGNMENT = 4; + + if (i < MEM_BOUND_ALIGNMENT) + return MEM_BOUND_ALIGNMENT; + else + return (i + (MEM_BOUND_ALIGNMENT-1)) & ~(MEM_BOUND_ALIGNMENT-1); +} + +//----------------------------------------------------------------------------- +void userMemoryManagerInitPools() +{ + // note that we MUST use stdio stuff here, and not the normal game file system + // (with bigfile support, etc), because that relies on memory pools, which + // aren't yet initialized properly! so rely ONLY on straight stdio stuff here. + // (not even AsciiString. thanks.) + + // since we're called prior to main, the cur dir might not be what + // we expect. so do it the hard way. + char buf[_MAX_PATH]; + ::GetModuleFileName(NULL, buf, sizeof(buf)); + char* pEnd = buf + strlen(buf); + while (pEnd != buf) + { + if (*pEnd == '\\') + { + *pEnd = 0; + break; + } + --pEnd; + } + strcat(buf, "\\Data\\INI\\MemoryPools.ini"); + + FILE* fp = fopen(buf, "r"); + if (fp) + { + char poolName[256]; + int initial, overflow; + while (fgets(buf, _MAX_PATH, fp)) + { + if (buf[0] == ';') + continue; + if (sscanf(buf, "%s %d %d", poolName, &initial, &overflow ) == 3) + { + for (PoolSizeRec* p = sizes; p->name != NULL; ++p) + { + if (stricmp(p->name, poolName) == 0) + { + // currently, these must be multiples of 4. so round up. + p->initial = roundUpMemBound(initial); + p->overflow = roundUpMemBound(overflow); + break; // from for-p + } + } + } + } + fclose(fp); + } +} + diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index 5b8ebc274a5..9a9ad1067c2 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -1,751 +1,751 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: ModuleFactory.cpp //////////////////////////////////////////////////////////////////////// -// Author: Colin Day, September 2001 -// Desc: TheModuleFactory is where we actually instance modules for objects -// and drawbles. Those modules are things such as an UpdateModule -// or DamageModule or DrawModule etc. -// -// TheModuleFactory will contain a list of ModuleTemplates, when we -// request a new module, we will look for that template in our -// list and create it -// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -#include "Common/Module.h" -#include "Common/ModuleFactory.h" -#include "Common/NameKeyGenerator.h" - -// behavior includes -#include "GameLogic/Module/AutoHealBehavior.h" -#include "GameLogic/Module/GrantStealthBehavior.h" -#include "GameLogic/Module/NeutronBlastBehavior.h" -#include "GameLogic/Module/BehaviorModule.h" -#include "GameLogic/Module/BridgeBehavior.h" -#include "GameLogic/Module/BridgeScaffoldBehavior.h" -#include "GameLogic/Module/BridgeTowerBehavior.h" -#include "GameLogic/Module/CountermeasuresBehavior.h" -#include "GameLogic/Module/DumbProjectileBehavior.h" -#include "GameLogic/Module/FreeFallProjectileBehavior.h" -#include "GameLogic/Module/InstantDeathBehavior.h" -#include "GameLogic/Module/ChronoDeathBehavior.h" -#include "GameLogic/Module/SlowDeathBehavior.h" -#include "GameLogic/Module/HelicopterSlowDeathUpdate.h" -#include "GameLogic/Module/NeutronMissileSlowDeathUpdate.h" -#include "GameLogic/Module/CaveContain.h" -#include "GameLogic/Module/OpenContain.h" -#include "GameLogic/Module/OverchargeBehavior.h" -#include "GameLogic/Module/HealContain.h" -#include "GameLogic/Module/GarrisonContain.h" -#include "GameLogic/Module/InternetHackContain.h" -#include "GameLogic/Module/RailedTransportContain.h" -#include "GameLogic/Module/RiderChangeContain.h" -#include "GameLogic/Module/TransportContain.h" -#include "GameLogic/Module/MobNexusContain.h" -#include "GameLogic/Module/TunnelContain.h" -#include "GameLogic/Module/OverlordContain.h" -#include "GameLogic/Module/HelixContain.h" -#include "GameLogic/Module/ParachuteContain.h" -#ifdef ALLOW_SURRENDER -#include "GameLogic/Module/POWTruckBehavior.h" -#include "GameLogic/Module/PrisonBehavior.h" -#include "GameLogic/Module/PropagandaCenterBehavior.h" -#endif -#include "GameLogic/Module/PropagandaTowerBehavior.h" -#include "GameLogic/Module/BunkerBusterBehavior.h" -#include "GameLogic/Module/FireWeaponWhenDamagedBehavior.h" -#include "GameLogic/Module/FireWeaponWhenDeadBehavior.h" -#include "GameLogic/Module/DelayedUpgradeBehavior.h" -#include "GameLogic/Module/GenerateMinefieldBehavior.h" -#include "GameLogic/Module/ParkingPlaceBehavior.h" -#include "GameLogic/Module/FlightDeckBehavior.h" -#include "GameLogic/Module/PoisonedBehavior.h" -#include "GameLogic/Module/RebuildHoleBehavior.h" -#include "GameLogic/Module/SupplyWarehouseCripplingBehavior.h" -#include "GameLogic/Module/TechBuildingBehavior.h" -#include "GameLogic/Module/MinefieldBehavior.h" -#include "GameLogic/Module/BattleBusSlowDeathBehavior.h" -#include "GameLogic/Module/JetSlowDeathBehavior.h" - -// die includes -#include "GameLogic/Module/CreateCrateDie.h" -#include "GameLogic/Module/CreateObjectDie.h" -#include "GameLogic/Module/CrushDie.h" -#include "GameLogic/Module/DamDie.h" -#include "GameLogic/Module/DestroyDie.h" -#include "GameLogic/Module/EjectPilotDie.h" -#include "GameLogic/Module/FXListDie.h" -#include "GameLogic/Module/RebuildHoleExposeDie.h" -#include "GameLogic/Module/SpecialPowerCompletionDie.h" -#include "GameLogic/Module/UpgradeDie.h" -#include "GameLogic/Module/KeepObjectDie.h" - -// logic update includes -#include "GameLogic/Module/AIUpdate.h" -#include "GameLogic/Module/AnimationSteeringUpdate.h" -#include "GameLogic/Module/AssistedTargetingUpdate.h" -#include "GameLogic/Module/BaseRegenerateUpdate.h" -#include "GameLogic/Module/BoneFXUpdate.h" -#include "GameLogic/Module/ChinookAIUpdate.h" -#include "GameLogic/Module/DefaultProductionExitUpdate.h" -#include "GameLogic/Module/DeletionUpdate.h" -#include "GameLogic/Module/SmartBombTargetHomingUpdate.h" -#include "GameLogic/Module/DeliverPayloadAIUpdate.h" -#include "GameLogic/Module/DozerAIUpdate.h" -#include "GameLogic/Module/DynamicGeometryInfoUpdate.h" -#include "GameLogic/Module/DynamicShroudClearingRangeUpdate.h" -#include "GameLogic/Module/EnemyNearUpdate.h" -#include "GameLogic/Module/FireSpreadUpdate.h" -#include "GameLogic/Module/FirestormDynamicGeometryInfoUpdate.h" -#include "GameLogic/Module/FireWeaponUpdate.h" -#include "GameLogic/Module/FlammableUpdate.h" -#include "GameLogic/Module/FloatUpdate.h" -#include "GameLogic/Module/TensileFormationUpdate.h" -#include "GameLogic/Module/HackInternetAIUpdate.h" -#include "GameLogic/Module/DeployStyleAIUpdate.h" -#include "GameLogic/Module/AssaultTransportAIUpdate.h" -#include "GameLogic/Module/HeightDieUpdate.h" -#include "GameLogic/Module/HordeUpdate.h" -#include "GameLogic/Module/ScatterShotUpdate.h" -#include "GameLogic/Module/JetAIUpdate.h" -#include "GameLogic/Module/LaserUpdate.h" -#include "GameLogic/Module/PointDefenseLaserUpdate.h" -#include "GameLogic/Module/CleanupHazardUpdate.h" -#include "GameLogic/Module/AutoFindHealingUpdate.h" -#include "GameLogic/Module/CommandButtonHuntUpdate.h" -#include "GameLogic/Module/PilotFindVehicleUpdate.h" -#include "GameLogic/Module/DemoTrapUpdate.h" -#include "GameLogic/Module/ParticleUplinkCannonUpdate.h" -#include "GameLogic/Module/SpectreGunshipUpdate.h" -#include "GameLogic/Module/SpectreGunshipDeploymentUpdate.h" -#include "GameLogic/Module/BaikonurLaunchPower.h" -#include "GameLogic/Module/BattlePlanUpdate.h" -#include "GameLogic/Module/LifetimeUpdate.h" -#include "GameLogic/Module/RadiusDecalUpdate.h" -#include "GameLogic/Module/RadiusDecalBehavior.h" -#include "GameLogic/Module/AutoDepositUpdate.h" -#include "GameLogic/Module/MissileAIUpdate.h" -#include "GameLogic/Module/NeutronMissileUpdate.h" -#include "GameLogic/Module/OCLUpdate.h" -#include "GameLogic/Module/PhysicsUpdate.h" -#ifdef ALLOW_SURRENDER -#include "GameLogic/Module/POWTruckAIUpdate.h" -#endif -#include "GameLogic/Module/ProductionUpdate.h" -#include "GameLogic/Module/ProjectileStreamUpdate.h" -#include "GameLogic/Module/ProneUpdate.h" -#include "GameLogic/Module/QueueProductionExitUpdate.h" -#include "GameLogic/Module/RadarUpdate.h" -#include "GameLogic/Module/RepairDockUpdate.h" -#ifdef ALLOW_SURRENDER -#include "GameLogic/Module/PrisonDockUpdate.h" -#endif -#include "GameLogic/Module/RailedTransportDockUpdate.h" -#include "GameLogic/Module/RailedTransportAIUpdate.h" -#include "GameLogic/Module/RailroadGuideAIUpdate.h" -#include "GameLogic/Module/SlavedUpdate.h" -#include "GameLogic/Module/MobMemberSlavedUpdate.h" -#include "GameLogic/Module/SpecialAbilityUpdate.h" -#include "GameLogic/Module/MissileLauncherBuildingUpdate.h" -#include "GameLogic/Module/StealthDetectorUpdate.h" -#include "GameLogic/Module/StealthUpdate.h" -#include "GameLogic/Module/SpawnPointProductionExitUpdate.h" -#include "GameLogic/Module/SpawnBehavior.h" -#include "GameLogic/Module/SpyVisionUpdate.h" -#include "GameLogic/Module/StickyBombUpdate.h" -#include "GameLogic/Module/FireOCLAfterWeaponCooldownUpdate.h" -#include "GameLogic/Module/HijackerUpdate.h" -#include "GameLogic/Module/StructureCollapseUpdate.h" -#include "GameLogic/Module/StructureToppleUpdate.h" -#include "GameLogic/Module/SupplyCenterDockUpdate.h" -#include "GameLogic/Module/SupplyCenterProductionExitUpdate.h" -#include "GameLogic/Module/SupplyTruckAIUpdate.h" -#include "GameLogic/Module/SupplyWarehouseDockUpdate.h" -#include "GameLogic/Module/ToppleUpdate.h" -#include "GameLogic/Module/TransportAIUpdate.h" -#include "GameLogic/Module/WanderAIUpdate.h" -#include "GameLogic/Module/TeleporterAIUpdate.h" -#include "GameLogic/Module/WaveGuideUpdate.h" -#include "GameLogic/Module/WeaponBonusUpdate.h" -#include "GameLogic/Module/ArmorDamageScalarUpdate.h" -#include "GameLogic/Module/WorkerAIUpdate.h" -#include "GameLogic/Module/PowerPlantUpdate.h" -#include "GameLogic/Module/CheckpointUpdate.h" -#include "GameLogic/Module/EMPUpdate.h" - -// upgrade includes -#include "GameLogic/Module/ActiveShroudUpgrade.h" -#include "GameLogic/Module/ArmorUpgrade.h" -#include "GameLogic/Module/CommandSetUpgrade.h" -#include "GameLogic/Module/GrantScienceUpgrade.h" -#include "GameLogic/Module/PassengersFireUpgrade.h" -#include "GameLogic/Module/LocomotorSetUpgrade.h" -#include "GameLogic/Module/ObjectCreationUpgrade.h" -#include "GameLogic/Module/RadarUpgrade.h" -#include "GameLogic/Module/PowerPlantUpgrade.h" -#include "GameLogic/Module/ReplaceObjectUpgrade.h" -#include "GameLogic/Module/ModelConditionUpgrade.h" -#include "GameLogic/Module/StatusBitsUpgrade.h" -#include "GameLogic/Module/SubObjectsUpgrade.h" -#include "GameLogic/Module/StealthUpgrade.h" -#include "GameLogic/Module/UnpauseSpecialPowerUpgrade.h" -#include "GameLogic/Module/WeaponBonusUpgrade.h" -#include "GameLogic/Module/WeaponSetUpgrade.h" -#include "GameLogic/Module/WeaponBonusUpgrade.h" -#include "GameLogic/Module/CostModifierUpgrade.h" -#include "GameLogic/Module/ProductionTimeModifierUpgrade.h" -#include "GameLogic/Module/UnitProductionBonusUpgrade.h" -#include "GameLogic/Module/ExperienceScalarUpgrade.h" -#include "GameLogic/Module/MaxHealthUpgrade.h" - -// create includes -#include "GameLogic/Module/LockWeaponCreate.h" -#include "GameLogic/Module/SupplyCenterCreate.h" -#include "GameLogic/Module/SupplyWarehouseCreate.h" -#include "GameLogic/Module/GrantUpgradeCreate.h" -#include "GameLogic/Module/PreorderCreate.h" -#include "GameLogic/Module/SpecialPowerCreate.h" -#include "GameLogic/Module/VeterancyGainCreate.h" - -// damage includes -#include "GameLogic/Module/BoneFXDamage.h" -#include "GameLogic/Module/TransitionDamageFX.h" - -// collide includes -#include "GameLogic/Module/FireWeaponCollide.h" -#include "GameLogic/Module/SquishCollide.h" - -#include "GameLogic/Module/ConvertToCarBombCrateCollide.h" -#include "GameLogic/Module/ConvertToHijackedVehicleCrateCollide.h" -#include "GameLogic/Module/HealCrateCollide.h" -#include "GameLogic/Module/MoneyCrateCollide.h" -#include "GameLogic/Module/SabotageCommandCenterCrateCollide.h" -#include "GameLogic/Module/SabotageFakeBuildingCrateCollide.h" -#include "GameLogic/Module/SabotageInternetCenterCrateCollide.h" -#include "GameLogic/Module/SabotageMilitaryFactoryCrateCollide.h" -#include "GameLogic/Module/SabotagePowerPlantCrateCollide.h" -#include "GameLogic/Module/SabotageSuperweaponCrateCollide.h" -#include "GameLogic/Module/SabotageSupplyCenterCrateCollide.h" -#include "GameLogic/Module/SabotageSupplyDropzoneCrateCollide.h" -#include "GameLogic/Module/SalvageCrateCollide.h" -#include "GameLogic/Module/ShroudCrateCollide.h" -#include "GameLogic/Module/UnitCrateCollide.h" -#include "GameLogic/Module/VeterancyCrateCollide.h" - -// body includes -#include "GameLogic/Module/InactiveBody.h" -#include "GameLogic/Module/ActiveBody.h" -#include "GameLogic/Module/HighlanderBody.h" -#include "GameLogic/Module/ImmortalBody.h" -#include "GameLogic/Module/StructureBody.h" -#include "GameLogic/Module/HiveStructureBody.h" -#include "GameLogic/Module/UndeadBody.h" - -// contain includes -// (none) - -// special power modules -#include "GameLogic/Module/CashHackSpecialPower.h" -#include "GameLogic/Module/DefectorSpecialPower.h" -#ifdef ALLOW_DEMORALIZE -#include "GameLogic/Module/DemoralizeSpecialPower.h" -#endif -#include "GameLogic/Module/OCLSpecialPower.h" -#include "GameLogic/Module/SpecialAbility.h" -#include "GameLogic/Module/SpyVisionSpecialPower.h" -#include "GameLogic/Module/UpgradeSpecialPower.h" -#include "GameLogic/Module/CashBountyPower.h" -#include "GameLogic/Module/CleanupAreaPower.h" -#include "GameLogic/Module/FireWeaponPower.h" - -// destroy includes -// (none) - -// client update includes -#include "GameClient/Module/AnimatedParticleSysBoneClientUpdate.h" -#include "GameClient/Module/SwayClientUpdate.h" -#include "GameClient/Module/BeaconClientUpdate.h" - -// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// -ModuleFactory *TheModuleFactory = NULL; ///< the module factory singleton - -// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// - -#ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -ModuleFactory::ModuleFactory( void ) -{ - m_moduleTemplateMap.clear(); - m_moduleDataList.clear(); - -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -ModuleFactory::~ModuleFactory( void ) -{ - m_moduleTemplateMap.clear(); - - for (ModuleDataList::iterator i = m_moduleDataList.begin(); i != m_moduleDataList.end(); ++i) - { - const ModuleData* data = *i; - delete data; - } - m_moduleDataList.clear(); - -} - -//------------------------------------------------------------------------------------------------- -/** Initialize the module factory. Any class that needs to be attached - * to objects or drawables as modules needs to add a template - * for that class here */ -//------------------------------------------------------------------------------------------------- -void ModuleFactory::init( void ) -{ - - // behavior modules - addModule( AutoHealBehavior ); - addModule( GrantStealthBehavior ); - addModule( NeutronBlastBehavior ); - addModule( BridgeBehavior ); - addModule( BridgeScaffoldBehavior ); - addModule( BridgeTowerBehavior ); - addModule( CountermeasuresBehavior ); - addModule( DumbProjectileBehavior ); - addModule( FreeFallProjectileBehavior ); - addModule( PhysicsBehavior ); - addModule( InstantDeathBehavior ); - addModule( ChronoDeathBehavior ); - addModule( SlowDeathBehavior ); - addModule( HelicopterSlowDeathBehavior ); - addModule( NeutronMissileSlowDeathBehavior ); - addModule( CaveContain ); - addModule( OpenContain ); - addModule( OverchargeBehavior ); - addModule( HealContain ); - addModule( GarrisonContain ); - addModule( InternetHackContain ); - addModule( TransportContain ); - addModule( RiderChangeContain ); - addModule( RailedTransportContain ); - addModule( MobNexusContain ); - addModule( TunnelContain ); - addModule( OverlordContain ); - addModule( HelixContain ); - addModule( ParachuteContain ); -#ifdef ALLOW_SURRENDER - addModule( POWTruckBehavior ); - addModule( PrisonBehavior ); - addModule( PropagandaCenterBehavior ); -#endif - addModule( PropagandaTowerBehavior ); - addModule( BunkerBusterBehavior ); - addModule( FireWeaponWhenDamagedBehavior ); - addModule( FireWeaponWhenDeadBehavior ); - addModule( DelayedUpgradeBehavior ); - addModule( GenerateMinefieldBehavior ); - addModule( ParkingPlaceBehavior ); - addModule( FlightDeckBehavior ); - addModule( PoisonedBehavior ); - addModule( RebuildHoleBehavior ); - addModule( SupplyWarehouseCripplingBehavior ); - addModule( TechBuildingBehavior ); - addModule( MinefieldBehavior ); - addModule( BattleBusSlowDeathBehavior ); - addModule( JetSlowDeathBehavior ); - addModule( RailroadBehavior ); - addModule( SpawnBehavior ); - - // die modules - addModule( DestroyDie ); - addModule( FXListDie ); - addModule( CrushDie ); - addModule( DamDie ); - addModule( CreateCrateDie ); - addModule( CreateObjectDie ); - addModule( EjectPilotDie ); - addModule( SpecialPowerCompletionDie ); - addModule( RebuildHoleExposeDie ); - addModule( UpgradeDie ); - addModule( KeepObjectDie ); - - // update modules - addModule( AssistedTargetingUpdate ); - addModule( AutoFindHealingUpdate ); - addModule( BaseRegenerateUpdate ); - addModule( StealthDetectorUpdate ); - addModule( StealthUpdate ); - addModule( DeletionUpdate ); - addModule( SmartBombTargetHomingUpdate ); - addModule( DynamicShroudClearingRangeUpdate ); - addModule( DeployStyleAIUpdate ); - addModule( AssaultTransportAIUpdate ); - addModule( HordeUpdate ); - addModule( ToppleUpdate ); - addModule( EnemyNearUpdate ); - addModule( LifetimeUpdate ); - addModule( RadiusDecalUpdate ); - addModule( RadiusDecalBehavior ); - addModule( EMPUpdate ); - addModule( LeafletDropBehavior ); - addModule( AutoDepositUpdate ); - addModule( WeaponBonusUpdate ); - addModule( ArmorDamageScalarUpdate ); - addModule( MissileAIUpdate ); - addModule( NeutronMissileUpdate ); - addModule( FireSpreadUpdate ); - addModule( FireWeaponUpdate ); - addModule( FlammableUpdate ); - addModule( FloatUpdate ); - addModule( TensileFormationUpdate ); - addModule( HeightDieUpdate ); - addModule( ScatterShotUpdate ); - addModule( ChinookAIUpdate ); - addModule( JetAIUpdate ); - addModule( AIUpdateInterface ); - addModule( SupplyTruckAIUpdate ); - addModule( DeliverPayloadAIUpdate ); - addModule( HackInternetAIUpdate ); - addModule( DynamicGeometryInfoUpdate ); - addModule( FirestormDynamicGeometryInfoUpdate ); - addModule( LaserUpdate ); - addModule( PointDefenseLaserUpdate ); - addModule( CleanupHazardUpdate ); - addModule( CommandButtonHuntUpdate ); - addModule( PilotFindVehicleUpdate ); - addModule( DemoTrapUpdate ); - addModule( ParticleUplinkCannonUpdate ); - addModule( SpectreGunshipUpdate ); - addModule( SpectreGunshipDeploymentUpdate ); - addModule( BaikonurLaunchPower ); - addModule( BattlePlanUpdate ); - addModule( ProjectileStreamUpdate ); - addModule( QueueProductionExitUpdate ); - addModule( RepairDockUpdate ); -#ifdef ALLOW_SURRENDER - addModule( PrisonDockUpdate ); -#endif - addModule( RailedTransportDockUpdate ); - addModule( DefaultProductionExitUpdate ); - addModule( SpawnPointProductionExitUpdate ); - addModule( SpyVisionUpdate ); - addModule( SlavedUpdate ); - addModule( MobMemberSlavedUpdate ); - addModule( OCLUpdate ); - addModule( SpecialAbilityUpdate ); - addModule( MissileLauncherBuildingUpdate ); - addModule( SupplyCenterProductionExitUpdate ); - addModule( SupplyCenterDockUpdate ); - addModule( SupplyWarehouseDockUpdate ); - addModule( DozerAIUpdate ); -#ifdef ALLOW_SURRENDER - addModule( POWTruckAIUpdate ); -#endif - addModule( RailedTransportAIUpdate ); - addModule( ProductionUpdate ); - addModule( ProneUpdate ); - addModule( StickyBombUpdate ); - addModule( FireOCLAfterWeaponCooldownUpdate ); - addModule( HijackerUpdate ); - addModule( StructureToppleUpdate ); - addModule( StructureCollapseUpdate ); - addModule( BoneFXUpdate ); - addModule( RadarUpdate ); - addModule( AnimationSteeringUpdate ); - addModule( TransportAIUpdate ); - addModule( WanderAIUpdate ); - addModule( TeleporterAIUpdate ); - addModule( WaveGuideUpdate ); - addModule( WorkerAIUpdate ); - addModule( PowerPlantUpdate ); - addModule( CheckpointUpdate ); - - // upgrade modules - addModule( CostModifierUpgrade ); - addModule( ProductionTimeModifierUpgrade ); - addModule( UnitProductionBonusUpgrade ); - addModule( ActiveShroudUpgrade ); - addModule( ArmorUpgrade ); - addModule( CommandSetUpgrade ); - addModule( GrantScienceUpgrade ); - addModule( PassengersFireUpgrade ); - addModule( StatusBitsUpgrade ); - addModule( SubObjectsUpgrade ); - addModule( StealthUpgrade ); - addModule( RadarUpgrade ); - addModule( PowerPlantUpgrade ); - addModule( LocomotorSetUpgrade ); - addModule( ObjectCreationUpgrade ); - addModule( ReplaceObjectUpgrade ); - addModule( ModelConditionUpgrade ); - addModule( UnpauseSpecialPowerUpgrade ); - addModule( WeaponBonusUpgrade ); - addModule( WeaponSetUpgrade ); - addModule( WeaponBonusUpgrade ); - addModule( ExperienceScalarUpgrade ); - addModule( MaxHealthUpgrade ); - - // create modules - addModule( LockWeaponCreate ); - addModule( PreorderCreate ); - addModule( SupplyCenterCreate ); - addModule( SupplyWarehouseCreate ); - addModule( SpecialPowerCreate ); - addModule( GrantUpgradeCreate ); - addModule( VeterancyGainCreate ); - - // damage modules - addModule( BoneFXDamage ); - addModule( TransitionDamageFX ); - - // collide modules - addModule( FireWeaponCollide ); - addModule( SquishCollide ); - - addModule( HealCrateCollide ); - addModule( MoneyCrateCollide ); - addModule( ShroudCrateCollide ); - addModule( UnitCrateCollide ); - addModule( VeterancyCrateCollide ); - addModule( ConvertToCarBombCrateCollide ); - addModule( ConvertToHijackedVehicleCrateCollide ); - addModule( SabotageCommandCenterCrateCollide ); - addModule( SabotageFakeBuildingCrateCollide ); - addModule( SabotageInternetCenterCrateCollide ); - addModule( SabotageMilitaryFactoryCrateCollide ); - addModule( SabotagePowerPlantCrateCollide ); - addModule( SabotageSuperweaponCrateCollide ); - addModule( SabotageSupplyCenterCrateCollide ); - addModule( SabotageSupplyDropzoneCrateCollide ); - addModule( SalvageCrateCollide ); - - // body modules - addModule( InactiveBody ); - addModule( ActiveBody ); - addModule( HighlanderBody ); - addModule( ImmortalBody ); - addModule( StructureBody ); - addModule( HiveStructureBody ); - addModule( UndeadBody ); - - // contain modules - // (none) - - // special power modules - addModule( CashHackSpecialPower ); - addModule( DefectorSpecialPower ); -#ifdef ALLOW_DEMORALIZE - addModule( DemoralizeSpecialPower ); -#endif - addModule( OCLSpecialPower ); - addModule( FireWeaponPower ); - addModule( SpecialAbility ); - addModule( SpyVisionSpecialPower ); - addModule( UpgradeSpecialPower ); - addModule( CashBountyPower ); - addModule( CleanupAreaPower ); - - // destroy modules - // (none) - - // client update modules - addModule( AnimatedParticleSysBoneClientUpdate ); - addModule( SwayClientUpdate ); - addModule( BeaconClientUpdate ); - -} // end init - -//------------------------------------------------------------------------------------------------- -Int ModuleFactory::findModuleInterfaceMask(const AsciiString& name, ModuleType type) -{ - if (name.isEmpty()) - return 0; - - const ModuleTemplate* moduleTemplate = findModuleTemplate(name, type); - if (moduleTemplate) - { - return moduleTemplate->m_whichInterfaces; - } - - return 0; -} - -//------------------------------------------------------------------------------------------------- -ModuleData* ModuleFactory::newModuleDataFromINI(INI* ini, const AsciiString& name, ModuleType type, - const AsciiString& moduleTag) -{ - if (name.isEmpty()) - return NULL; - - const ModuleTemplate* moduleTemplate = findModuleTemplate(name, type); - if (moduleTemplate) - { - ModuleData* md = (*moduleTemplate->m_createDataProc)(ini); - md->setModuleTagNameKey( NAMEKEY( moduleTag ) ); - m_moduleDataList.push_back(md); - return md; - } - - return NULL; -} - -// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------------- -/*static*/ NameKeyType ModuleFactory::makeDecoratedNameKey(const AsciiString& name, ModuleType type) -{ - char tmp[256]; - tmp[0] = '0' + (int)type; - strcpy(&tmp[1], name.str()); - return TheNameKeyGenerator->nameToKey(tmp); -} - -//------------------------------------------------------------------------------------------------- -const ModuleFactory::ModuleTemplate* ModuleFactory::findModuleTemplate(const AsciiString& name, ModuleType type) -{ - NameKeyType namekey = makeDecoratedNameKey(name, type); - - ModuleTemplateMap::const_iterator it = m_moduleTemplateMap.find(namekey); - if (it == m_moduleTemplateMap.end()) - { - DEBUG_CRASH(( "Module name '%s' not found\n", name.str() )); - return NULL; - } - else - { - return &(*it).second; - } -} - -//------------------------------------------------------------------------------------------------- -/** Allocate a new acton class istance given the name */ -//------------------------------------------------------------------------------------------------- -Module *ModuleFactory::newModule( Thing *thing, const AsciiString& name, const ModuleData* moduleData, ModuleType type ) -{ - // sanity - if( name.isEmpty() ) - { - DEBUG_CRASH(("attempting to create module with empty name\n")); - return NULL; - } - const ModuleTemplate* mt = findModuleTemplate(name, type); - if (mt) - { - Module* mod = (*mt->m_createProc)( thing, moduleData ); - -#ifdef DEBUG_CRASHING - if (type == MODULETYPE_BEHAVIOR) - { - BehaviorModule* bm = (BehaviorModule*)mod; - - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_BODY)) != 0) == (bm->getBody() != NULL), - ("getInterfaceMask bad for MODULE_BODY (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_COLLIDE)) != 0) == (bm->getCollide() != NULL), - ("getInterfaceMask bad for MODULE_COLLIDE (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_CONTAIN)) != 0) == (bm->getContain() != NULL), - ("getInterfaceMask bad for MODULE_CONTAIN (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_CREATE)) != 0) == (bm->getCreate() != NULL), - ("getInterfaceMask bad for MODULE_CREATE (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_DAMAGE)) != 0) == (bm->getDamage() != NULL), - ("getInterfaceMask bad for MODULE_DAMAGE (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_DESTROY)) != 0) == (bm->getDestroy() != NULL), - ("getInterfaceMask bad for MODULE_DESTROY (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_DIE)) != 0) == (bm->getDie() != NULL), - ("getInterfaceMask bad for MODULE_DIE (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_SPECIAL_POWER)) != 0) == (bm->getSpecialPower() != NULL), - ("getInterfaceMask bad for MODULE_SPECIAL_POWER (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_UPDATE)) != 0) == (bm->getUpdate() != NULL), - ("getInterfaceMask bad for MODULE_UPDATE (%s)\n",name.str())); - DEBUG_ASSERTCRASH( - ((mt->m_whichInterfaces & (MODULEINTERFACE_UPGRADE)) != 0) == (bm->getUpgrade() != NULL), - ("getInterfaceMask bad for MODULE_UPGRADE (%s)\n",name.str())); - } -#endif - - return mod; - } - - return NULL; - -} // end newModule - -//------------------------------------------------------------------------------------------------- -/** Add a module template to our list of templates */ -//------------------------------------------------------------------------------------------------- -void ModuleFactory::addModuleInternal( NewModuleProc proc, NewModuleDataProc dataproc, ModuleType type, const AsciiString& name, Int whichIntf ) -{ - NameKeyType namekey = makeDecoratedNameKey(name, type); - ModuleTemplate& mtm = m_moduleTemplateMap[namekey]; // this creates it if it does not exist already - mtm.m_createProc = proc; - mtm.m_createDataProc = dataproc; - mtm.m_whichInterfaces = whichIntf; -} - -//------------------------------------------------------------------------------------------------- -void ModuleFactory::crc( Xfer *xfer ) -{ - for (ModuleDataList::iterator mdIt = m_moduleDataList.begin(); mdIt != m_moduleDataList.end(); ++mdIt) - { - ((ModuleData *)(*mdIt))->crc(xfer); - } -} - -//------------------------------------------------------------------------------------------------- -void ModuleFactory::xfer( Xfer *xfer ) -{ - - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - for (ModuleDataList::iterator mdIt = m_moduleDataList.begin(); mdIt != m_moduleDataList.end(); ++mdIt) - { - ((ModuleData *)(*mdIt))->xfer(xfer); - } -} - -//------------------------------------------------------------------------------------------------- -void ModuleFactory::loadPostProcess( void ) -{ -} +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: ModuleFactory.cpp //////////////////////////////////////////////////////////////////////// +// Author: Colin Day, September 2001 +// Desc: TheModuleFactory is where we actually instance modules for objects +// and drawbles. Those modules are things such as an UpdateModule +// or DamageModule or DrawModule etc. +// +// TheModuleFactory will contain a list of ModuleTemplates, when we +// request a new module, we will look for that template in our +// list and create it +// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#include "Common/Module.h" +#include "Common/ModuleFactory.h" +#include "Common/NameKeyGenerator.h" + +// behavior includes +#include "GameLogic/Module/AutoHealBehavior.h" +#include "GameLogic/Module/GrantStealthBehavior.h" +#include "GameLogic/Module/NeutronBlastBehavior.h" +#include "GameLogic/Module/BehaviorModule.h" +#include "GameLogic/Module/BridgeBehavior.h" +#include "GameLogic/Module/BridgeScaffoldBehavior.h" +#include "GameLogic/Module/BridgeTowerBehavior.h" +#include "GameLogic/Module/CountermeasuresBehavior.h" +#include "GameLogic/Module/DumbProjectileBehavior.h" +#include "GameLogic/Module/FreeFallProjectileBehavior.h" +#include "GameLogic/Module/InstantDeathBehavior.h" +#include "GameLogic/Module/ChronoDeathBehavior.h" +#include "GameLogic/Module/SlowDeathBehavior.h" +#include "GameLogic/Module/HelicopterSlowDeathUpdate.h" +#include "GameLogic/Module/NeutronMissileSlowDeathUpdate.h" +#include "GameLogic/Module/CaveContain.h" +#include "GameLogic/Module/OpenContain.h" +#include "GameLogic/Module/OverchargeBehavior.h" +#include "GameLogic/Module/HealContain.h" +#include "GameLogic/Module/GarrisonContain.h" +#include "GameLogic/Module/InternetHackContain.h" +#include "GameLogic/Module/RailedTransportContain.h" +#include "GameLogic/Module/RiderChangeContain.h" +#include "GameLogic/Module/TransportContain.h" +#include "GameLogic/Module/MobNexusContain.h" +#include "GameLogic/Module/TunnelContain.h" +#include "GameLogic/Module/OverlordContain.h" +#include "GameLogic/Module/HelixContain.h" +#include "GameLogic/Module/ParachuteContain.h" +#ifdef ALLOW_SURRENDER +#include "GameLogic/Module/POWTruckBehavior.h" +#include "GameLogic/Module/PrisonBehavior.h" +#include "GameLogic/Module/PropagandaCenterBehavior.h" +#endif +#include "GameLogic/Module/PropagandaTowerBehavior.h" +#include "GameLogic/Module/BunkerBusterBehavior.h" +#include "GameLogic/Module/FireWeaponWhenDamagedBehavior.h" +#include "GameLogic/Module/FireWeaponWhenDeadBehavior.h" +#include "GameLogic/Module/DelayedUpgradeBehavior.h" +#include "GameLogic/Module/GenerateMinefieldBehavior.h" +#include "GameLogic/Module/ParkingPlaceBehavior.h" +#include "GameLogic/Module/FlightDeckBehavior.h" +#include "GameLogic/Module/PoisonedBehavior.h" +#include "GameLogic/Module/RebuildHoleBehavior.h" +#include "GameLogic/Module/SupplyWarehouseCripplingBehavior.h" +#include "GameLogic/Module/TechBuildingBehavior.h" +#include "GameLogic/Module/MinefieldBehavior.h" +#include "GameLogic/Module/BattleBusSlowDeathBehavior.h" +#include "GameLogic/Module/JetSlowDeathBehavior.h" + +// die includes +#include "GameLogic/Module/CreateCrateDie.h" +#include "GameLogic/Module/CreateObjectDie.h" +#include "GameLogic/Module/CrushDie.h" +#include "GameLogic/Module/DamDie.h" +#include "GameLogic/Module/DestroyDie.h" +#include "GameLogic/Module/EjectPilotDie.h" +#include "GameLogic/Module/FXListDie.h" +#include "GameLogic/Module/RebuildHoleExposeDie.h" +#include "GameLogic/Module/SpecialPowerCompletionDie.h" +#include "GameLogic/Module/UpgradeDie.h" +#include "GameLogic/Module/KeepObjectDie.h" + +// logic update includes +#include "GameLogic/Module/AIUpdate.h" +#include "GameLogic/Module/AnimationSteeringUpdate.h" +#include "GameLogic/Module/AssistedTargetingUpdate.h" +#include "GameLogic/Module/BaseRegenerateUpdate.h" +#include "GameLogic/Module/BoneFXUpdate.h" +#include "GameLogic/Module/ChinookAIUpdate.h" +#include "GameLogic/Module/DefaultProductionExitUpdate.h" +#include "GameLogic/Module/DeletionUpdate.h" +#include "GameLogic/Module/SmartBombTargetHomingUpdate.h" +#include "GameLogic/Module/DeliverPayloadAIUpdate.h" +#include "GameLogic/Module/DozerAIUpdate.h" +#include "GameLogic/Module/DynamicGeometryInfoUpdate.h" +#include "GameLogic/Module/DynamicShroudClearingRangeUpdate.h" +#include "GameLogic/Module/EnemyNearUpdate.h" +#include "GameLogic/Module/FireSpreadUpdate.h" +#include "GameLogic/Module/FirestormDynamicGeometryInfoUpdate.h" +#include "GameLogic/Module/FireWeaponUpdate.h" +#include "GameLogic/Module/FlammableUpdate.h" +#include "GameLogic/Module/FloatUpdate.h" +#include "GameLogic/Module/TensileFormationUpdate.h" +#include "GameLogic/Module/HackInternetAIUpdate.h" +#include "GameLogic/Module/DeployStyleAIUpdate.h" +#include "GameLogic/Module/AssaultTransportAIUpdate.h" +#include "GameLogic/Module/HeightDieUpdate.h" +#include "GameLogic/Module/HordeUpdate.h" +#include "GameLogic/Module/ScatterShotUpdate.h" +#include "GameLogic/Module/JetAIUpdate.h" +#include "GameLogic/Module/LaserUpdate.h" +#include "GameLogic/Module/PointDefenseLaserUpdate.h" +#include "GameLogic/Module/CleanupHazardUpdate.h" +#include "GameLogic/Module/AutoFindHealingUpdate.h" +#include "GameLogic/Module/CommandButtonHuntUpdate.h" +#include "GameLogic/Module/PilotFindVehicleUpdate.h" +#include "GameLogic/Module/DemoTrapUpdate.h" +#include "GameLogic/Module/ParticleUplinkCannonUpdate.h" +#include "GameLogic/Module/SpectreGunshipUpdate.h" +#include "GameLogic/Module/SpectreGunshipDeploymentUpdate.h" +#include "GameLogic/Module/BaikonurLaunchPower.h" +#include "GameLogic/Module/BattlePlanUpdate.h" +#include "GameLogic/Module/LifetimeUpdate.h" +#include "GameLogic/Module/RadiusDecalUpdate.h" +#include "GameLogic/Module/RadiusDecalBehavior.h" +#include "GameLogic/Module/AutoDepositUpdate.h" +#include "GameLogic/Module/MissileAIUpdate.h" +#include "GameLogic/Module/NeutronMissileUpdate.h" +#include "GameLogic/Module/OCLUpdate.h" +#include "GameLogic/Module/PhysicsUpdate.h" +#ifdef ALLOW_SURRENDER +#include "GameLogic/Module/POWTruckAIUpdate.h" +#endif +#include "GameLogic/Module/ProductionUpdate.h" +#include "GameLogic/Module/ProjectileStreamUpdate.h" +#include "GameLogic/Module/ProneUpdate.h" +#include "GameLogic/Module/QueueProductionExitUpdate.h" +#include "GameLogic/Module/RadarUpdate.h" +#include "GameLogic/Module/RepairDockUpdate.h" +#ifdef ALLOW_SURRENDER +#include "GameLogic/Module/PrisonDockUpdate.h" +#endif +#include "GameLogic/Module/RailedTransportDockUpdate.h" +#include "GameLogic/Module/RailedTransportAIUpdate.h" +#include "GameLogic/Module/RailroadGuideAIUpdate.h" +#include "GameLogic/Module/SlavedUpdate.h" +#include "GameLogic/Module/MobMemberSlavedUpdate.h" +#include "GameLogic/Module/SpecialAbilityUpdate.h" +#include "GameLogic/Module/MissileLauncherBuildingUpdate.h" +#include "GameLogic/Module/StealthDetectorUpdate.h" +#include "GameLogic/Module/StealthUpdate.h" +#include "GameLogic/Module/SpawnPointProductionExitUpdate.h" +#include "GameLogic/Module/SpawnBehavior.h" +#include "GameLogic/Module/SpyVisionUpdate.h" +#include "GameLogic/Module/StickyBombUpdate.h" +#include "GameLogic/Module/FireOCLAfterWeaponCooldownUpdate.h" +#include "GameLogic/Module/HijackerUpdate.h" +#include "GameLogic/Module/StructureCollapseUpdate.h" +#include "GameLogic/Module/StructureToppleUpdate.h" +#include "GameLogic/Module/SupplyCenterDockUpdate.h" +#include "GameLogic/Module/SupplyCenterProductionExitUpdate.h" +#include "GameLogic/Module/SupplyTruckAIUpdate.h" +#include "GameLogic/Module/SupplyWarehouseDockUpdate.h" +#include "GameLogic/Module/ToppleUpdate.h" +#include "GameLogic/Module/TransportAIUpdate.h" +#include "GameLogic/Module/WanderAIUpdate.h" +#include "GameLogic/Module/TeleporterAIUpdate.h" +#include "GameLogic/Module/WaveGuideUpdate.h" +#include "GameLogic/Module/WeaponBonusUpdate.h" +#include "GameLogic/Module/ArmorDamageScalarUpdate.h" +#include "GameLogic/Module/WorkerAIUpdate.h" +#include "GameLogic/Module/PowerPlantUpdate.h" +#include "GameLogic/Module/CheckpointUpdate.h" +#include "GameLogic/Module/EMPUpdate.h" + +// upgrade includes +#include "GameLogic/Module/ActiveShroudUpgrade.h" +#include "GameLogic/Module/ArmorUpgrade.h" +#include "GameLogic/Module/CommandSetUpgrade.h" +#include "GameLogic/Module/GrantScienceUpgrade.h" +#include "GameLogic/Module/PassengersFireUpgrade.h" +#include "GameLogic/Module/LocomotorSetUpgrade.h" +#include "GameLogic/Module/ObjectCreationUpgrade.h" +#include "GameLogic/Module/RadarUpgrade.h" +#include "GameLogic/Module/PowerPlantUpgrade.h" +#include "GameLogic/Module/ReplaceObjectUpgrade.h" +#include "GameLogic/Module/ModelConditionUpgrade.h" +#include "GameLogic/Module/StatusBitsUpgrade.h" +#include "GameLogic/Module/SubObjectsUpgrade.h" +#include "GameLogic/Module/StealthUpgrade.h" +#include "GameLogic/Module/UnpauseSpecialPowerUpgrade.h" +#include "GameLogic/Module/WeaponBonusUpgrade.h" +#include "GameLogic/Module/WeaponSetUpgrade.h" +#include "GameLogic/Module/WeaponBonusUpgrade.h" +#include "GameLogic/Module/CostModifierUpgrade.h" +#include "GameLogic/Module/ProductionTimeModifierUpgrade.h" +#include "GameLogic/Module/UnitProductionBonusUpgrade.h" +#include "GameLogic/Module/ExperienceScalarUpgrade.h" +#include "GameLogic/Module/MaxHealthUpgrade.h" + +// create includes +#include "GameLogic/Module/LockWeaponCreate.h" +#include "GameLogic/Module/SupplyCenterCreate.h" +#include "GameLogic/Module/SupplyWarehouseCreate.h" +#include "GameLogic/Module/GrantUpgradeCreate.h" +#include "GameLogic/Module/PreorderCreate.h" +#include "GameLogic/Module/SpecialPowerCreate.h" +#include "GameLogic/Module/VeterancyGainCreate.h" + +// damage includes +#include "GameLogic/Module/BoneFXDamage.h" +#include "GameLogic/Module/TransitionDamageFX.h" + +// collide includes +#include "GameLogic/Module/FireWeaponCollide.h" +#include "GameLogic/Module/SquishCollide.h" + +#include "GameLogic/Module/ConvertToCarBombCrateCollide.h" +#include "GameLogic/Module/ConvertToHijackedVehicleCrateCollide.h" +#include "GameLogic/Module/HealCrateCollide.h" +#include "GameLogic/Module/MoneyCrateCollide.h" +#include "GameLogic/Module/SabotageCommandCenterCrateCollide.h" +#include "GameLogic/Module/SabotageFakeBuildingCrateCollide.h" +#include "GameLogic/Module/SabotageInternetCenterCrateCollide.h" +#include "GameLogic/Module/SabotageMilitaryFactoryCrateCollide.h" +#include "GameLogic/Module/SabotagePowerPlantCrateCollide.h" +#include "GameLogic/Module/SabotageSuperweaponCrateCollide.h" +#include "GameLogic/Module/SabotageSupplyCenterCrateCollide.h" +#include "GameLogic/Module/SabotageSupplyDropzoneCrateCollide.h" +#include "GameLogic/Module/SalvageCrateCollide.h" +#include "GameLogic/Module/ShroudCrateCollide.h" +#include "GameLogic/Module/UnitCrateCollide.h" +#include "GameLogic/Module/VeterancyCrateCollide.h" + +// body includes +#include "GameLogic/Module/InactiveBody.h" +#include "GameLogic/Module/ActiveBody.h" +#include "GameLogic/Module/HighlanderBody.h" +#include "GameLogic/Module/ImmortalBody.h" +#include "GameLogic/Module/StructureBody.h" +#include "GameLogic/Module/HiveStructureBody.h" +#include "GameLogic/Module/UndeadBody.h" + +// contain includes +// (none) + +// special power modules +#include "GameLogic/Module/CashHackSpecialPower.h" +#include "GameLogic/Module/DefectorSpecialPower.h" +#ifdef ALLOW_DEMORALIZE +#include "GameLogic/Module/DemoralizeSpecialPower.h" +#endif +#include "GameLogic/Module/OCLSpecialPower.h" +#include "GameLogic/Module/SpecialAbility.h" +#include "GameLogic/Module/SpyVisionSpecialPower.h" +#include "GameLogic/Module/UpgradeSpecialPower.h" +#include "GameLogic/Module/CashBountyPower.h" +#include "GameLogic/Module/CleanupAreaPower.h" +#include "GameLogic/Module/FireWeaponPower.h" + +// destroy includes +// (none) + +// client update includes +#include "GameClient/Module/AnimatedParticleSysBoneClientUpdate.h" +#include "GameClient/Module/SwayClientUpdate.h" +#include "GameClient/Module/BeaconClientUpdate.h" + +// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// +ModuleFactory *TheModuleFactory = NULL; ///< the module factory singleton + +// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// + +#ifdef RTS_INTERNAL +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ModuleFactory::ModuleFactory( void ) +{ + m_moduleTemplateMap.clear(); + m_moduleDataList.clear(); + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ModuleFactory::~ModuleFactory( void ) +{ + m_moduleTemplateMap.clear(); + + for (ModuleDataList::iterator i = m_moduleDataList.begin(); i != m_moduleDataList.end(); ++i) + { + const ModuleData* data = *i; + delete data; + } + m_moduleDataList.clear(); + +} + +//------------------------------------------------------------------------------------------------- +/** Initialize the module factory. Any class that needs to be attached + * to objects or drawables as modules needs to add a template + * for that class here */ +//------------------------------------------------------------------------------------------------- +void ModuleFactory::init( void ) +{ + + // behavior modules + addModule( AutoHealBehavior ); + addModule( GrantStealthBehavior ); + addModule( NeutronBlastBehavior ); + addModule( BridgeBehavior ); + addModule( BridgeScaffoldBehavior ); + addModule( BridgeTowerBehavior ); + addModule( CountermeasuresBehavior ); + addModule( DumbProjectileBehavior ); + addModule( FreeFallProjectileBehavior ); + addModule( PhysicsBehavior ); + addModule( InstantDeathBehavior ); + addModule( ChronoDeathBehavior ); + addModule( SlowDeathBehavior ); + addModule( HelicopterSlowDeathBehavior ); + addModule( NeutronMissileSlowDeathBehavior ); + addModule( CaveContain ); + addModule( OpenContain ); + addModule( OverchargeBehavior ); + addModule( HealContain ); + addModule( GarrisonContain ); + addModule( InternetHackContain ); + addModule( TransportContain ); + addModule( RiderChangeContain ); + addModule( RailedTransportContain ); + addModule( MobNexusContain ); + addModule( TunnelContain ); + addModule( OverlordContain ); + addModule( HelixContain ); + addModule( ParachuteContain ); +#ifdef ALLOW_SURRENDER + addModule( POWTruckBehavior ); + addModule( PrisonBehavior ); + addModule( PropagandaCenterBehavior ); +#endif + addModule( PropagandaTowerBehavior ); + addModule( BunkerBusterBehavior ); + addModule( FireWeaponWhenDamagedBehavior ); + addModule( FireWeaponWhenDeadBehavior ); + addModule( DelayedUpgradeBehavior ); + addModule( GenerateMinefieldBehavior ); + addModule( ParkingPlaceBehavior ); + addModule( FlightDeckBehavior ); + addModule( PoisonedBehavior ); + addModule( RebuildHoleBehavior ); + addModule( SupplyWarehouseCripplingBehavior ); + addModule( TechBuildingBehavior ); + addModule( MinefieldBehavior ); + addModule( BattleBusSlowDeathBehavior ); + addModule( JetSlowDeathBehavior ); + addModule( RailroadBehavior ); + addModule( SpawnBehavior ); + + // die modules + addModule( DestroyDie ); + addModule( FXListDie ); + addModule( CrushDie ); + addModule( DamDie ); + addModule( CreateCrateDie ); + addModule( CreateObjectDie ); + addModule( EjectPilotDie ); + addModule( SpecialPowerCompletionDie ); + addModule( RebuildHoleExposeDie ); + addModule( UpgradeDie ); + addModule( KeepObjectDie ); + + // update modules + addModule( AssistedTargetingUpdate ); + addModule( AutoFindHealingUpdate ); + addModule( BaseRegenerateUpdate ); + addModule( StealthDetectorUpdate ); + addModule( StealthUpdate ); + addModule( DeletionUpdate ); + addModule( SmartBombTargetHomingUpdate ); + addModule( DynamicShroudClearingRangeUpdate ); + addModule( DeployStyleAIUpdate ); + addModule( AssaultTransportAIUpdate ); + addModule( HordeUpdate ); + addModule( ToppleUpdate ); + addModule( EnemyNearUpdate ); + addModule( LifetimeUpdate ); + addModule( RadiusDecalUpdate ); + addModule( RadiusDecalBehavior ); + addModule( EMPUpdate ); + addModule( LeafletDropBehavior ); + addModule( AutoDepositUpdate ); + addModule( WeaponBonusUpdate ); + addModule( ArmorDamageScalarUpdate ); + addModule( MissileAIUpdate ); + addModule( NeutronMissileUpdate ); + addModule( FireSpreadUpdate ); + addModule( FireWeaponUpdate ); + addModule( FlammableUpdate ); + addModule( FloatUpdate ); + addModule( TensileFormationUpdate ); + addModule( HeightDieUpdate ); + addModule( ScatterShotUpdate ); + addModule( ChinookAIUpdate ); + addModule( JetAIUpdate ); + addModule( AIUpdateInterface ); + addModule( SupplyTruckAIUpdate ); + addModule( DeliverPayloadAIUpdate ); + addModule( HackInternetAIUpdate ); + addModule( DynamicGeometryInfoUpdate ); + addModule( FirestormDynamicGeometryInfoUpdate ); + addModule( LaserUpdate ); + addModule( PointDefenseLaserUpdate ); + addModule( CleanupHazardUpdate ); + addModule( CommandButtonHuntUpdate ); + addModule( PilotFindVehicleUpdate ); + addModule( DemoTrapUpdate ); + addModule( ParticleUplinkCannonUpdate ); + addModule( SpectreGunshipUpdate ); + addModule( SpectreGunshipDeploymentUpdate ); + addModule( BaikonurLaunchPower ); + addModule( BattlePlanUpdate ); + addModule( ProjectileStreamUpdate ); + addModule( QueueProductionExitUpdate ); + addModule( RepairDockUpdate ); +#ifdef ALLOW_SURRENDER + addModule( PrisonDockUpdate ); +#endif + addModule( RailedTransportDockUpdate ); + addModule( DefaultProductionExitUpdate ); + addModule( SpawnPointProductionExitUpdate ); + addModule( SpyVisionUpdate ); + addModule( SlavedUpdate ); + addModule( MobMemberSlavedUpdate ); + addModule( OCLUpdate ); + addModule( SpecialAbilityUpdate ); + addModule( MissileLauncherBuildingUpdate ); + addModule( SupplyCenterProductionExitUpdate ); + addModule( SupplyCenterDockUpdate ); + addModule( SupplyWarehouseDockUpdate ); + addModule( DozerAIUpdate ); +#ifdef ALLOW_SURRENDER + addModule( POWTruckAIUpdate ); +#endif + addModule( RailedTransportAIUpdate ); + addModule( ProductionUpdate ); + addModule( ProneUpdate ); + addModule( StickyBombUpdate ); + addModule( FireOCLAfterWeaponCooldownUpdate ); + addModule( HijackerUpdate ); + addModule( StructureToppleUpdate ); + addModule( StructureCollapseUpdate ); + addModule( BoneFXUpdate ); + addModule( RadarUpdate ); + addModule( AnimationSteeringUpdate ); + addModule( TransportAIUpdate ); + addModule( WanderAIUpdate ); + addModule( TeleporterAIUpdate ); + addModule( WaveGuideUpdate ); + addModule( WorkerAIUpdate ); + addModule( PowerPlantUpdate ); + addModule( CheckpointUpdate ); + + // upgrade modules + addModule( CostModifierUpgrade ); + addModule( ProductionTimeModifierUpgrade ); + addModule( UnitProductionBonusUpgrade ); + addModule( ActiveShroudUpgrade ); + addModule( ArmorUpgrade ); + addModule( CommandSetUpgrade ); + addModule( GrantScienceUpgrade ); + addModule( PassengersFireUpgrade ); + addModule( StatusBitsUpgrade ); + addModule( SubObjectsUpgrade ); + addModule( StealthUpgrade ); + addModule( RadarUpgrade ); + addModule( PowerPlantUpgrade ); + addModule( LocomotorSetUpgrade ); + addModule( ObjectCreationUpgrade ); + addModule( ReplaceObjectUpgrade ); + addModule( ModelConditionUpgrade ); + addModule( UnpauseSpecialPowerUpgrade ); + addModule( WeaponBonusUpgrade ); + addModule( WeaponSetUpgrade ); + addModule( WeaponBonusUpgrade ); + addModule( ExperienceScalarUpgrade ); + addModule( MaxHealthUpgrade ); + + // create modules + addModule( LockWeaponCreate ); + addModule( PreorderCreate ); + addModule( SupplyCenterCreate ); + addModule( SupplyWarehouseCreate ); + addModule( SpecialPowerCreate ); + addModule( GrantUpgradeCreate ); + addModule( VeterancyGainCreate ); + + // damage modules + addModule( BoneFXDamage ); + addModule( TransitionDamageFX ); + + // collide modules + addModule( FireWeaponCollide ); + addModule( SquishCollide ); + + addModule( HealCrateCollide ); + addModule( MoneyCrateCollide ); + addModule( ShroudCrateCollide ); + addModule( UnitCrateCollide ); + addModule( VeterancyCrateCollide ); + addModule( ConvertToCarBombCrateCollide ); + addModule( ConvertToHijackedVehicleCrateCollide ); + addModule( SabotageCommandCenterCrateCollide ); + addModule( SabotageFakeBuildingCrateCollide ); + addModule( SabotageInternetCenterCrateCollide ); + addModule( SabotageMilitaryFactoryCrateCollide ); + addModule( SabotagePowerPlantCrateCollide ); + addModule( SabotageSuperweaponCrateCollide ); + addModule( SabotageSupplyCenterCrateCollide ); + addModule( SabotageSupplyDropzoneCrateCollide ); + addModule( SalvageCrateCollide ); + + // body modules + addModule( InactiveBody ); + addModule( ActiveBody ); + addModule( HighlanderBody ); + addModule( ImmortalBody ); + addModule( StructureBody ); + addModule( HiveStructureBody ); + addModule( UndeadBody ); + + // contain modules + // (none) + + // special power modules + addModule( CashHackSpecialPower ); + addModule( DefectorSpecialPower ); +#ifdef ALLOW_DEMORALIZE + addModule( DemoralizeSpecialPower ); +#endif + addModule( OCLSpecialPower ); + addModule( FireWeaponPower ); + addModule( SpecialAbility ); + addModule( SpyVisionSpecialPower ); + addModule( UpgradeSpecialPower ); + addModule( CashBountyPower ); + addModule( CleanupAreaPower ); + + // destroy modules + // (none) + + // client update modules + addModule( AnimatedParticleSysBoneClientUpdate ); + addModule( SwayClientUpdate ); + addModule( BeaconClientUpdate ); + +} // end init + +//------------------------------------------------------------------------------------------------- +Int ModuleFactory::findModuleInterfaceMask(const AsciiString& name, ModuleType type) +{ + if (name.isEmpty()) + return 0; + + const ModuleTemplate* moduleTemplate = findModuleTemplate(name, type); + if (moduleTemplate) + { + return moduleTemplate->m_whichInterfaces; + } + + return 0; +} + +//------------------------------------------------------------------------------------------------- +ModuleData* ModuleFactory::newModuleDataFromINI(INI* ini, const AsciiString& name, ModuleType type, + const AsciiString& moduleTag) +{ + if (name.isEmpty()) + return NULL; + + const ModuleTemplate* moduleTemplate = findModuleTemplate(name, type); + if (moduleTemplate) + { + ModuleData* md = (*moduleTemplate->m_createDataProc)(ini); + md->setModuleTagNameKey( NAMEKEY( moduleTag ) ); + m_moduleDataList.push_back(md); + return md; + } + + return NULL; +} + +// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +/*static*/ NameKeyType ModuleFactory::makeDecoratedNameKey(const AsciiString& name, ModuleType type) +{ + char tmp[256]; + tmp[0] = '0' + (int)type; + strcpy(&tmp[1], name.str()); + return TheNameKeyGenerator->nameToKey(tmp); +} + +//------------------------------------------------------------------------------------------------- +const ModuleFactory::ModuleTemplate* ModuleFactory::findModuleTemplate(const AsciiString& name, ModuleType type) +{ + NameKeyType namekey = makeDecoratedNameKey(name, type); + + ModuleTemplateMap::const_iterator it = m_moduleTemplateMap.find(namekey); + if (it == m_moduleTemplateMap.end()) + { + DEBUG_CRASH(( "Module name '%s' not found\n", name.str() )); + return NULL; + } + else + { + return &(*it).second; + } +} + +//------------------------------------------------------------------------------------------------- +/** Allocate a new acton class istance given the name */ +//------------------------------------------------------------------------------------------------- +Module *ModuleFactory::newModule( Thing *thing, const AsciiString& name, const ModuleData* moduleData, ModuleType type ) +{ + // sanity + if( name.isEmpty() ) + { + DEBUG_CRASH(("attempting to create module with empty name\n")); + return NULL; + } + const ModuleTemplate* mt = findModuleTemplate(name, type); + if (mt) + { + Module* mod = (*mt->m_createProc)( thing, moduleData ); + +#ifdef DEBUG_CRASHING + if (type == MODULETYPE_BEHAVIOR) + { + BehaviorModule* bm = (BehaviorModule*)mod; + + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_BODY)) != 0) == (bm->getBody() != NULL), + ("getInterfaceMask bad for MODULE_BODY (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_COLLIDE)) != 0) == (bm->getCollide() != NULL), + ("getInterfaceMask bad for MODULE_COLLIDE (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_CONTAIN)) != 0) == (bm->getContain() != NULL), + ("getInterfaceMask bad for MODULE_CONTAIN (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_CREATE)) != 0) == (bm->getCreate() != NULL), + ("getInterfaceMask bad for MODULE_CREATE (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_DAMAGE)) != 0) == (bm->getDamage() != NULL), + ("getInterfaceMask bad for MODULE_DAMAGE (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_DESTROY)) != 0) == (bm->getDestroy() != NULL), + ("getInterfaceMask bad for MODULE_DESTROY (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_DIE)) != 0) == (bm->getDie() != NULL), + ("getInterfaceMask bad for MODULE_DIE (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_SPECIAL_POWER)) != 0) == (bm->getSpecialPower() != NULL), + ("getInterfaceMask bad for MODULE_SPECIAL_POWER (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_UPDATE)) != 0) == (bm->getUpdate() != NULL), + ("getInterfaceMask bad for MODULE_UPDATE (%s)\n",name.str())); + DEBUG_ASSERTCRASH( + ((mt->m_whichInterfaces & (MODULEINTERFACE_UPGRADE)) != 0) == (bm->getUpgrade() != NULL), + ("getInterfaceMask bad for MODULE_UPGRADE (%s)\n",name.str())); + } +#endif + + return mod; + } + + return NULL; + +} // end newModule + +//------------------------------------------------------------------------------------------------- +/** Add a module template to our list of templates */ +//------------------------------------------------------------------------------------------------- +void ModuleFactory::addModuleInternal( NewModuleProc proc, NewModuleDataProc dataproc, ModuleType type, const AsciiString& name, Int whichIntf ) +{ + NameKeyType namekey = makeDecoratedNameKey(name, type); + ModuleTemplate& mtm = m_moduleTemplateMap[namekey]; // this creates it if it does not exist already + mtm.m_createProc = proc; + mtm.m_createDataProc = dataproc; + mtm.m_whichInterfaces = whichIntf; +} + +//------------------------------------------------------------------------------------------------- +void ModuleFactory::crc( Xfer *xfer ) +{ + for (ModuleDataList::iterator mdIt = m_moduleDataList.begin(); mdIt != m_moduleDataList.end(); ++mdIt) + { + ((ModuleData *)(*mdIt))->crc(xfer); + } +} + +//------------------------------------------------------------------------------------------------- +void ModuleFactory::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + for (ModuleDataList::iterator mdIt = m_moduleDataList.begin(); mdIt != m_moduleDataList.end(); ++mdIt) + { + ((ModuleData *)(*mdIt))->xfer(xfer); + } +} + +//------------------------------------------------------------------------------------------------- +void ModuleFactory::loadPostProcess( void ) +{ +} diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Armor.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Armor.cpp index cd6736ed53b..5d1fb7cb818 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Armor.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Armor.cpp @@ -1,192 +1,192 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: ArmorTemplate.cpp /////////////////////////////////////////////////////////////////////////////// -// Author: Steven Johnson, November 2001 -// Desc: ArmorTemplate descriptions -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - - -#include "Common/INI.h" -#include "Common/ThingFactory.h" -#include "GameLogic/Armor.h" -#include "GameLogic/Damage.h" - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -ArmorStore* TheArmorStore = NULL; ///< the ArmorTemplate store definition - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------------- -ArmorTemplate::ArmorTemplate() -{ - clear(); -} - -//------------------------------------------------------------------------------------------------- -void ArmorTemplate::clear() -{ - for (int i = 0; i < DAMAGE_NUM_TYPES; i++) - { - m_damageCoefficient[i] = 1.0f; - } -} - -void ArmorTemplate::copyFrom(const ArmorTemplate* other) { - for (int i = 0; i < DAMAGE_NUM_TYPES; i++) - { - m_damageCoefficient[i] = other->m_damageCoefficient[i]; - } -} - -//------------------------------------------------------------------------------------------------- -Real ArmorTemplate::adjustDamage(DamageType t, Real damage) const -{ - if (t == DAMAGE_UNRESISTABLE) - return damage; - if (t == DAMAGE_SUBDUAL_UNRESISTABLE) - return damage; - if (t == DAMAGE_CHRONO_UNRESISTABLE) - return damage; - - damage *= m_damageCoefficient[t]; - - if (damage < 0.0f) - damage = 0.0f; - - return damage; -} - -//-------------------------------------------------------------------------------------------Static -/*static*/ void ArmorTemplate::parseArmorCoefficients( INI* ini, void *instance, void* /* store */, const void* userData ) -{ - ArmorTemplate* self = (ArmorTemplate*) instance; - - const char* damageName = ini->getNextToken(); - Real pct = INI::scanPercentToReal(ini->getNextToken()); - - if (stricmp(damageName, "Default") == 0) - { - for (Int i = 0; i < DAMAGE_NUM_TYPES; i++) - { - self->m_damageCoefficient[i] = pct; - } - return; - } - - DamageType dt = (DamageType)DamageTypeFlags::getSingleBitFromName(damageName); - self->m_damageCoefficient[dt] = pct; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -ArmorStore::ArmorStore() -{ - m_armorTemplates.clear(); -} - -//------------------------------------------------------------------------------------------------- -ArmorStore::~ArmorStore() -{ - m_armorTemplates.clear(); -} - -//------------------------------------------------------------------------------------------------- -const ArmorTemplate* ArmorStore::findArmorTemplate(AsciiString name) const -{ - NameKeyType namekey = TheNameKeyGenerator->nameToKey(name); - ArmorTemplateMap::const_iterator it = m_armorTemplates.find(namekey); - if (it == m_armorTemplates.end()) - { - return NULL; - } - else - { - return &(*it).second; - } -} - -//------------------------------------------------------------------------------------------------- -/*static */ void ArmorStore::parseArmorDefinition(INI *ini) -{ - static const FieldParse myFieldParse[] = - { - { "Armor", ArmorTemplate::parseArmorCoefficients, NULL, 0 } - }; - - const char *c = ini->getNextToken(); - NameKeyType key = TheNameKeyGenerator->nameToKey(c); - ArmorTemplate& armorTmpl = TheArmorStore->m_armorTemplates[key]; - armorTmpl.clear(); - ini->initFromINI(&armorTmpl, myFieldParse); -} - -//------------------------------------------------------------------------------------------------- -/*static */ void ArmorStore::parseArmorExtendDefinition(INI* ini) -{ - static const FieldParse myFieldParse[] = - { - { "Armor", ArmorTemplate::parseArmorCoefficients, NULL, 0 } - }; - - const char* new_armor_name = ini->getNextToken(); - - const char* parent = ini->getNextToken(); - const ArmorTemplate* parentTemplate = TheArmorStore->findArmorTemplate(parent); - if (parentTemplate == NULL) { - DEBUG_CRASH(("ArmorExtend must extend a previously defined Armor (%s).\n", parent)); - throw INI_INVALID_DATA; - } - - NameKeyType key = TheNameKeyGenerator->nameToKey(new_armor_name); - ArmorTemplate& armorTmpl = TheArmorStore->m_armorTemplates[key]; - armorTmpl.clear(); - armorTmpl.copyFrom(parentTemplate); - - ini->initFromINI(&armorTmpl, myFieldParse); -} - -//------------------------------------------------------------------------------------------------- -/*static*/ void INI::parseArmorDefinition(INI *ini) -{ - ArmorStore::parseArmorDefinition(ini); -} - -//------------------------------------------------------------------------------------------------- -/*static*/ void INI::parseArmorExtendDefinition(INI* ini) -{ - ArmorStore::parseArmorExtendDefinition(ini); -} +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: ArmorTemplate.cpp /////////////////////////////////////////////////////////////////////////////// +// Author: Steven Johnson, November 2001 +// Desc: ArmorTemplate descriptions +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + + +#include "Common/INI.h" +#include "Common/ThingFactory.h" +#include "GameLogic/Armor.h" +#include "GameLogic/Damage.h" + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +ArmorStore* TheArmorStore = NULL; ///< the ArmorTemplate store definition + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +ArmorTemplate::ArmorTemplate() +{ + clear(); +} + +//------------------------------------------------------------------------------------------------- +void ArmorTemplate::clear() +{ + for (int i = 0; i < DAMAGE_NUM_TYPES; i++) + { + m_damageCoefficient[i] = 1.0f; + } +} + +void ArmorTemplate::copyFrom(const ArmorTemplate* other) { + for (int i = 0; i < DAMAGE_NUM_TYPES; i++) + { + m_damageCoefficient[i] = other->m_damageCoefficient[i]; + } +} + +//------------------------------------------------------------------------------------------------- +Real ArmorTemplate::adjustDamage(DamageType t, Real damage) const +{ + if (t == DAMAGE_UNRESISTABLE) + return damage; + if (t == DAMAGE_SUBDUAL_UNRESISTABLE) + return damage; + if (t == DAMAGE_CHRONO_UNRESISTABLE) + return damage; + + damage *= m_damageCoefficient[t]; + + if (damage < 0.0f) + damage = 0.0f; + + return damage; +} + +//-------------------------------------------------------------------------------------------Static +/*static*/ void ArmorTemplate::parseArmorCoefficients( INI* ini, void *instance, void* /* store */, const void* userData ) +{ + ArmorTemplate* self = (ArmorTemplate*) instance; + + const char* damageName = ini->getNextToken(); + Real pct = INI::scanPercentToReal(ini->getNextToken()); + + if (stricmp(damageName, "Default") == 0) + { + for (Int i = 0; i < DAMAGE_NUM_TYPES; i++) + { + self->m_damageCoefficient[i] = pct; + } + return; + } + + DamageType dt = (DamageType)DamageTypeFlags::getSingleBitFromName(damageName); + self->m_damageCoefficient[dt] = pct; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ArmorStore::ArmorStore() +{ + m_armorTemplates.clear(); +} + +//------------------------------------------------------------------------------------------------- +ArmorStore::~ArmorStore() +{ + m_armorTemplates.clear(); +} + +//------------------------------------------------------------------------------------------------- +const ArmorTemplate* ArmorStore::findArmorTemplate(AsciiString name) const +{ + NameKeyType namekey = TheNameKeyGenerator->nameToKey(name); + ArmorTemplateMap::const_iterator it = m_armorTemplates.find(namekey); + if (it == m_armorTemplates.end()) + { + return NULL; + } + else + { + return &(*it).second; + } +} + +//------------------------------------------------------------------------------------------------- +/*static */ void ArmorStore::parseArmorDefinition(INI *ini) +{ + static const FieldParse myFieldParse[] = + { + { "Armor", ArmorTemplate::parseArmorCoefficients, NULL, 0 } + }; + + const char *c = ini->getNextToken(); + NameKeyType key = TheNameKeyGenerator->nameToKey(c); + ArmorTemplate& armorTmpl = TheArmorStore->m_armorTemplates[key]; + armorTmpl.clear(); + ini->initFromINI(&armorTmpl, myFieldParse); +} + +//------------------------------------------------------------------------------------------------- +/*static */ void ArmorStore::parseArmorExtendDefinition(INI* ini) +{ + static const FieldParse myFieldParse[] = + { + { "Armor", ArmorTemplate::parseArmorCoefficients, NULL, 0 } + }; + + const char* new_armor_name = ini->getNextToken(); + + const char* parent = ini->getNextToken(); + const ArmorTemplate* parentTemplate = TheArmorStore->findArmorTemplate(parent); + if (parentTemplate == NULL) { + DEBUG_CRASH(("ArmorExtend must extend a previously defined Armor (%s).\n", parent)); + throw INI_INVALID_DATA; + } + + NameKeyType key = TheNameKeyGenerator->nameToKey(new_armor_name); + ArmorTemplate& armorTmpl = TheArmorStore->m_armorTemplates[key]; + armorTmpl.clear(); + armorTmpl.copyFrom(parentTemplate); + + ini->initFromINI(&armorTmpl, myFieldParse); +} + +//------------------------------------------------------------------------------------------------- +/*static*/ void INI::parseArmorDefinition(INI *ini) +{ + ArmorStore::parseArmorDefinition(ini); +} + +//------------------------------------------------------------------------------------------------- +/*static*/ void INI::parseArmorExtendDefinition(INI* ini) +{ + ArmorStore::parseArmorExtendDefinition(ini); +} diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DelayedUpgradeBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DelayedUpgradeBehavior.cpp index f56b9c3486e..f71e31f8290 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DelayedUpgradeBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DelayedUpgradeBehavior.cpp @@ -1,248 +1,248 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: DelayedUpgradeBehavior.cpp /////////////////////////////////////////////////////////////////////// -// Author: -// Desc: -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - - -//#include "Common/Thing.h" -//#include "Common/ThingTemplate.h" -#include "Common/INI.h" -//#include "Common/RandomValue.h" -#include "Common/Xfer.h" -#include "Common/Player.h" -//#include "GameClient/Drawable.h" -//#include "GameClient/FXList.h" -//#include "GameClient/InGameUI.h" -#include "GameLogic/GameLogic.h" -//#include "GameLogic/Module/BodyModule.h" -#include "GameLogic/Module/DelayedUpgradeBehavior.h" -#include "GameLogic/Module/AIUpdate.h" -#include "GameLogic/Object.h" -//#include "GameLogic/ObjectCreationList.h" -#include "GameLogic/Weapon.h" -//#include "GameClient/Drawable.h" - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -DelayedUpgradeBehavior::DelayedUpgradeBehavior(Thing* thing, const ModuleData* moduleData) : UpdateModule(thing, moduleData) -{ - DEBUG_LOG(("DelayedUpgradeBehavior::INIT\n")); - m_triggerCompleted = FALSE; - m_triggerFrame = 0; - //m_shotsLeft = 0; - - if (getDelayedUpgradeBehaviorModuleData()->m_initiallyActive) - { - giveSelfUpgrade(); - } - else { - setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -DelayedUpgradeBehavior::~DelayedUpgradeBehavior(void) -{ -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void DelayedUpgradeBehavior::upgradeImplementation(void) -{ - DEBUG_LOG(("DelayedUpgradeBehavior::upgradeImplementation() 1\n")); - - const DelayedUpgradeBehaviorModuleData* d = getDelayedUpgradeBehaviorModuleData(); - - UnsignedInt delay = d->m_triggerDelay; - // Trigger after time: - if (delay > 0) { - m_triggerFrame = TheGameLogic->getFrame() + delay; - } - - //if (d->m_triggerNumShots > 0) { - // m_shotsLeft = d->m_triggerNumShots; - // setWakeFrame(getObject(), UPDATE_SLEEP_NONE); - // return; - //} - - if (delay > 0) { - - DEBUG_LOG(("DelayedUpgradeBehavior::upgradeImplementation(): trigger_frame = %d\n", m_triggerFrame)); - - setWakeFrame(getObject(), UPDATE_SLEEP(d->m_triggerDelay)); - return; - } - - DEBUG_LOG(("DelayedUpgradeBehavior::upgradeImplementation(): We have no trigger!!!\n")); - -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -UpdateSleepTime DelayedUpgradeBehavior::update(void) -{ - if (m_triggerCompleted) { - DEBUG_LOG(("DelayedUpgradeBehavior::Update(): Already triggered. We should not be awake!!!\n")); - return UPDATE_SLEEP_FOREVER; - } - - if (!isUpgradeActive()) { - DEBUG_LOG(("DelayedUpgradeBehavior::Update(): Upgrade not applied. We should not be awake!!!\n")); - return UPDATE_SLEEP_FOREVER; - } - - const DelayedUpgradeBehaviorModuleData* d = getDelayedUpgradeBehaviorModuleData(); - - if (d->m_triggerDelay > 0) { - UnsignedInt now = TheGameLogic->getFrame(); - if (now >= m_triggerFrame) { - DEBUG_LOG(("DelayedUpgradeBehavior::update(): Trigger Frame reached.\n")); - triggerUpgrade(); - return UPDATE_SLEEP_FOREVER; - } - } - - //if (d->m_triggerNumShots > 0) { - - // //checkShots(); - // if (m_shotsLeft >= 0) { - // triggerUpgrade(); - // return UPDATE_SLEEP_FOREVER; - // } - //} - - return UPDATE_SLEEP_NONE; -} - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void DelayedUpgradeBehavior::triggerUpgrade(void) -{ - - const DelayedUpgradeBehaviorModuleData* d = getDelayedUpgradeBehaviorModuleData(); - const UpgradeTemplate* upgradeTemplate = TheUpgradeCenter->findUpgrade(d->m_upgradeToTrigger); - if (!upgradeTemplate) - { - DEBUG_ASSERTCRASH(0, ("DelayedUpgradeBehavior for %s can't find upgrade template %s.", getObject()->getName(), d->m_upgradeToTrigger)); - return; - } - - m_triggerCompleted = TRUE; - - Player* player = getObject()->getControllingPlayer(); - if (upgradeTemplate->getUpgradeType() == UPGRADE_TYPE_PLAYER) - { - // get the player - player->addUpgrade(upgradeTemplate, UPGRADE_STATUS_COMPLETE); - } - else - { - getObject()->giveUpgrade(upgradeTemplate); - } - - player->getAcademyStats()->recordUpgrade(upgradeTemplate, TRUE); - - DEBUG_LOG(("DelayedUpgradeBehavior::triggerUpgrade() Done.\n")); - -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Bool DelayedUpgradeBehavior::resetUpgrade(UpgradeMaskType keyMask) -{ - DEBUG_LOG(("DelayedUpgradeBehavior::resetUpgrade().\n")); - if (UpgradeMux::resetUpgrade(keyMask)) { - m_triggerCompleted = FALSE; - m_triggerFrame = 0; - // m_shotsLeft = 0; - return TRUE; - } - else { - return FALSE; - } -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void DelayedUpgradeBehavior::crc(Xfer* xfer) -{ - - // extend base class - BehaviorModule::crc(xfer); - - // extend upgrade mux - UpgradeMux::upgradeMuxCRC(xfer); - -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ - // ------------------------------------------------------------------------------------------------ -void DelayedUpgradeBehavior::xfer(Xfer* xfer) -{ - - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion(&version, currentVersion); - - // extend base class - BehaviorModule::xfer(xfer); - - // extend upgrade mux - UpgradeMux::upgradeMuxXfer(xfer); - - // trigger frame - xfer->xferUnsignedInt(&m_triggerFrame); - - // trigger completed - xfer->xferBool(&m_triggerCompleted); - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void DelayedUpgradeBehavior::loadPostProcess(void) -{ - - // extend base class - BehaviorModule::loadPostProcess(); - - // extend upgrade mux - UpgradeMux::upgradeMuxLoadPostProcess(); - -} // end loadPostProcess +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: DelayedUpgradeBehavior.cpp /////////////////////////////////////////////////////////////////////// +// Author: +// Desc: +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + + +//#include "Common/Thing.h" +//#include "Common/ThingTemplate.h" +#include "Common/INI.h" +//#include "Common/RandomValue.h" +#include "Common/Xfer.h" +#include "Common/Player.h" +//#include "GameClient/Drawable.h" +//#include "GameClient/FXList.h" +//#include "GameClient/InGameUI.h" +#include "GameLogic/GameLogic.h" +//#include "GameLogic/Module/BodyModule.h" +#include "GameLogic/Module/DelayedUpgradeBehavior.h" +#include "GameLogic/Module/AIUpdate.h" +#include "GameLogic/Object.h" +//#include "GameLogic/ObjectCreationList.h" +#include "GameLogic/Weapon.h" +//#include "GameClient/Drawable.h" + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +DelayedUpgradeBehavior::DelayedUpgradeBehavior(Thing* thing, const ModuleData* moduleData) : UpdateModule(thing, moduleData) +{ + DEBUG_LOG(("DelayedUpgradeBehavior::INIT\n")); + m_triggerCompleted = FALSE; + m_triggerFrame = 0; + //m_shotsLeft = 0; + + if (getDelayedUpgradeBehaviorModuleData()->m_initiallyActive) + { + giveSelfUpgrade(); + } + else { + setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +DelayedUpgradeBehavior::~DelayedUpgradeBehavior(void) +{ +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void DelayedUpgradeBehavior::upgradeImplementation(void) +{ + DEBUG_LOG(("DelayedUpgradeBehavior::upgradeImplementation() 1\n")); + + const DelayedUpgradeBehaviorModuleData* d = getDelayedUpgradeBehaviorModuleData(); + + UnsignedInt delay = d->m_triggerDelay; + // Trigger after time: + if (delay > 0) { + m_triggerFrame = TheGameLogic->getFrame() + delay; + } + + //if (d->m_triggerNumShots > 0) { + // m_shotsLeft = d->m_triggerNumShots; + // setWakeFrame(getObject(), UPDATE_SLEEP_NONE); + // return; + //} + + if (delay > 0) { + + DEBUG_LOG(("DelayedUpgradeBehavior::upgradeImplementation(): trigger_frame = %d\n", m_triggerFrame)); + + setWakeFrame(getObject(), UPDATE_SLEEP(d->m_triggerDelay)); + return; + } + + DEBUG_LOG(("DelayedUpgradeBehavior::upgradeImplementation(): We have no trigger!!!\n")); + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +UpdateSleepTime DelayedUpgradeBehavior::update(void) +{ + if (m_triggerCompleted) { + DEBUG_LOG(("DelayedUpgradeBehavior::Update(): Already triggered. We should not be awake!!!\n")); + return UPDATE_SLEEP_FOREVER; + } + + if (!isUpgradeActive()) { + DEBUG_LOG(("DelayedUpgradeBehavior::Update(): Upgrade not applied. We should not be awake!!!\n")); + return UPDATE_SLEEP_FOREVER; + } + + const DelayedUpgradeBehaviorModuleData* d = getDelayedUpgradeBehaviorModuleData(); + + if (d->m_triggerDelay > 0) { + UnsignedInt now = TheGameLogic->getFrame(); + if (now >= m_triggerFrame) { + DEBUG_LOG(("DelayedUpgradeBehavior::update(): Trigger Frame reached.\n")); + triggerUpgrade(); + return UPDATE_SLEEP_FOREVER; + } + } + + //if (d->m_triggerNumShots > 0) { + + // //checkShots(); + // if (m_shotsLeft >= 0) { + // triggerUpgrade(); + // return UPDATE_SLEEP_FOREVER; + // } + //} + + return UPDATE_SLEEP_NONE; +} + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void DelayedUpgradeBehavior::triggerUpgrade(void) +{ + + const DelayedUpgradeBehaviorModuleData* d = getDelayedUpgradeBehaviorModuleData(); + const UpgradeTemplate* upgradeTemplate = TheUpgradeCenter->findUpgrade(d->m_upgradeToTrigger); + if (!upgradeTemplate) + { + DEBUG_ASSERTCRASH(0, ("DelayedUpgradeBehavior for %s can't find upgrade template %s.", getObject()->getName(), d->m_upgradeToTrigger)); + return; + } + + m_triggerCompleted = TRUE; + + Player* player = getObject()->getControllingPlayer(); + if (upgradeTemplate->getUpgradeType() == UPGRADE_TYPE_PLAYER) + { + // get the player + player->addUpgrade(upgradeTemplate, UPGRADE_STATUS_COMPLETE); + } + else + { + getObject()->giveUpgrade(upgradeTemplate); + } + + player->getAcademyStats()->recordUpgrade(upgradeTemplate, TRUE); + + DEBUG_LOG(("DelayedUpgradeBehavior::triggerUpgrade() Done.\n")); + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool DelayedUpgradeBehavior::resetUpgrade(UpgradeMaskType keyMask) +{ + DEBUG_LOG(("DelayedUpgradeBehavior::resetUpgrade().\n")); + if (UpgradeMux::resetUpgrade(keyMask)) { + m_triggerCompleted = FALSE; + m_triggerFrame = 0; + // m_shotsLeft = 0; + return TRUE; + } + else { + return FALSE; + } +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void DelayedUpgradeBehavior::crc(Xfer* xfer) +{ + + // extend base class + BehaviorModule::crc(xfer); + + // extend upgrade mux + UpgradeMux::upgradeMuxCRC(xfer); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ + // ------------------------------------------------------------------------------------------------ +void DelayedUpgradeBehavior::xfer(Xfer* xfer) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion(&version, currentVersion); + + // extend base class + BehaviorModule::xfer(xfer); + + // extend upgrade mux + UpgradeMux::upgradeMuxXfer(xfer); + + // trigger frame + xfer->xferUnsignedInt(&m_triggerFrame); + + // trigger completed + xfer->xferBool(&m_triggerCompleted); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void DelayedUpgradeBehavior::loadPostProcess(void) +{ + + // extend base class + BehaviorModule::loadPostProcess(); + + // extend upgrade mux + UpgradeMux::upgradeMuxLoadPostProcess(); + +} // end loadPostProcess diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp index 2e48cbe1e02..bff18be663f 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp @@ -1,1866 +1,1866 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: ActiveBody.cpp /////////////////////////////////////////////////////////////////////////// -// Author: Colin Day, November 2001 -// Desc: Active bodies have health, they can die and are affected by health -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine -#include "Common/BitFlagsIO.h" -#include "Common/CRCDebug.h" -#include "Common/DamageFX.h" -#include "Common/Player.h" -#include "Common/GameState.h" -#include "Common/GlobalData.h" -#include "Common/PlayerList.h" -#include "Common/Team.h" -#include "Common/Thing.h" -#include "Common/ThingTemplate.h" -#include "Common/Xfer.h" -#include "GameClient/ControlBar.h" -#include "GameClient/Drawable.h" -#include "GameClient/InGameUI.h" -#include "GameClient/ParticleSys.h" -#include "GameLogic/AI.h" -#include "GameLogic/AIPathfind.h" -#include "GameLogic/Armor.h" -#include "GameLogic/GameLogic.h" -#include "GameLogic/Object.h" -#include "GameLogic/Damage.h" -#include "GameLogic/PartitionManager.h" -#include "GameLogic/TerrainLogic.h" -#include "GameLogic/Weapon.h" -#include "GameLogic/Module/AIUpdate.h" -#include "GameLogic/Module/ActiveBody.h" -#include "GameLogic/Module/BridgeBehavior.h" -#include "GameLogic/Module/ContainModule.h" -#include "GameLogic/Module/DamageModule.h" -#include "GameLogic/Module/DieModule.h" - - -#ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -#define YELLOW_DAMAGE_PERCENT (0.25f) - -// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// - -// ------------------------------------------------------------------------------------------------ -/** Body particle systems are particle systems that are automatically created and attached - * to an object as the damage state changes for that object. We keep a list of these - * so that when we transition from one state to another we can kill any old particle - * systems that we need to before we create new ones */ -// ------------------------------------------------------------------------------------------------ -class BodyParticleSystem : public MemoryPoolObject -{ - - MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( BodyParticleSystem, "BodyParticleSystem" ) - -public: - - ParticleSystemID m_particleSystemID; ///< the particle system ID - BodyParticleSystem *m_next; ///< next particle system in this body module - -}; - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -BodyParticleSystem::~BodyParticleSystem( void ) -{ - -} // end ~BodyParticleSystem - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------------- -// ------------------------------------------------------------------------------------------------ -static BodyDamageType calcDamageState(Real health, Real maxHealth) -{ - if (!TheGlobalData) - return BODY_PRISTINE; - - Real ratio = health / maxHealth; - - if (ratio > TheGlobalData->m_unitDamagedThresh) - { - return BODY_PRISTINE; - } - else if (ratio > TheGlobalData->m_unitReallyDamagedThresh) - { - return BODY_DAMAGED; - } - else if (ratio > 0.0f) - { - return BODY_REALLYDAMAGED; - } - else - { - return BODY_RUBBLE; - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -ActiveBodyModuleData::ActiveBodyModuleData() -{ - m_maxHealth = 0; - m_initialHealth = 0; - m_subdualDamageCap = 0; - m_subdualDamageHealRate = 0; - m_subdualDamageHealAmount = 0; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBodyModuleData::buildFieldParse(MultiIniFieldParse& p) -{ - ModuleData::buildFieldParse(p); - - static const FieldParse dataFieldParse[] = - { - { "MaxHealth", INI::parseReal, NULL, offsetof( ActiveBodyModuleData, m_maxHealth ) }, - { "InitialHealth", INI::parseReal, NULL, offsetof( ActiveBodyModuleData, m_initialHealth ) }, - - { "SubdualDamageCap", INI::parseReal, NULL, offsetof( ActiveBodyModuleData, m_subdualDamageCap ) }, - { "SubdualDamageHealRate", INI::parseDurationUnsignedInt, NULL, offsetof( ActiveBodyModuleData, m_subdualDamageHealRate ) }, - { "SubdualDamageHealAmount", INI::parseReal, NULL, offsetof( ActiveBodyModuleData, m_subdualDamageHealAmount ) }, - { 0, 0, 0, 0 } - }; - p.add(dataFieldParse); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -ActiveBody::ActiveBody( Thing *thing, const ModuleData* moduleData ) : - BodyModule(thing, moduleData), - m_curDamageFX(NULL), - m_curArmorSet(NULL), - m_frontCrushed(false), - m_backCrushed(false), - m_lastDamageTimestamp(0xffffffff),// So we don't think we just got damaged on the first frame - m_lastHealingTimestamp(0xffffffff),// So we don't think we just got healed on the first frame - m_curDamageState(BODY_PRISTINE), - m_nextDamageFXTime(0), - m_lastDamageFXDone((DamageType)-1), - m_lastDamageCleared(false), - m_particleSystems(NULL), - m_currentSubdualDamage(0), - m_indestructible(false), - m_damageFXOverride(false) -{ - m_currentHealth = getActiveBodyModuleData()->m_initialHealth; - m_prevHealth = getActiveBodyModuleData()->m_initialHealth; - m_maxHealth = getActiveBodyModuleData()->m_maxHealth; - m_initialHealth = getActiveBodyModuleData()->m_initialHealth; - - // force an initially-valid armor setup - validateArmorAndDamageFX(); - // start us in the right state - setCorrectDamageState(); - -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -ActiveBody::~ActiveBody( void ) -{ -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::onDelete( void ) -{ - - // delete all particle systems - deleteAllParticleSystems(); - -} // end onDelete - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBody::setCorrectDamageState() -{ - m_curDamageState = calcDamageState(m_currentHealth, m_maxHealth); - - /// @todo srj -- bleah, this is an icky way to do it. oh well. - if (m_curDamageState == BODY_RUBBLE && getObject()->isKindOf(KINDOF_STRUCTURE)) - { - Real rubbleHeight = getObject()->getTemplate()->getStructureRubbleHeight(); - - if (rubbleHeight <= 0.0f) - rubbleHeight = TheGlobalData->m_defaultStructureRubbleHeight; - - /** @todo I had to change this to a Z only version to keep it from disappearing from the - PartitionManager for a frame. That didn't used to happen. - */ - getObject()->setGeometryInfoZ(rubbleHeight); - - // Have to tell pathfind as well, as rubble pathfinds differently. - TheAI->pathfinder()->removeObjectFromPathfindMap(getObject()); - TheAI->pathfinder()->addObjectToPathfindMap(getObject()); - - - // here we make sure nobody collides with us, ever again... //Lorenzen - //THis allows projectiles shot from infantry that are inside rubble to get out of said rubble safely - getObject()->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_COLLISIONS ) ); - - - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBody::setDamageState( BodyDamageType newState ) -{ - Real ratio = 1.0f; - if( newState == BODY_PRISTINE ) - { - ratio = 1.0f; - } - else if( newState == BODY_DAMAGED ) - { - ratio = TheGlobalData->m_unitDamagedThresh; - } - else if( newState == BODY_REALLYDAMAGED ) - { - ratio = TheGlobalData->m_unitReallyDamagedThresh; - } - else if( newState == BODY_RUBBLE ) - { - ratio = 0.0f; - } - Real desiredHealth = m_maxHealth * ratio - 1;// -1 because < not <= in calcState - desiredHealth = max( desiredHealth, 0.0f ); - internalChangeHealth( desiredHealth - m_currentHealth ); - setCorrectDamageState(); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBody::validateArmorAndDamageFX() const -{ - const ArmorTemplateSet* set = getObject()->getTemplate()->findArmorTemplateSet(m_curArmorSetFlags); - DEBUG_ASSERTCRASH(set, ("findArmorSet should never return null")); - if (set && set != m_curArmorSet) - { - if (set->getArmorTemplate()) - { - m_curArmor = TheArmorStore->makeArmor(set->getArmorTemplate()); - } - else - { - m_curArmor.clear(); - } - if (!m_damageFXOverride) m_curDamageFX = set->getDamageFX(); // Only set this if override is cleared - m_curArmorSet = set; - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Real ActiveBody::estimateDamage( DamageInfoInput& damageInfo ) const -{ - validateArmorAndDamageFX(); - - //Subdual damage can't affect you if you can't be subdued - if( IsSubdualDamage(damageInfo.m_damageType) && !canBeSubdued() ) - return 0.0f; - - if( damageInfo.m_damageType == DAMAGE_KILL_GARRISONED ) - { - ContainModuleInterface* contain = getObject()->getContain(); - if( contain && contain->getContainCount() > 0 && contain->isGarrisonable() && !contain->isImmuneToClearBuildingAttacks() ) - return 1.0f; - else - return 0.0f; - } - - if( damageInfo.m_damageType == DAMAGE_SNIPER ) - { - if( getObject()->isKindOf( KINDOF_STRUCTURE ) && getObject()->testStatus( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) - { - //If we're a pathfinder shooting a stinger site under construction... don't. Special case code. - return 0.0f; - } - } - - Real amount = m_curArmor.adjustDamage(damageInfo.m_damageType, damageInfo.m_amount); - - return amount; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBody::doDamageFX( const DamageInfo *damageInfo ) -{ - DamageType damageTypeToUse = damageInfo->in.m_damageType; - if (damageInfo->in.m_damageFXOverride != DAMAGE_UNRESISTABLE ) - { - // Just the visual aspect of damage can be overridden in some cases. - // Unresistable is the default to mean no override, as we are out of bits. - damageTypeToUse = damageInfo->in.m_damageFXOverride; - } - - if (m_curDamageFX) - { - UnsignedInt now = TheGameLogic->getFrame(); - if (damageTypeToUse == m_lastDamageFXDone && m_nextDamageFXTime > now) - return; - Object *source = TheGameLogic->findObjectByID(damageInfo->in.m_sourceID); // might be null, I guess - m_lastDamageFXDone = damageTypeToUse; - m_nextDamageFXTime = now + m_curDamageFX->getDamageFXThrottleTime(damageTypeToUse, source); - m_curDamageFX->doDamageFX(damageTypeToUse, damageInfo->out.m_actualDamageDealt, source, getObject()); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBody::attemptDamage( DamageInfo *damageInfo ) -{ - validateArmorAndDamageFX(); - - // sanity - if( damageInfo == NULL ) - return; - - if ( m_indestructible ) - return; - - // initialize these, just in case we bail out early - damageInfo->out.m_actualDamageDealt = 0.0f; - damageInfo->out.m_actualDamageClipped = 0.0f; - - // we cannot damage again objects that are already dead - Object* obj = getObject(); - if( obj->isEffectivelyDead() ) - return; - - Object *damager = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID ); - if( damager ) - { - //Store the template so later if the attacking object dies, we use script conditions to look at the - //damager's template inside evaluateTeamAttackedByType or evaluateNameAttackedByType. - damageInfo->in.m_sourceTemplate = damager->getTemplate(); - } - - Bool alreadyHandled = FALSE; - Bool allowModifier = TRUE; - Bool doDamageModules = TRUE; - Bool adjustConditions = TRUE; - Real amount = m_curArmor.adjustDamage(damageInfo->in.m_damageType, damageInfo->in.m_amount); - - // Units that get disabled by Chrono damage cannot take damage: - if (obj->isDisabledByType(DISABLED_CHRONO) && - !(damageInfo->in.m_damageType == DAMAGE_CHRONO_GUN || damageInfo->in.m_damageType == DAMAGE_CHRONO_UNRESISTABLE)) - return; - - switch( damageInfo->in.m_damageType ) - { - case DAMAGE_HEALING: - { - if( !damageInfo->in.m_kill ) - { - // Healing and Damage are separate, so this shouldn't happen - attemptHealing( damageInfo ); - } - return; - } - - case DAMAGE_KILLPILOT: - { - // This type of damage doesn't actually damage the unit, but it does kill it's - // pilot, in the case of a vehicle. - if( obj->isKindOf( KINDOF_VEHICLE ) ) - { - //Handle special case for combat bike. We actually will kill the bike by - //forcing the rider to leave the bike. That way the bike will automatically - //scuttle and be unusable. - ContainModuleInterface *contain = obj->getContain(); - if( contain && contain->isRiderChangeContain() ) - { - - AIUpdateInterface *ai = obj->getAI(); - - if( ai->isMoving() ) - { - //Bike is moving, so just blow it up instead. - if (damager) - damager->scoreTheKill( obj ); - obj->kill(); - } - else - { - //Removing the rider will scuttle the bike. - Object *rider = *(contain->getContainedItemsList()->begin()); - ai->aiEvacuateInstantly( TRUE, CMD_FROM_AI ); - - //Kill the rider. - if (damager) - damager->scoreTheKill( rider ); - rider->kill(); - } - } - else - { - // Make it unmanned, so units can easily check the ability to "take control of it" - obj->setDisabled( DISABLED_UNMANNED ); - TheGameLogic->deselectObject(obj, PLAYERMASK_ALL, TRUE); - - if ( obj->getAI() ) - obj->getAI()->aiIdle( CMD_FROM_AI ); - - // Convert it to the neutral team so it renders gray giving visual representation that it is unmanned. - obj->setTeam( ThePlayerList->getNeutralPlayer()->getDefaultTeam() ); - } - - //We don't care which team sniped the vehicle... we use this information to flag whether or not - //we captured a vehicle. - ThePlayerList->getNeutralPlayer()->getAcademyStats()->recordVehicleSniped(); - } - alreadyHandled = TRUE; - allowModifier = FALSE; - break; - } - - case DAMAGE_KILL_GARRISONED: - { - // KRIS: READ THIS!!! - // This code is very misleading (but in a good way). One would think this is - // an excellent place to add the hook to kill garrisoned troops. And that is - // a correct assumption. Unfortunately, the vast majority of garrison slayings - // are performed in DumbProjectileBehavior::projectileHandleCollision(), so my - // hope is that this message will save you some research time! - - Int killsToMake = REAL_TO_INT_FLOOR(damageInfo->in.m_amount); - ContainModuleInterface* contain = obj->getContain(); - if( contain && contain->getContainCount() > 0 && contain->isGarrisonable() && !contain->isImmuneToClearBuildingAttacks() ) - { - Int numKilled = 0; - - // garrisonable buildings subvert the normal process here. - const ContainedItemsList* items = contain->getContainedItemsList(); - if (items) - { - for( ContainedItemsList::const_iterator it = items->begin(); (it != items->end()) && (numKilled < killsToMake); it++ ) - { - Object* thingToKill = *it; - if (!thingToKill->isEffectivelyDead() ) - { - if (damager) - damager->scoreTheKill( thingToKill ); - thingToKill->kill(); - ++numKilled; - thingToKill->getControllingPlayer()->getAcademyStats()->recordClearedGarrisonedBuilding(); - } - } // next contained item - - } // if items - } // if a garrisonable thing - alreadyHandled = TRUE; - allowModifier = FALSE; - break; - } - - case DAMAGE_STATUS: - { - // Damage amount is msec time we set the status given in damageStatusType - Real realFramesToStatusFor = ConvertDurationFromMsecsToFrames(amount); - obj->doStatusDamage( damageInfo->in.m_damageStatusType , REAL_TO_INT_CEIL(realFramesToStatusFor) ); - alreadyHandled = TRUE; - allowModifier = FALSE; - break; - } - - case DAMAGE_CHRONO_GUN: - case DAMAGE_CHRONO_UNRESISTABLE: - { - // This handles both gaining chrono damage and recovering from it - - // Note: Should HoldTheLine or Shields apply? (Not for recovery) - if (damageInfo->in.m_damageType != DAMAGE_CHRONO_UNRESISTABLE) { - amount *= m_damageScalar; - } - - Bool wasSubdued = isSubduedChrono(); - - // Increase damage counter - internalAddChronoDamage(amount); - // DEBUG_LOG(("ActiveBody::attemptDamage - amount = %f, chronoDmg = %f\n", amount, getCurrentChronoDamageAmount())); - - // Check for disabling threshold - Bool nowSubdued = isSubduedChrono(); - - if (wasSubdued != nowSubdued) - { - // Enable/Disable ; Apply/Remove Visual Effects - onSubdualChronoChange(nowSubdued); - } - - // This will handle continuous art changes such as transparency - getObject()->notifyChronoDamage(amount); - - // Check kill state: - if (getCurrentChronoDamageAmount() > getMaxHealth()) { - damageInfo->in.m_kill = TRUE; - doDamageModules = FALSE; - adjustConditions = FALSE; - } - else { - alreadyHandled = TRUE; - } - allowModifier = FALSE; - } - } - - if( IsSubdualDamage(damageInfo->in.m_damageType) ) - { - if( !canBeSubdued() ) - return; - - Bool wasSubdued = isSubdued(); - internalAddSubdualDamage(amount); - Bool nowSubdued = isSubdued(); - alreadyHandled = TRUE; - allowModifier = FALSE; - - if( wasSubdued != nowSubdued ) - { - onSubdualChange(nowSubdued); - } - - getObject()->notifySubdualDamage(amount); - } - - if (allowModifier) - { - if( damageInfo->in.m_damageType != DAMAGE_UNRESISTABLE ) - { - // Apply the damage scalar (extra bonuses -- like strategy center defensive battle plan) - // And remember not to adjust unresistable damage, just like the armor code can't. - amount *= m_damageScalar; - } - } - - // sanity check the damage value -- can't apply negative damage - if( amount > 0.0f || damageInfo->in.m_kill ) - { - BodyDamageType oldState = m_curDamageState; - - //If the object is going to die, make sure we damage all remaining health. - if( damageInfo->in.m_kill ) - { - amount = m_currentHealth; - } - - if (!alreadyHandled) - { - // do the damage simplistic damage subtraction - internalChangeHealth( -amount, adjustConditions); - } - -#ifdef ALLOW_SURRENDER -//***************************************************************************************** -//***************************************************************************************** -//THIS CODE HAS BEEN DISABLED FOR THE MULTIPLAYER PLAY TEST!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!** -//***************************************************************************************** -// // if we were "killed" by surrender damage... -// if (damageInfo->in.m_damageType == DAMAGE_SURRENDER && m_currentHealth <= 0.0f && obj->isKindOf(KINDOF_CAN_SURRENDER)) -// { -// AIUpdateInterface* ai = obj->getAIUpdateInterface(); -// if (ai) -// { -// // do no damage, but make it surrender instead. -// m_currentHealth = m_prevHealth; -// const Object* killer = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID ); -// ai->setSurrendered(killer, true); -// return; -// } -// } -//***************************************************************************************** -//***************************************************************************************** -#endif - - // record the actual damage done from this, and when it happened - damageInfo->out.m_actualDamageDealt = amount; - damageInfo->out.m_actualDamageClipped = m_prevHealth - m_currentHealth; - - // then copy the whole DamageInfo struct for easy lookup - // (object pointer loses scope as soon as atteptdamage's caller ends) - // m_lastDamageTimestamp is initialized to FFFFFFFFFF, so doing a < compare is problematic. - // jba. - if (m_lastDamageTimestamp!=TheGameLogic->getFrame() && m_lastDamageTimestamp != TheGameLogic->getFrame()-1) { - m_lastDamageInfo = *damageInfo; - m_lastDamageCleared = false; - m_lastDamageTimestamp = TheGameLogic->getFrame(); - } else { - // Multiple damages applied in one/next frame. We prefer the one that tells who the attacker is. - Object *srcObj1 = TheGameLogic->findObjectByID(m_lastDamageInfo.in.m_sourceID); - Object *srcObj2 = TheGameLogic->findObjectByID(damageInfo->in.m_sourceID); - if (srcObj2) { - if (srcObj1) { - if (srcObj2->isKindOf(KINDOF_VEHICLE) || srcObj2->isKindOf(KINDOF_INFANTRY) || - srcObj2->isFactionStructure()) { - m_lastDamageInfo = *damageInfo; - m_lastDamageCleared = false; - m_lastDamageTimestamp = TheGameLogic->getFrame(); - } - } else { - m_lastDamageInfo = *damageInfo; - m_lastDamageCleared = false; - m_lastDamageTimestamp = TheGameLogic->getFrame(); - } - - } else { - // no change. - } - } - - // Notify the player that they have been attacked by this player - if (m_lastDamageInfo.in.m_sourceID != INVALID_ID) - { - Object *srcObj = TheGameLogic->findObjectByID(m_lastDamageInfo.in.m_sourceID); - if (srcObj) - { - Player *srcPlayer = srcObj->getControllingPlayer(); - obj->getControllingPlayer()->setAttackedBy(srcPlayer->getPlayerIndex()); - } - } - - // if our health has gone down then do run the damage module callback - if( m_currentHealth < m_prevHealth && doDamageModules) - { - for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m) - { - DamageModuleInterface* d = (*m)->getDamage(); - if (!d) - continue; - - d->onDamage( damageInfo ); - } - } - - if (m_curDamageState != oldState && adjustConditions) - { - for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m) - { - DamageModuleInterface* d = (*m)->getDamage(); - if (!d) - continue; - - d->onBodyDamageStateChange( damageInfo, oldState, m_curDamageState ); - } - - // @todo: This really feels like it should be in the TransitionFX lists. - if (m_curDamageState == BODY_DAMAGED) - { - AudioEventRTS damaged = *obj->getTemplate()->getSoundOnDamaged(); - damaged.setObjectID(obj->getID()); - TheAudio->addAudioEvent(&damaged); - } - else if (m_curDamageState == BODY_REALLYDAMAGED) - { - AudioEventRTS reallyDamaged = *obj->getTemplate()->getSoundOnReallyDamaged(); - reallyDamaged.setObjectID(obj->getID()); - TheAudio->addAudioEvent(&reallyDamaged); - } - - } - - // Should we play our fear sound? - if( (m_prevHealth / m_maxHealth) > YELLOW_DAMAGE_PERCENT && - (m_currentHealth / m_maxHealth) < YELLOW_DAMAGE_PERCENT && - (m_currentHealth > 0) ) - { - // 25% chance to play - if (GameLogicRandomValue(0, 99) < 25) - { - AudioEventRTS fearSound = *obj->getTemplate()->getVoiceFear(); - fearSound.setPosition( obj->getPosition() ); - fearSound.setPlayerIndex( obj->getControllingPlayer()->getPlayerIndex() ); - TheAudio->addAudioEvent(&fearSound); - } - } - - // check to see if we died - if( m_currentHealth <= 0 && m_prevHealth > 0 ) - { - // Give our killer credit for killing us, if there is one. - if( damager ) - { - damager->scoreTheKill( obj ); - } - - obj->onDie( damageInfo ); - } - } - - doDamageFX(damageInfo); - - // Damaged repulsable civilians scare (repulse) other civs. jba. - if( TheAI->getAiData()->m_enableRepulsors ) - { - if( obj->isKindOf( KINDOF_CAN_BE_REPULSED ) ) - { - obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_REPULSOR ) ); - } - } - - //Retaliate, even if I'm dead -- we'll still get my nearby friends to get revenge!!! - //Also only retaliate if we're controlled by a human player and the thing that attacked me - //is an enemy. - Player *controllingPlayer = obj->getControllingPlayer(); - if( controllingPlayer && controllingPlayer->isLogicalRetaliationModeEnabled() && controllingPlayer->getPlayerType() == PLAYER_HUMAN ) - { - if( shouldRetaliateAgainstAggressor(obj, damager)) - { - PartitionFilterPlayerAffiliation f1( controllingPlayer, ALLOW_ALLIES, true ); - PartitionFilterOnMap filterMapStatus; - PartitionFilter *filters[] = { &f1, &filterMapStatus, 0 }; - - - Real distance = TheAI->getAiData()->m_retaliateFriendsRadius + obj->getGeometryInfo().getBoundingCircleRadius(); - SimpleObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( obj->getPosition(), distance, FROM_CENTER_2D, filters, ITER_FASTEST ); - MemoryPoolObjectHolder hold( iter ); - for( Object *them = iter->first(); them; them = iter->next() ) - { - if (!shouldRetaliate(them)) { - continue; - } - AIUpdateInterface *ai = them->getAI(); - if (ai==NULL) { - continue; - } - //If we have AI and we're mobile, then assist! - if( !them->isKindOf( KINDOF_IMMOBILE )) - { - //But only if we can attack it! - CanAttackResult result = them->getAbleToAttackSpecificObject( ATTACK_NEW_TARGET, damager, CMD_FROM_AI ); - if( result == ATTACKRESULT_POSSIBLE_AFTER_MOVING || result == ATTACKRESULT_POSSIBLE ) - { - ai->aiGuardRetaliate( damager, them->getPosition(), NO_MAX_SHOTS_LIMIT, CMD_FROM_AI ); - } - } - } - } - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Bool ActiveBody::shouldRetaliateAgainstAggressor(Object *obj, Object *damager) -{ - /* This considers whether obj should invoke his friends to retaliate against damager. - Note that obj could be a structure, so we don't actually check whether obj will - retaliate, as in many cases he wouldn't. */ - if (damager==NULL) { - return false; - } - if (damager->isAirborneTarget()) { - return false; // Don't retaliate against aircraft. [8/25/2003] - } - if (damager->getRelationship( obj ) != ENEMIES) { - return false; // only retaliate against enemies. - } - Real distSqr = ThePartitionManager->getDistanceSquared(obj, damager, FROM_BOUNDINGSPHERE_2D); - if (distSqr > sqr(TheAI->getAiData()->m_maxRetaliateDistance)) { - return false; - } - // Only human players retaliate. [8/25/2003] - if (obj->getControllingPlayer()->getPlayerType() != PLAYER_HUMAN) { - return false; - } - // Drones never retaliate. [8/25/2003] - if (obj->isKindOf(KINDOF_DRONE)) { - return false; - } - return true; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Bool ActiveBody::shouldRetaliate(Object *obj) -{ - // Cannot retaliate objects dont. [8/25/2003] - if (obj->isKindOf(KINDOF_CANNOT_RETALIATE)) { - return false; - } - if (obj->isKindOf( KINDOF_IMMOBILE )) { - return false; - } - // Drones never retaliate. [8/25/2003] - if (obj->isKindOf(KINDOF_DRONE)) { - return false; - } - // Any unit that isn't idle won't retaliate. [8/25/2003] - if (obj->getAI()) { - if (!obj->getAI()->isIdle()) { - return false; - } - } else { - return false; // Non-ai can't retaliate. [8/26/2003] - } - // Stealthed units don't retaliate unless they're detected. [8/25/2003] - if ( obj->getStatusBits().test( OBJECT_STATUS_STEALTHED ) && - !obj->getStatusBits().test( OBJECT_STATUS_DETECTED ) ) { - return false; - } - // If we're using an ability, don't stop. [8/25/2003] - if (obj->testStatus(OBJECT_STATUS_IS_USING_ABILITY)) { - return false; - } - return true; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBody::attemptHealing( DamageInfo *damageInfo ) -{ - validateArmorAndDamageFX(); - - // sanity - if( damageInfo == NULL ) - return; - - if( damageInfo->in.m_damageType != DAMAGE_HEALING ) - { - // Healing and Damage are separate, so this shouldn't happen - attemptDamage( damageInfo ); - return; - } - - Object* obj = getObject(); - - // srj sez: sorry, once yer dead, yer dead. - // Special case for bridges, cause the system now things they're dead - ///@todo we need to figure out what has changed so we don't have to hack this (CBD 11-1-2002) - if( obj->isKindOf( KINDOF_BRIDGE ) == FALSE && - obj->isKindOf( KINDOF_BRIDGE_TOWER ) == FALSE && - obj->isEffectivelyDead()) - return; - - // initialize these, just in case we bail out early - damageInfo->out.m_actualDamageDealt = 0.0f; - damageInfo->out.m_actualDamageClipped = 0.0f; - - Real amount = m_curArmor.adjustDamage(damageInfo->in.m_damageType, damageInfo->in.m_amount); - - // sanity check the damage value -- can't apply negative healing - if( amount > 0.0f ) - { - BodyDamageType oldState = m_curDamageState; - - // do the damage simplistic damage ADDITION - internalChangeHealth( amount ); - - // record the actual damage done from this, and when it happened - damageInfo->out.m_actualDamageDealt = amount; - damageInfo->out.m_actualDamageClipped = m_prevHealth - m_currentHealth; - - //then copy the whole DamageInfo struct for easy lookup - //(object pointer loses scope as soon as atteptdamage's caller ends) - m_lastDamageInfo = *damageInfo; - m_lastDamageCleared = false; - m_lastDamageTimestamp = TheGameLogic->getFrame(); - m_lastHealingTimestamp = TheGameLogic->getFrame(); - - // if our health has gone UP then do run the damage module callback - if( m_currentHealth > m_prevHealth ) - { - for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m) - { - DamageModuleInterface* d = (*m)->getDamage(); - if (!d) - continue; - - d->onHealing( damageInfo ); - } - } - - if (m_curDamageState != oldState) - { - for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m) - { - DamageModuleInterface* d = (*m)->getDamage(); - if (!d) - continue; - - d->onBodyDamageStateChange( damageInfo, oldState, m_curDamageState ); - } - } - } - - doDamageFX(damageInfo); -} - -//------------------------------------------------------------------------------------------------- -/** Simple setting of the health value, it does *NOT* track any transition - * states for the event of "damage" or the event of "death". */ -//------------------------------------------------------------------------------------------------- -void ActiveBody::setInitialHealth(Int initialPercent) -{ - - // save the current health as the previous health - m_prevHealth = m_currentHealth; - - Real factor = initialPercent/100.0f; - Real newHealth = factor * m_initialHealth; - - // change the health to the requested percentage. - internalChangeHealth(newHealth - m_currentHealth); - -} - -//------------------------------------------------------------------------------------------------- -/** Simple setting of the health value, it does *NOT* track any transition - * states for the event of "damage" or the event of "death". */ -//------------------------------------------------------------------------------------------------- -void ActiveBody::setMaxHealth( Real maxHealth, MaxHealthChangeType healthChangeType ) -{ - Real prevMaxHealth = m_maxHealth; - m_maxHealth = maxHealth; - m_initialHealth = maxHealth; - - switch( healthChangeType ) - { - case PRESERVE_RATIO: - { - //400/500 (80%) + 100 becomes 480/600 (80%) - //200/500 (40%) - 100 becomes 160/400 (40%) - Real ratio = m_currentHealth / prevMaxHealth; - Real newHealth = maxHealth * ratio; - internalChangeHealth( newHealth - m_currentHealth ); - break; - } - case ADD_CURRENT_HEALTH_TOO: - { - //Add the same amount that we are adding to the max health. - //This could kill you if max health is reduced (if we ever have that ability to add buffer health like in D&D) - //400/500 (80%) + 100 becomes 500/600 (83%) - //200/500 (40%) - 100 becomes 100/400 (25%) - internalChangeHealth( maxHealth - prevMaxHealth ); - break; - } - case SAME_CURRENTHEALTH: - //do nothing - break; - - case FULLY_HEAL: - { - // Set current to the new Max. - //400/500 (80%) + 100 becomes 600/600 (100%) - //200/500 (40%) - 100 becomes 400/400 (100%) - internalChangeHealth(m_maxHealth - m_currentHealth); - break; - } - } - - // - // when max health is getting clipped to a lower value, if our current health - // value is now outside of the max health range we will set it back down to the - // new cap. Note that we are *NOT* going through any healing or damage methods here - // and are doing a direct set - // - if( m_currentHealth > maxHealth ) - { - internalChangeHealth( maxHealth - m_currentHealth ); - } - -} - -// ------------------------------------------------------------------------------------------------ -/** Given the current damage state of the object, evaluate the visual model conditions - * that have a visual impact on the object */ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::evaluateVisualCondition() -{ - - Drawable* draw = getObject()->getDrawable(); - if (draw) - { - draw->reactToBodyDamageStateChange(m_curDamageState); - } - - // - // destroy any particle systems that were attached to our body for the old state - // and create new particle systems for the new state - // - updateBodyParticleSystems(); - -} - -// ------------------------------------------------------------------------------------------------ -/** Create up to maxSystems particle systems of type particleSystemName and attach to bones - * specified by the bone base name. If there are more bones than maxSystems then the - * bones will be randomly selected */ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::createParticleSystems( const AsciiString &boneBaseName, - const ParticleSystemTemplate *systemTemplate, - Int maxSystems ) -{ - Object *us = getObject(); - - // sanity - if( systemTemplate == NULL ) - return; - - // get the bones - enum { MAX_BONES = 16 }; - Coord3D bonePositions[ MAX_BONES ]; - Int numBones = us->getMultiLogicalBonePosition( boneBaseName.str(), - MAX_BONES, - bonePositions, - NULL, - FALSE ); - - // if no bones found nothing else to do - if( numBones == 0 ) - return; - - // - // if we don't have enough bones to go up to maxSystems, we will change maxSystems to be - // the number of bones we actually have (we don't want systems doubling up on bones) - // - if( numBones < maxSystems ) - maxSystems = numBones; - - // - // create an array that we'll use to mark which bone positions have already been used, - // this is necessary when we have more bones than particle systems we're going to - // create, in which case we place the particle systems at random bone locations - // but don't want to repeat any - // - Bool usedBoneIndices[ MAX_BONES ] = { FALSE }; - - // create the particle systems - const Coord3D *pos; - for( Int i = 0; i < maxSystems; ++i ) - { - - // pick a bone index to place this particle system at - // MDC: moving to GameLogicRandomValue. This does not need to be synced, but having it so makes searches *so* much nicer. - // DTEH: Moved back to GameClientRandomValue because of desync problems. July 27th 2003. - Int boneIndex = GameClientRandomValue( 0, maxSystems - i - 1 ); - - // find the actual bone location to use and mark that bone index as used - Int count = 0; - Int j = 0; - for( ; j < numBones; j++ ) - { - - // ignore bone positions that have already been used - if( usedBoneIndices[ j ] == TRUE ) - continue; - - // this spot is available, if count == boneIndex then use this index - if( count == boneIndex ) - { - - pos = &bonePositions[ j ]; - usedBoneIndices[ j ] = TRUE; - break; // exit for j - - } // end if - else - { - - // we won't use this index, increment count until we find a suitable index to use - ++count; - - } // end else - - } // end for, j - - // sanity - DEBUG_ASSERTCRASH( j != numBones, - ("ActiveBody::createParticleSystems, Unable to select particle system index\n") ); - - // create particle system here - ParticleSystem *particleSystem = TheParticleSystemManager->createParticleSystem( systemTemplate ); - if( particleSystem ) - { - - // set the position of the particle system in local object space - particleSystem->setPosition( pos ); - - // attach particle system to object - particleSystem->attachToObject( us ); - - // create a new body particle system entry and keep this particle system in it - BodyParticleSystem *newEntry = newInstance(BodyParticleSystem); - newEntry->m_particleSystemID = particleSystem->getSystemID(); - newEntry->m_next = m_particleSystems; - m_particleSystems = newEntry; - - } // end if - - } // end for, i - -} // end createParticleSystems - -// ------------------------------------------------------------------------------------------------ -/** Delete all the body particle systems */ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::deleteAllParticleSystems( void ) -{ - BodyParticleSystem *nextBodySystem; - ParticleSystem *particleSystem; - - while( m_particleSystems ) - { - - // get this particle system - particleSystem = TheParticleSystemManager->findParticleSystem( m_particleSystems->m_particleSystemID ); - if( particleSystem ) - particleSystem->destroy(); - - // get next system in the body - nextBodySystem = m_particleSystems->m_next; - - // destroy this entry - m_particleSystems->deleteInstance(); - - // set the body systems head to the next - m_particleSystems = nextBodySystem; - - } // end while - -} // end deleteAllParticleSystems - -// ------------------------------------------------------------------------------------------------ -/* This function is called on state changes only. Body Type or Aflameness. */ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::updateBodyParticleSystems( void ) -{ - static const ParticleSystemTemplate *fireSmallTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoFireParticleSmallSystem ); - static const ParticleSystemTemplate *fireMediumTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoFireParticleMediumSystem ); - static const ParticleSystemTemplate *fireLargeTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoFireParticleLargeSystem ); - static const ParticleSystemTemplate *smokeSmallTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoSmokeParticleSmallSystem ); - static const ParticleSystemTemplate *smokeMediumTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoSmokeParticleMediumSystem ); - static const ParticleSystemTemplate *smokeLargeTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoSmokeParticleLargeSystem ); - static const ParticleSystemTemplate *aflameTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoAflameParticleSystem ); - Int countModifier; - const ParticleSystemTemplate *fireSmall; - const ParticleSystemTemplate *fireMedium; - const ParticleSystemTemplate *fireLarge; - const ParticleSystemTemplate *smokeSmall; - const ParticleSystemTemplate *smokeMedium; - const ParticleSystemTemplate *smokeLarge; - - // - // when we're aflame, we use a slightly different set of particle systems that are - // auto created that lends itself to more fire and bigger fire - // - if( getObject()->testStatus( OBJECT_STATUS_AFLAME ) ) - { - - fireSmall = fireMediumTemplate; // small fire becomes medium fire - fireMedium = fireLargeTemplate; // medium fire becomes large fire - fireLarge = fireLargeTemplate; // large fire stays large - smokeSmall = fireSmallTemplate; // small smoke becomes small fire - smokeMedium = fireSmallTemplate; // medium smoke becomes small fire - smokeLarge = fireSmallTemplate; // large smoke becomes small fire - - // we get to make more of them all too - countModifier = 2; - - } // end if - else - { - - // use regular templates - fireSmall = fireSmallTemplate; - fireMedium = fireMediumTemplate; - fireLarge = fireLargeTemplate; - smokeSmall = smokeSmallTemplate; - smokeMedium = smokeMediumTemplate; - smokeLarge = smokeLargeTemplate; - - // we make just the normal amount of these - countModifier = 1; - - } // end else - - // - // remove any particle systems we have currently in the list in favor of any new ones - // that we're going to autopopulate ourselves with - // - deleteAllParticleSystems(); - - // - // create particle systems for the new body state - // - - // small fire bones - createParticleSystems( TheGlobalData->m_autoFireParticleSmallPrefix, - fireSmall, TheGlobalData->m_autoFireParticleSmallMax * countModifier ); - - // medium fire bones - createParticleSystems( TheGlobalData->m_autoFireParticleMediumPrefix, - fireMedium, TheGlobalData->m_autoFireParticleMediumMax * countModifier ); - - // large fire bones - createParticleSystems( TheGlobalData->m_autoFireParticleLargePrefix, - fireLarge, TheGlobalData->m_autoFireParticleLargeMax * countModifier ); - - // small smoke bones - createParticleSystems( TheGlobalData->m_autoSmokeParticleSmallPrefix, - smokeSmall, TheGlobalData->m_autoSmokeParticleSmallMax * countModifier ); - - // medium smoke bones - createParticleSystems( TheGlobalData->m_autoSmokeParticleMediumPrefix, - smokeMedium, TheGlobalData->m_autoSmokeParticleMediumMax * countModifier ); - - // large smoke bones - createParticleSystems( TheGlobalData->m_autoSmokeParticleLargePrefix, - smokeLarge, TheGlobalData->m_autoSmokeParticleLargeMax * countModifier ); - - // actively on fire - if( getObject()->testStatus( OBJECT_STATUS_AFLAME ) ) - createParticleSystems( TheGlobalData->m_autoAflameParticlePrefix, - aflameTemplate, TheGlobalData->m_autoAflameParticleMax * countModifier ); - -} // end updatebodyParticleSystems - -//------------------------------------------------------------------------------------------------- -/** Simple changing of the health value, it does *NOT* track any transition - * states for the event of "damage" or the event of "death". If you - * with to kill an object and give these modules a chance to react - * to that event use the proper damage method calls. - * No game logic should go in here. This is the low level math and flag maintenance. - * Game stuff goes in attemptDamage and attemptHealing. -*/ -//------------------------------------------------------------------------------------------------- -void ActiveBody::internalChangeHealth( Real delta, Bool changeModelCondition) -{ - // save the current health as the previous health - m_prevHealth = m_currentHealth; - - // change the health by the delta, it can be positive or negative - m_currentHealth += delta; - - // high end cap - Real maxHealth = m_maxHealth; - if( m_currentHealth > maxHealth ) - m_currentHealth = maxHealth; - - // low end cap - const Real lowEndCap = 0.0f; // low end cap for health, don't go below this - if( m_currentHealth < lowEndCap ) - m_currentHealth = lowEndCap; - - if (changeModelCondition) { - // recalc the damage state - BodyDamageType oldState = m_curDamageState; - setCorrectDamageState(); - - // if our state has changed - if (m_curDamageState != oldState) - { - - // - // show a visual change in the model for the damage state, we do not show visual changes - // for damage states when things are under construction because we just don't have - // all the art states for that during buildup animation - // - if (!getObject()->getStatusBits().test(OBJECT_STATUS_UNDER_CONSTRUCTION)) - evaluateVisualCondition(); - - } // end if - } - - // mark the bit according to our health. (if our AI is dead but our health improves, it will - // still re-flag this bit in the AIDeadState every frame.) - getObject()->setEffectivelyDead(m_currentHealth <= 0); - -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBody::internalAddSubdualDamage( Real delta ) -{ - const ActiveBodyModuleData *data = getActiveBodyModuleData(); - - m_currentSubdualDamage += delta; - m_currentSubdualDamage = min(m_currentSubdualDamage, data->m_subdualDamageCap); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBody::internalAddChronoDamage(Real delta) -{ - // Just increment, we don't need a cap. we kill once maxHealth is reached - //Real chronoDamageCap = m_maxHealth * 2.0; - m_currentChronoDamage += delta; - //m_currentChronoDamage = min(m_currentChronoDamage, chronoDamageCap); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Bool ActiveBody::canBeSubdued() const -{ - // Any body with subdue listings can be subdued. - return getActiveBodyModuleData()->m_subdualDamageCap > 0; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBody::onSubdualChange( Bool isNowSubdued ) -{ - if( !getObject()->isKindOf(KINDOF_PROJECTILE) ) - { - Object *me = getObject(); - - if( isNowSubdued ) - { - me->setDisabled(DISABLED_SUBDUED); - - ContainModuleInterface *contain = me->getContain(); - if ( contain ) - contain->orderAllPassengersToIdle( CMD_FROM_AI ); - - } - else - { - me->clearDisabled(DISABLED_SUBDUED); - - if( me->isKindOf( KINDOF_FS_INTERNET_CENTER ) ) - { - //Kris: October 20, 2003 - Patch 1.01 - //Any unit inside an internet center is a hacker! Order - //them to start hacking again. - ContainModuleInterface *contain = me->getContain(); - if ( contain ) - contain->orderAllPassengersToHackInternet( CMD_FROM_AI ); - } - } - } - else if( isNowSubdued )// There is no coming back from being jammed, and projectiles can't even heal, but this makes it clear. - { - ProjectileUpdateInterface *pui = getObject()->getProjectileUpdateInterface(); - if( pui ) - { - pui->projectileNowJammed(); - } - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBody::onSubdualChronoChange( Bool isNowSubdued ) -{ - Object *me = getObject(); - - if( isNowSubdued ) - { - me->setDisabled(DISABLED_CHRONO); - - // Apply Chrono Particles - applyChronoParticleSystems(); - - m_chronoDisabledSoundLoop = TheAudio->getMiscAudio()->m_chronoDisabledSoundLoop; - m_chronoDisabledSoundLoop.setObjectID(me->getID()); - m_chronoDisabledSoundLoop.setPlayingHandle(TheAudio->addAudioEvent(&m_chronoDisabledSoundLoop)); - - ContainModuleInterface *contain = me->getContain(); - if ( contain ) - contain->orderAllPassengersToIdle( CMD_FROM_AI ); - } - else - { - me->clearDisabled(DISABLED_CHRONO); - - // Remove Chrono Particles, i.e. restore default particles - updateBodyParticleSystems(); - - TheAudio->removeAudioEvent(m_chronoDisabledSoundLoop.getPlayingHandle()); - - if (me->isKindOf(KINDOF_FS_INTERNET_CENTER)) - { - //Kris: October 20, 2003 - Patch 1.01 - //Any unit inside an internet center is a hacker! Order - //them to start hacking again. - ContainModuleInterface* contain = me->getContain(); - if (contain) - contain->orderAllPassengersToHackInternet(CMD_FROM_AI); - } - } -} - -// ------------------------------------------------------------------------------------------------ -/* This function is called on state changes only. Body Type or Aflameness. */ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::applyChronoParticleSystems(void) -{ - deleteAllParticleSystems(); - - static const ParticleSystemTemplate* chronoEffectsLargeTemplate = TheParticleSystemManager->findTemplate(TheGlobalData->m_chronoDisableParticleSystemLarge); - static const ParticleSystemTemplate* chronoEffectsMediumTemplate = TheParticleSystemManager->findTemplate(TheGlobalData->m_chronoDisableParticleSystemMedium); - static const ParticleSystemTemplate* chronoEffectsSmallTemplate = TheParticleSystemManager->findTemplate(TheGlobalData->m_chronoDisableParticleSystemSmall); - - const ParticleSystemTemplate* chronoEffects; - - // TODO: select particles - Object* obj = getObject(); - - if (obj->isKindOf(KINDOF_INFANTRY)) { - chronoEffects = chronoEffectsSmallTemplate; - } - else if (obj->isKindOf(KINDOF_STRUCTURE)) { - chronoEffects = chronoEffectsLargeTemplate; - } - // Use Medium as default - else { - chronoEffects = chronoEffectsMediumTemplate; - } - - ParticleSystem* particleSystem = TheParticleSystemManager->createParticleSystem(chronoEffects); - if (particleSystem) - { - // set the position of the particle system in local object space - // particleSystem->setPosition(obj->getPosition()); - - // attach particle system to object - particleSystem->attachToObject(obj); - - // Scale particle count based on size - Real x = obj->getGeometryInfo().getMajorRadius(); - Real y = obj->getGeometryInfo().getMinorRadius(); - Real z = obj->getGeometryInfo().getMaxHeightAbovePosition() * 0.5; - particleSystem->setEmissionBoxHalfSize(x, y, z); - //Real size = x * y; - //particleSystem->setBurstCountMultiplier(MAX(1.0, sqrt(size * 0.02f))); // these are somewhat tweaked right now - //particleSystem->setBurstDelayMultiplier(MIN(5.0, sqrt(500.0f / size))); - - // create a new body particle system entry and keep this particle system in it - BodyParticleSystem* newEntry = newInstance(BodyParticleSystem); - newEntry->m_particleSystemID = particleSystem->getSystemID(); - newEntry->m_next = m_particleSystems; - m_particleSystems = newEntry; - - // DEBUG_LOG(("ActiveBody::applyChronoParticleSystems - created particleSystem.\n")); - } - else { - // DEBUG_LOG(("ActiveBody::applyChronoParticleSystems - Failed to create particleSystem?!\n")); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Bool ActiveBody::isSubduedChrono() const -{ - return (m_maxHealth * TheGlobalData->m_chronoDamageDisableThreshold) <= m_currentChronoDamage; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Bool ActiveBody::isSubdued() const -{ - return m_maxHealth <= m_currentSubdualDamage; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Real ActiveBody::getHealth() const -{ - return m_currentHealth; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -BodyDamageType ActiveBody::getDamageState() const -{ - return m_curDamageState; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Real ActiveBody::getMaxHealth() const -{ - return m_maxHealth; -} ///< return max health - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -UnsignedInt ActiveBody::getSubdualDamageHealRate() const -{ - return getActiveBodyModuleData()->m_subdualDamageHealRate; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Real ActiveBody::getSubdualDamageHealAmount() const -{ - return getActiveBodyModuleData()->m_subdualDamageHealAmount; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Bool ActiveBody::hasAnySubdualDamage() const -{ - return m_currentSubdualDamage > 0; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -UnsignedInt ActiveBody::getChronoDamageHealRate() const -{ - return TheGlobalData->m_chronoDamageHealRate; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Real ActiveBody::getChronoDamageHealAmount() const -{ - // DEBUG_LOG(("ActiveBody::getChronoDamageHealAmount() - maxHealth = %f\n", m_maxHealth)); - return m_maxHealth * TheGlobalData->m_chronoDamageHealAmount; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Bool ActiveBody::hasAnyChronoDamage() const -{ - return m_currentChronoDamage > 0; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Real ActiveBody::getInitialHealth() const -{ - return m_initialHealth; -} // return initial health - - -// ------------------------------------------------------------------------------------------------ -/** Set or unset the overridable indestructible flag in the body */ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::setIndestructible( Bool indestructible ) -{ - - m_indestructible = indestructible; - - // for bridges, we mirror this state on its towers - Object *us = getObject(); - if( us->isKindOf( KINDOF_BRIDGE ) ) - { - BridgeBehaviorInterface *bbi = BridgeBehavior::getBridgeBehaviorInterfaceFromObject( us ); - if( bbi ) - { - Object *tower; - - // get tower - for( Int i = 0; i < BRIDGE_MAX_TOWERS; ++i ) - { - - tower = TheGameLogic->findObjectByID( bbi->getTowerID( BridgeTowerType(i) ) ); - if( tower ) - { - BodyModuleInterface *body = tower->getBodyModule(); - - if( body ) - body->setIndestructible( indestructible ); - - } // end if - - } // end for, i - - } // end if - - } // end if - -} // end setIndestructible - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void ActiveBody::onVeterancyLevelChanged( VeterancyLevel oldLevel, VeterancyLevel newLevel, Bool provideFeedback ) -{ - if (oldLevel == newLevel) - return; - - if (oldLevel < newLevel) - { - if( provideFeedback ) - { - AudioEventRTS veterancyChanged; - switch (newLevel) - { - case LEVEL_VETERAN: - veterancyChanged = *getObject()->getTemplate()->getSoundPromotedVeteran(); - break; - case LEVEL_ELITE: - veterancyChanged = *getObject()->getTemplate()->getSoundPromotedElite(); - break; - case LEVEL_HEROIC: - veterancyChanged = *getObject()->getTemplate()->getSoundPromotedHero(); - break; - } - - veterancyChanged.setObjectID(getObject()->getID()); - TheAudio->addAudioEvent(&veterancyChanged); - } - - //Also mark the UI dirty -- incase the object is selected or contained. - Object *obj = getObject(); - Drawable *draw = TheInGameUI->getFirstSelectedDrawable(); - if( draw ) - { - Object *checkOwner = draw->getObject(); - if( checkOwner == obj ) - { - //Our selected object has been promoted! - TheControlBar->markUIDirty(); - } - else - { - const Object *containedBy = obj->getContainedBy(); - if( containedBy && TheInGameUI->getSelectCount() == 1 ) - { - Object *checkOwner = draw->getObject(); - if( checkOwner == containedBy ) - { - //But only if the contained by object is containing me! - TheControlBar->markUIDirty(); - } - } - } - } - } - - Real oldBonus = TheGlobalData->m_healthBonus[oldLevel]; - Real newBonus = TheGlobalData->m_healthBonus[newLevel]; - Real mult = newBonus / oldBonus; - - // get this before calling setMaxHealth, since it can clip curHealth - //Real newHealth = m_currentHealth * mult; - - // change the max - setMaxHealth(m_maxHealth * mult, PRESERVE_RATIO ); - - // now change the cur (setMaxHealth now handles it) - //internalChangeHealth( newHealth - m_currentHealth ); - - switch (newLevel) - { - case LEVEL_REGULAR: - clearArmorSetFlag(ARMORSET_VETERAN); - clearArmorSetFlag(ARMORSET_ELITE); - clearArmorSetFlag(ARMORSET_HERO); - break; - case LEVEL_VETERAN: - setArmorSetFlag(ARMORSET_VETERAN); - clearArmorSetFlag(ARMORSET_ELITE); - clearArmorSetFlag(ARMORSET_HERO); - break; - case LEVEL_ELITE: - clearArmorSetFlag(ARMORSET_VETERAN); - setArmorSetFlag(ARMORSET_ELITE); - clearArmorSetFlag(ARMORSET_HERO); - break; - case LEVEL_HEROIC: - clearArmorSetFlag(ARMORSET_VETERAN); - clearArmorSetFlag(ARMORSET_ELITE); - setArmorSetFlag(ARMORSET_HERO); - break; - } -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::setAflame( Bool ) -{ - - // - // All this does now is act like a major body state change. It is called after Aflame has been - // set or cleared as an Object Status - // - updateBodyParticleSystems(); - -} -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::overrideDamageFX(DamageFX* damageFX) -{ - if (damageFX != NULL) { - m_curDamageFX = damageFX; - m_damageFXOverride = true; - } - else { - m_curDamageFX = NULL; - m_damageFXOverride = false; - - // Restore DamageFX from current armorset - const ArmorTemplateSet* set = getObject()->getTemplate()->findArmorTemplateSet(m_curArmorSetFlags); - if (set) - { - m_curDamageFX = set->getDamageFX(); - } - } - //DEBUG_LOG((">>>ActiveBody: overrideDamageFX - new m_curDamageFX = %d, m_damageFXOverride = %d\n", - // m_curDamageFX, m_damageFXOverride)); -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::crc( Xfer *xfer ) -{ - - // extend base class - BodyModule::crc( xfer ); - -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::xfer( Xfer *xfer ) -{ - - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // base class - BodyModule::xfer( xfer ); - - // current health - xfer->xferReal( &m_currentHealth ); - - xfer->xferReal( &m_currentSubdualDamage ); - - // previous health - xfer->xferReal( &m_prevHealth ); - - // max health - xfer->xferReal( &m_maxHealth ); - - // initial health - xfer->xferReal( &m_initialHealth ); - - // current damage state - xfer->xferUser( &m_curDamageState, sizeof( BodyDamageType ) ); - - // next damage fx time - xfer->xferUnsignedInt( &m_nextDamageFXTime ); - - // last damage fx done - xfer->xferUser( &m_lastDamageFXDone, sizeof( DamageType ) ); - - // last damage info - xfer->xferSnapshot( &m_lastDamageInfo ); - - // last damage timestamp - xfer->xferUnsignedInt( &m_lastDamageTimestamp ); - - // last damage timestamp - xfer->xferUnsignedInt( &m_lastHealingTimestamp ); - - // front crushed - xfer->xferBool( &m_frontCrushed ); - - // back crushed - xfer->xferBool( &m_backCrushed ); - - // last damaged cleared - xfer->xferBool( &m_lastDamageCleared ); - - // indestructible - xfer->xferBool( &m_indestructible ); - - // particle system count - BodyParticleSystem *system; - UnsignedShort particleSystemCount = 0; - for( system = m_particleSystems; system; system = system->m_next ) - particleSystemCount++; - xfer->xferUnsignedShort( &particleSystemCount ); - - // particle systems - if( xfer->getXferMode() == XFER_SAVE ) - { - - // walk the particle systems - for( system = m_particleSystems; system; system = system->m_next ) - { - - // write particle system ID - xfer->xferUser( &system->m_particleSystemID, sizeof( ParticleSystemID ) ); - - } // end for, system - - } // end if, save - else - { - ParticleSystemID particleSystemID; - - // the list should be empty at this time - if( m_particleSystems != NULL ) - { - - DEBUG_CRASH(( "ActiveBody::xfer - m_particleSystems should be empty, but is not\n" )); - throw SC_INVALID_DATA; - - } // end if - - // read all data elements - BodyParticleSystem *newEntry; - for( UnsignedShort i = 0; i < particleSystemCount; ++i ) - { - - // read particle system ID - xfer->xferUser( &particleSystemID, sizeof( ParticleSystemID ) ); - - // allocate entry and add to list - newEntry = newInstance(BodyParticleSystem); - newEntry->m_particleSystemID = particleSystemID; - newEntry->m_next = m_particleSystems; // the list will be reversed, but we don't care - m_particleSystems = newEntry; - - } // end for, i - - } // end else, load - - // armor set flags - m_curArmorSetFlags.xfer( xfer ); - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void ActiveBody::loadPostProcess( void ) -{ - - // extend base class - BodyModule::loadPostProcess(); - -} // end loadPostProcess +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: ActiveBody.cpp /////////////////////////////////////////////////////////////////////////// +// Author: Colin Day, November 2001 +// Desc: Active bodies have health, they can die and are affected by health +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine +#include "Common/BitFlagsIO.h" +#include "Common/CRCDebug.h" +#include "Common/DamageFX.h" +#include "Common/Player.h" +#include "Common/GameState.h" +#include "Common/GlobalData.h" +#include "Common/PlayerList.h" +#include "Common/Team.h" +#include "Common/Thing.h" +#include "Common/ThingTemplate.h" +#include "Common/Xfer.h" +#include "GameClient/ControlBar.h" +#include "GameClient/Drawable.h" +#include "GameClient/InGameUI.h" +#include "GameClient/ParticleSys.h" +#include "GameLogic/AI.h" +#include "GameLogic/AIPathfind.h" +#include "GameLogic/Armor.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/Object.h" +#include "GameLogic/Damage.h" +#include "GameLogic/PartitionManager.h" +#include "GameLogic/TerrainLogic.h" +#include "GameLogic/Weapon.h" +#include "GameLogic/Module/AIUpdate.h" +#include "GameLogic/Module/ActiveBody.h" +#include "GameLogic/Module/BridgeBehavior.h" +#include "GameLogic/Module/ContainModule.h" +#include "GameLogic/Module/DamageModule.h" +#include "GameLogic/Module/DieModule.h" + + +#ifdef RTS_INTERNAL +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +#define YELLOW_DAMAGE_PERCENT (0.25f) + +// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// + +// ------------------------------------------------------------------------------------------------ +/** Body particle systems are particle systems that are automatically created and attached + * to an object as the damage state changes for that object. We keep a list of these + * so that when we transition from one state to another we can kill any old particle + * systems that we need to before we create new ones */ +// ------------------------------------------------------------------------------------------------ +class BodyParticleSystem : public MemoryPoolObject +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( BodyParticleSystem, "BodyParticleSystem" ) + +public: + + ParticleSystemID m_particleSystemID; ///< the particle system ID + BodyParticleSystem *m_next; ///< next particle system in this body module + +}; + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +BodyParticleSystem::~BodyParticleSystem( void ) +{ + +} // end ~BodyParticleSystem + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +// ------------------------------------------------------------------------------------------------ +static BodyDamageType calcDamageState(Real health, Real maxHealth) +{ + if (!TheGlobalData) + return BODY_PRISTINE; + + Real ratio = health / maxHealth; + + if (ratio > TheGlobalData->m_unitDamagedThresh) + { + return BODY_PRISTINE; + } + else if (ratio > TheGlobalData->m_unitReallyDamagedThresh) + { + return BODY_DAMAGED; + } + else if (ratio > 0.0f) + { + return BODY_REALLYDAMAGED; + } + else + { + return BODY_RUBBLE; + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ActiveBodyModuleData::ActiveBodyModuleData() +{ + m_maxHealth = 0; + m_initialHealth = 0; + m_subdualDamageCap = 0; + m_subdualDamageHealRate = 0; + m_subdualDamageHealAmount = 0; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBodyModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + ModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + { "MaxHealth", INI::parseReal, NULL, offsetof( ActiveBodyModuleData, m_maxHealth ) }, + { "InitialHealth", INI::parseReal, NULL, offsetof( ActiveBodyModuleData, m_initialHealth ) }, + + { "SubdualDamageCap", INI::parseReal, NULL, offsetof( ActiveBodyModuleData, m_subdualDamageCap ) }, + { "SubdualDamageHealRate", INI::parseDurationUnsignedInt, NULL, offsetof( ActiveBodyModuleData, m_subdualDamageHealRate ) }, + { "SubdualDamageHealAmount", INI::parseReal, NULL, offsetof( ActiveBodyModuleData, m_subdualDamageHealAmount ) }, + { 0, 0, 0, 0 } + }; + p.add(dataFieldParse); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ActiveBody::ActiveBody( Thing *thing, const ModuleData* moduleData ) : + BodyModule(thing, moduleData), + m_curDamageFX(NULL), + m_curArmorSet(NULL), + m_frontCrushed(false), + m_backCrushed(false), + m_lastDamageTimestamp(0xffffffff),// So we don't think we just got damaged on the first frame + m_lastHealingTimestamp(0xffffffff),// So we don't think we just got healed on the first frame + m_curDamageState(BODY_PRISTINE), + m_nextDamageFXTime(0), + m_lastDamageFXDone((DamageType)-1), + m_lastDamageCleared(false), + m_particleSystems(NULL), + m_currentSubdualDamage(0), + m_indestructible(false), + m_damageFXOverride(false) +{ + m_currentHealth = getActiveBodyModuleData()->m_initialHealth; + m_prevHealth = getActiveBodyModuleData()->m_initialHealth; + m_maxHealth = getActiveBodyModuleData()->m_maxHealth; + m_initialHealth = getActiveBodyModuleData()->m_initialHealth; + + // force an initially-valid armor setup + validateArmorAndDamageFX(); + // start us in the right state + setCorrectDamageState(); + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +ActiveBody::~ActiveBody( void ) +{ +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::onDelete( void ) +{ + + // delete all particle systems + deleteAllParticleSystems(); + +} // end onDelete + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::setCorrectDamageState() +{ + m_curDamageState = calcDamageState(m_currentHealth, m_maxHealth); + + /// @todo srj -- bleah, this is an icky way to do it. oh well. + if (m_curDamageState == BODY_RUBBLE && getObject()->isKindOf(KINDOF_STRUCTURE)) + { + Real rubbleHeight = getObject()->getTemplate()->getStructureRubbleHeight(); + + if (rubbleHeight <= 0.0f) + rubbleHeight = TheGlobalData->m_defaultStructureRubbleHeight; + + /** @todo I had to change this to a Z only version to keep it from disappearing from the + PartitionManager for a frame. That didn't used to happen. + */ + getObject()->setGeometryInfoZ(rubbleHeight); + + // Have to tell pathfind as well, as rubble pathfinds differently. + TheAI->pathfinder()->removeObjectFromPathfindMap(getObject()); + TheAI->pathfinder()->addObjectToPathfindMap(getObject()); + + + // here we make sure nobody collides with us, ever again... //Lorenzen + //THis allows projectiles shot from infantry that are inside rubble to get out of said rubble safely + getObject()->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_COLLISIONS ) ); + + + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::setDamageState( BodyDamageType newState ) +{ + Real ratio = 1.0f; + if( newState == BODY_PRISTINE ) + { + ratio = 1.0f; + } + else if( newState == BODY_DAMAGED ) + { + ratio = TheGlobalData->m_unitDamagedThresh; + } + else if( newState == BODY_REALLYDAMAGED ) + { + ratio = TheGlobalData->m_unitReallyDamagedThresh; + } + else if( newState == BODY_RUBBLE ) + { + ratio = 0.0f; + } + Real desiredHealth = m_maxHealth * ratio - 1;// -1 because < not <= in calcState + desiredHealth = max( desiredHealth, 0.0f ); + internalChangeHealth( desiredHealth - m_currentHealth ); + setCorrectDamageState(); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::validateArmorAndDamageFX() const +{ + const ArmorTemplateSet* set = getObject()->getTemplate()->findArmorTemplateSet(m_curArmorSetFlags); + DEBUG_ASSERTCRASH(set, ("findArmorSet should never return null")); + if (set && set != m_curArmorSet) + { + if (set->getArmorTemplate()) + { + m_curArmor = TheArmorStore->makeArmor(set->getArmorTemplate()); + } + else + { + m_curArmor.clear(); + } + if (!m_damageFXOverride) m_curDamageFX = set->getDamageFX(); // Only set this if override is cleared + m_curArmorSet = set; + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Real ActiveBody::estimateDamage( DamageInfoInput& damageInfo ) const +{ + validateArmorAndDamageFX(); + + //Subdual damage can't affect you if you can't be subdued + if( IsSubdualDamage(damageInfo.m_damageType) && !canBeSubdued() ) + return 0.0f; + + if( damageInfo.m_damageType == DAMAGE_KILL_GARRISONED ) + { + ContainModuleInterface* contain = getObject()->getContain(); + if( contain && contain->getContainCount() > 0 && contain->isGarrisonable() && !contain->isImmuneToClearBuildingAttacks() ) + return 1.0f; + else + return 0.0f; + } + + if( damageInfo.m_damageType == DAMAGE_SNIPER ) + { + if( getObject()->isKindOf( KINDOF_STRUCTURE ) && getObject()->testStatus( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) + { + //If we're a pathfinder shooting a stinger site under construction... don't. Special case code. + return 0.0f; + } + } + + Real amount = m_curArmor.adjustDamage(damageInfo.m_damageType, damageInfo.m_amount); + + return amount; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::doDamageFX( const DamageInfo *damageInfo ) +{ + DamageType damageTypeToUse = damageInfo->in.m_damageType; + if (damageInfo->in.m_damageFXOverride != DAMAGE_UNRESISTABLE ) + { + // Just the visual aspect of damage can be overridden in some cases. + // Unresistable is the default to mean no override, as we are out of bits. + damageTypeToUse = damageInfo->in.m_damageFXOverride; + } + + if (m_curDamageFX) + { + UnsignedInt now = TheGameLogic->getFrame(); + if (damageTypeToUse == m_lastDamageFXDone && m_nextDamageFXTime > now) + return; + Object *source = TheGameLogic->findObjectByID(damageInfo->in.m_sourceID); // might be null, I guess + m_lastDamageFXDone = damageTypeToUse; + m_nextDamageFXTime = now + m_curDamageFX->getDamageFXThrottleTime(damageTypeToUse, source); + m_curDamageFX->doDamageFX(damageTypeToUse, damageInfo->out.m_actualDamageDealt, source, getObject()); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::attemptDamage( DamageInfo *damageInfo ) +{ + validateArmorAndDamageFX(); + + // sanity + if( damageInfo == NULL ) + return; + + if ( m_indestructible ) + return; + + // initialize these, just in case we bail out early + damageInfo->out.m_actualDamageDealt = 0.0f; + damageInfo->out.m_actualDamageClipped = 0.0f; + + // we cannot damage again objects that are already dead + Object* obj = getObject(); + if( obj->isEffectivelyDead() ) + return; + + Object *damager = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID ); + if( damager ) + { + //Store the template so later if the attacking object dies, we use script conditions to look at the + //damager's template inside evaluateTeamAttackedByType or evaluateNameAttackedByType. + damageInfo->in.m_sourceTemplate = damager->getTemplate(); + } + + Bool alreadyHandled = FALSE; + Bool allowModifier = TRUE; + Bool doDamageModules = TRUE; + Bool adjustConditions = TRUE; + Real amount = m_curArmor.adjustDamage(damageInfo->in.m_damageType, damageInfo->in.m_amount); + + // Units that get disabled by Chrono damage cannot take damage: + if (obj->isDisabledByType(DISABLED_CHRONO) && + !(damageInfo->in.m_damageType == DAMAGE_CHRONO_GUN || damageInfo->in.m_damageType == DAMAGE_CHRONO_UNRESISTABLE)) + return; + + switch( damageInfo->in.m_damageType ) + { + case DAMAGE_HEALING: + { + if( !damageInfo->in.m_kill ) + { + // Healing and Damage are separate, so this shouldn't happen + attemptHealing( damageInfo ); + } + return; + } + + case DAMAGE_KILLPILOT: + { + // This type of damage doesn't actually damage the unit, but it does kill it's + // pilot, in the case of a vehicle. + if( obj->isKindOf( KINDOF_VEHICLE ) ) + { + //Handle special case for combat bike. We actually will kill the bike by + //forcing the rider to leave the bike. That way the bike will automatically + //scuttle and be unusable. + ContainModuleInterface *contain = obj->getContain(); + if( contain && contain->isRiderChangeContain() ) + { + + AIUpdateInterface *ai = obj->getAI(); + + if( ai->isMoving() ) + { + //Bike is moving, so just blow it up instead. + if (damager) + damager->scoreTheKill( obj ); + obj->kill(); + } + else + { + //Removing the rider will scuttle the bike. + Object *rider = *(contain->getContainedItemsList()->begin()); + ai->aiEvacuateInstantly( TRUE, CMD_FROM_AI ); + + //Kill the rider. + if (damager) + damager->scoreTheKill( rider ); + rider->kill(); + } + } + else + { + // Make it unmanned, so units can easily check the ability to "take control of it" + obj->setDisabled( DISABLED_UNMANNED ); + TheGameLogic->deselectObject(obj, PLAYERMASK_ALL, TRUE); + + if ( obj->getAI() ) + obj->getAI()->aiIdle( CMD_FROM_AI ); + + // Convert it to the neutral team so it renders gray giving visual representation that it is unmanned. + obj->setTeam( ThePlayerList->getNeutralPlayer()->getDefaultTeam() ); + } + + //We don't care which team sniped the vehicle... we use this information to flag whether or not + //we captured a vehicle. + ThePlayerList->getNeutralPlayer()->getAcademyStats()->recordVehicleSniped(); + } + alreadyHandled = TRUE; + allowModifier = FALSE; + break; + } + + case DAMAGE_KILL_GARRISONED: + { + // KRIS: READ THIS!!! + // This code is very misleading (but in a good way). One would think this is + // an excellent place to add the hook to kill garrisoned troops. And that is + // a correct assumption. Unfortunately, the vast majority of garrison slayings + // are performed in DumbProjectileBehavior::projectileHandleCollision(), so my + // hope is that this message will save you some research time! + + Int killsToMake = REAL_TO_INT_FLOOR(damageInfo->in.m_amount); + ContainModuleInterface* contain = obj->getContain(); + if( contain && contain->getContainCount() > 0 && contain->isGarrisonable() && !contain->isImmuneToClearBuildingAttacks() ) + { + Int numKilled = 0; + + // garrisonable buildings subvert the normal process here. + const ContainedItemsList* items = contain->getContainedItemsList(); + if (items) + { + for( ContainedItemsList::const_iterator it = items->begin(); (it != items->end()) && (numKilled < killsToMake); it++ ) + { + Object* thingToKill = *it; + if (!thingToKill->isEffectivelyDead() ) + { + if (damager) + damager->scoreTheKill( thingToKill ); + thingToKill->kill(); + ++numKilled; + thingToKill->getControllingPlayer()->getAcademyStats()->recordClearedGarrisonedBuilding(); + } + } // next contained item + + } // if items + } // if a garrisonable thing + alreadyHandled = TRUE; + allowModifier = FALSE; + break; + } + + case DAMAGE_STATUS: + { + // Damage amount is msec time we set the status given in damageStatusType + Real realFramesToStatusFor = ConvertDurationFromMsecsToFrames(amount); + obj->doStatusDamage( damageInfo->in.m_damageStatusType , REAL_TO_INT_CEIL(realFramesToStatusFor) ); + alreadyHandled = TRUE; + allowModifier = FALSE; + break; + } + + case DAMAGE_CHRONO_GUN: + case DAMAGE_CHRONO_UNRESISTABLE: + { + // This handles both gaining chrono damage and recovering from it + + // Note: Should HoldTheLine or Shields apply? (Not for recovery) + if (damageInfo->in.m_damageType != DAMAGE_CHRONO_UNRESISTABLE) { + amount *= m_damageScalar; + } + + Bool wasSubdued = isSubduedChrono(); + + // Increase damage counter + internalAddChronoDamage(amount); + // DEBUG_LOG(("ActiveBody::attemptDamage - amount = %f, chronoDmg = %f\n", amount, getCurrentChronoDamageAmount())); + + // Check for disabling threshold + Bool nowSubdued = isSubduedChrono(); + + if (wasSubdued != nowSubdued) + { + // Enable/Disable ; Apply/Remove Visual Effects + onSubdualChronoChange(nowSubdued); + } + + // This will handle continuous art changes such as transparency + getObject()->notifyChronoDamage(amount); + + // Check kill state: + if (getCurrentChronoDamageAmount() > getMaxHealth()) { + damageInfo->in.m_kill = TRUE; + doDamageModules = FALSE; + adjustConditions = FALSE; + } + else { + alreadyHandled = TRUE; + } + allowModifier = FALSE; + } + } + + if( IsSubdualDamage(damageInfo->in.m_damageType) ) + { + if( !canBeSubdued() ) + return; + + Bool wasSubdued = isSubdued(); + internalAddSubdualDamage(amount); + Bool nowSubdued = isSubdued(); + alreadyHandled = TRUE; + allowModifier = FALSE; + + if( wasSubdued != nowSubdued ) + { + onSubdualChange(nowSubdued); + } + + getObject()->notifySubdualDamage(amount); + } + + if (allowModifier) + { + if( damageInfo->in.m_damageType != DAMAGE_UNRESISTABLE ) + { + // Apply the damage scalar (extra bonuses -- like strategy center defensive battle plan) + // And remember not to adjust unresistable damage, just like the armor code can't. + amount *= m_damageScalar; + } + } + + // sanity check the damage value -- can't apply negative damage + if( amount > 0.0f || damageInfo->in.m_kill ) + { + BodyDamageType oldState = m_curDamageState; + + //If the object is going to die, make sure we damage all remaining health. + if( damageInfo->in.m_kill ) + { + amount = m_currentHealth; + } + + if (!alreadyHandled) + { + // do the damage simplistic damage subtraction + internalChangeHealth( -amount, adjustConditions); + } + +#ifdef ALLOW_SURRENDER +//***************************************************************************************** +//***************************************************************************************** +//THIS CODE HAS BEEN DISABLED FOR THE MULTIPLAYER PLAY TEST!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!** +//***************************************************************************************** +// // if we were "killed" by surrender damage... +// if (damageInfo->in.m_damageType == DAMAGE_SURRENDER && m_currentHealth <= 0.0f && obj->isKindOf(KINDOF_CAN_SURRENDER)) +// { +// AIUpdateInterface* ai = obj->getAIUpdateInterface(); +// if (ai) +// { +// // do no damage, but make it surrender instead. +// m_currentHealth = m_prevHealth; +// const Object* killer = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID ); +// ai->setSurrendered(killer, true); +// return; +// } +// } +//***************************************************************************************** +//***************************************************************************************** +#endif + + // record the actual damage done from this, and when it happened + damageInfo->out.m_actualDamageDealt = amount; + damageInfo->out.m_actualDamageClipped = m_prevHealth - m_currentHealth; + + // then copy the whole DamageInfo struct for easy lookup + // (object pointer loses scope as soon as atteptdamage's caller ends) + // m_lastDamageTimestamp is initialized to FFFFFFFFFF, so doing a < compare is problematic. + // jba. + if (m_lastDamageTimestamp!=TheGameLogic->getFrame() && m_lastDamageTimestamp != TheGameLogic->getFrame()-1) { + m_lastDamageInfo = *damageInfo; + m_lastDamageCleared = false; + m_lastDamageTimestamp = TheGameLogic->getFrame(); + } else { + // Multiple damages applied in one/next frame. We prefer the one that tells who the attacker is. + Object *srcObj1 = TheGameLogic->findObjectByID(m_lastDamageInfo.in.m_sourceID); + Object *srcObj2 = TheGameLogic->findObjectByID(damageInfo->in.m_sourceID); + if (srcObj2) { + if (srcObj1) { + if (srcObj2->isKindOf(KINDOF_VEHICLE) || srcObj2->isKindOf(KINDOF_INFANTRY) || + srcObj2->isFactionStructure()) { + m_lastDamageInfo = *damageInfo; + m_lastDamageCleared = false; + m_lastDamageTimestamp = TheGameLogic->getFrame(); + } + } else { + m_lastDamageInfo = *damageInfo; + m_lastDamageCleared = false; + m_lastDamageTimestamp = TheGameLogic->getFrame(); + } + + } else { + // no change. + } + } + + // Notify the player that they have been attacked by this player + if (m_lastDamageInfo.in.m_sourceID != INVALID_ID) + { + Object *srcObj = TheGameLogic->findObjectByID(m_lastDamageInfo.in.m_sourceID); + if (srcObj) + { + Player *srcPlayer = srcObj->getControllingPlayer(); + obj->getControllingPlayer()->setAttackedBy(srcPlayer->getPlayerIndex()); + } + } + + // if our health has gone down then do run the damage module callback + if( m_currentHealth < m_prevHealth && doDamageModules) + { + for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m) + { + DamageModuleInterface* d = (*m)->getDamage(); + if (!d) + continue; + + d->onDamage( damageInfo ); + } + } + + if (m_curDamageState != oldState && adjustConditions) + { + for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m) + { + DamageModuleInterface* d = (*m)->getDamage(); + if (!d) + continue; + + d->onBodyDamageStateChange( damageInfo, oldState, m_curDamageState ); + } + + // @todo: This really feels like it should be in the TransitionFX lists. + if (m_curDamageState == BODY_DAMAGED) + { + AudioEventRTS damaged = *obj->getTemplate()->getSoundOnDamaged(); + damaged.setObjectID(obj->getID()); + TheAudio->addAudioEvent(&damaged); + } + else if (m_curDamageState == BODY_REALLYDAMAGED) + { + AudioEventRTS reallyDamaged = *obj->getTemplate()->getSoundOnReallyDamaged(); + reallyDamaged.setObjectID(obj->getID()); + TheAudio->addAudioEvent(&reallyDamaged); + } + + } + + // Should we play our fear sound? + if( (m_prevHealth / m_maxHealth) > YELLOW_DAMAGE_PERCENT && + (m_currentHealth / m_maxHealth) < YELLOW_DAMAGE_PERCENT && + (m_currentHealth > 0) ) + { + // 25% chance to play + if (GameLogicRandomValue(0, 99) < 25) + { + AudioEventRTS fearSound = *obj->getTemplate()->getVoiceFear(); + fearSound.setPosition( obj->getPosition() ); + fearSound.setPlayerIndex( obj->getControllingPlayer()->getPlayerIndex() ); + TheAudio->addAudioEvent(&fearSound); + } + } + + // check to see if we died + if( m_currentHealth <= 0 && m_prevHealth > 0 ) + { + // Give our killer credit for killing us, if there is one. + if( damager ) + { + damager->scoreTheKill( obj ); + } + + obj->onDie( damageInfo ); + } + } + + doDamageFX(damageInfo); + + // Damaged repulsable civilians scare (repulse) other civs. jba. + if( TheAI->getAiData()->m_enableRepulsors ) + { + if( obj->isKindOf( KINDOF_CAN_BE_REPULSED ) ) + { + obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_REPULSOR ) ); + } + } + + //Retaliate, even if I'm dead -- we'll still get my nearby friends to get revenge!!! + //Also only retaliate if we're controlled by a human player and the thing that attacked me + //is an enemy. + Player *controllingPlayer = obj->getControllingPlayer(); + if( controllingPlayer && controllingPlayer->isLogicalRetaliationModeEnabled() && controllingPlayer->getPlayerType() == PLAYER_HUMAN ) + { + if( shouldRetaliateAgainstAggressor(obj, damager)) + { + PartitionFilterPlayerAffiliation f1( controllingPlayer, ALLOW_ALLIES, true ); + PartitionFilterOnMap filterMapStatus; + PartitionFilter *filters[] = { &f1, &filterMapStatus, 0 }; + + + Real distance = TheAI->getAiData()->m_retaliateFriendsRadius + obj->getGeometryInfo().getBoundingCircleRadius(); + SimpleObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( obj->getPosition(), distance, FROM_CENTER_2D, filters, ITER_FASTEST ); + MemoryPoolObjectHolder hold( iter ); + for( Object *them = iter->first(); them; them = iter->next() ) + { + if (!shouldRetaliate(them)) { + continue; + } + AIUpdateInterface *ai = them->getAI(); + if (ai==NULL) { + continue; + } + //If we have AI and we're mobile, then assist! + if( !them->isKindOf( KINDOF_IMMOBILE )) + { + //But only if we can attack it! + CanAttackResult result = them->getAbleToAttackSpecificObject( ATTACK_NEW_TARGET, damager, CMD_FROM_AI ); + if( result == ATTACKRESULT_POSSIBLE_AFTER_MOVING || result == ATTACKRESULT_POSSIBLE ) + { + ai->aiGuardRetaliate( damager, them->getPosition(), NO_MAX_SHOTS_LIMIT, CMD_FROM_AI ); + } + } + } + } + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool ActiveBody::shouldRetaliateAgainstAggressor(Object *obj, Object *damager) +{ + /* This considers whether obj should invoke his friends to retaliate against damager. + Note that obj could be a structure, so we don't actually check whether obj will + retaliate, as in many cases he wouldn't. */ + if (damager==NULL) { + return false; + } + if (damager->isAirborneTarget()) { + return false; // Don't retaliate against aircraft. [8/25/2003] + } + if (damager->getRelationship( obj ) != ENEMIES) { + return false; // only retaliate against enemies. + } + Real distSqr = ThePartitionManager->getDistanceSquared(obj, damager, FROM_BOUNDINGSPHERE_2D); + if (distSqr > sqr(TheAI->getAiData()->m_maxRetaliateDistance)) { + return false; + } + // Only human players retaliate. [8/25/2003] + if (obj->getControllingPlayer()->getPlayerType() != PLAYER_HUMAN) { + return false; + } + // Drones never retaliate. [8/25/2003] + if (obj->isKindOf(KINDOF_DRONE)) { + return false; + } + return true; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool ActiveBody::shouldRetaliate(Object *obj) +{ + // Cannot retaliate objects dont. [8/25/2003] + if (obj->isKindOf(KINDOF_CANNOT_RETALIATE)) { + return false; + } + if (obj->isKindOf( KINDOF_IMMOBILE )) { + return false; + } + // Drones never retaliate. [8/25/2003] + if (obj->isKindOf(KINDOF_DRONE)) { + return false; + } + // Any unit that isn't idle won't retaliate. [8/25/2003] + if (obj->getAI()) { + if (!obj->getAI()->isIdle()) { + return false; + } + } else { + return false; // Non-ai can't retaliate. [8/26/2003] + } + // Stealthed units don't retaliate unless they're detected. [8/25/2003] + if ( obj->getStatusBits().test( OBJECT_STATUS_STEALTHED ) && + !obj->getStatusBits().test( OBJECT_STATUS_DETECTED ) ) { + return false; + } + // If we're using an ability, don't stop. [8/25/2003] + if (obj->testStatus(OBJECT_STATUS_IS_USING_ABILITY)) { + return false; + } + return true; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::attemptHealing( DamageInfo *damageInfo ) +{ + validateArmorAndDamageFX(); + + // sanity + if( damageInfo == NULL ) + return; + + if( damageInfo->in.m_damageType != DAMAGE_HEALING ) + { + // Healing and Damage are separate, so this shouldn't happen + attemptDamage( damageInfo ); + return; + } + + Object* obj = getObject(); + + // srj sez: sorry, once yer dead, yer dead. + // Special case for bridges, cause the system now things they're dead + ///@todo we need to figure out what has changed so we don't have to hack this (CBD 11-1-2002) + if( obj->isKindOf( KINDOF_BRIDGE ) == FALSE && + obj->isKindOf( KINDOF_BRIDGE_TOWER ) == FALSE && + obj->isEffectivelyDead()) + return; + + // initialize these, just in case we bail out early + damageInfo->out.m_actualDamageDealt = 0.0f; + damageInfo->out.m_actualDamageClipped = 0.0f; + + Real amount = m_curArmor.adjustDamage(damageInfo->in.m_damageType, damageInfo->in.m_amount); + + // sanity check the damage value -- can't apply negative healing + if( amount > 0.0f ) + { + BodyDamageType oldState = m_curDamageState; + + // do the damage simplistic damage ADDITION + internalChangeHealth( amount ); + + // record the actual damage done from this, and when it happened + damageInfo->out.m_actualDamageDealt = amount; + damageInfo->out.m_actualDamageClipped = m_prevHealth - m_currentHealth; + + //then copy the whole DamageInfo struct for easy lookup + //(object pointer loses scope as soon as atteptdamage's caller ends) + m_lastDamageInfo = *damageInfo; + m_lastDamageCleared = false; + m_lastDamageTimestamp = TheGameLogic->getFrame(); + m_lastHealingTimestamp = TheGameLogic->getFrame(); + + // if our health has gone UP then do run the damage module callback + if( m_currentHealth > m_prevHealth ) + { + for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m) + { + DamageModuleInterface* d = (*m)->getDamage(); + if (!d) + continue; + + d->onHealing( damageInfo ); + } + } + + if (m_curDamageState != oldState) + { + for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m) + { + DamageModuleInterface* d = (*m)->getDamage(); + if (!d) + continue; + + d->onBodyDamageStateChange( damageInfo, oldState, m_curDamageState ); + } + } + } + + doDamageFX(damageInfo); +} + +//------------------------------------------------------------------------------------------------- +/** Simple setting of the health value, it does *NOT* track any transition + * states for the event of "damage" or the event of "death". */ +//------------------------------------------------------------------------------------------------- +void ActiveBody::setInitialHealth(Int initialPercent) +{ + + // save the current health as the previous health + m_prevHealth = m_currentHealth; + + Real factor = initialPercent/100.0f; + Real newHealth = factor * m_initialHealth; + + // change the health to the requested percentage. + internalChangeHealth(newHealth - m_currentHealth); + +} + +//------------------------------------------------------------------------------------------------- +/** Simple setting of the health value, it does *NOT* track any transition + * states for the event of "damage" or the event of "death". */ +//------------------------------------------------------------------------------------------------- +void ActiveBody::setMaxHealth( Real maxHealth, MaxHealthChangeType healthChangeType ) +{ + Real prevMaxHealth = m_maxHealth; + m_maxHealth = maxHealth; + m_initialHealth = maxHealth; + + switch( healthChangeType ) + { + case PRESERVE_RATIO: + { + //400/500 (80%) + 100 becomes 480/600 (80%) + //200/500 (40%) - 100 becomes 160/400 (40%) + Real ratio = m_currentHealth / prevMaxHealth; + Real newHealth = maxHealth * ratio; + internalChangeHealth( newHealth - m_currentHealth ); + break; + } + case ADD_CURRENT_HEALTH_TOO: + { + //Add the same amount that we are adding to the max health. + //This could kill you if max health is reduced (if we ever have that ability to add buffer health like in D&D) + //400/500 (80%) + 100 becomes 500/600 (83%) + //200/500 (40%) - 100 becomes 100/400 (25%) + internalChangeHealth( maxHealth - prevMaxHealth ); + break; + } + case SAME_CURRENTHEALTH: + //do nothing + break; + + case FULLY_HEAL: + { + // Set current to the new Max. + //400/500 (80%) + 100 becomes 600/600 (100%) + //200/500 (40%) - 100 becomes 400/400 (100%) + internalChangeHealth(m_maxHealth - m_currentHealth); + break; + } + } + + // + // when max health is getting clipped to a lower value, if our current health + // value is now outside of the max health range we will set it back down to the + // new cap. Note that we are *NOT* going through any healing or damage methods here + // and are doing a direct set + // + if( m_currentHealth > maxHealth ) + { + internalChangeHealth( maxHealth - m_currentHealth ); + } + +} + +// ------------------------------------------------------------------------------------------------ +/** Given the current damage state of the object, evaluate the visual model conditions + * that have a visual impact on the object */ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::evaluateVisualCondition() +{ + + Drawable* draw = getObject()->getDrawable(); + if (draw) + { + draw->reactToBodyDamageStateChange(m_curDamageState); + } + + // + // destroy any particle systems that were attached to our body for the old state + // and create new particle systems for the new state + // + updateBodyParticleSystems(); + +} + +// ------------------------------------------------------------------------------------------------ +/** Create up to maxSystems particle systems of type particleSystemName and attach to bones + * specified by the bone base name. If there are more bones than maxSystems then the + * bones will be randomly selected */ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::createParticleSystems( const AsciiString &boneBaseName, + const ParticleSystemTemplate *systemTemplate, + Int maxSystems ) +{ + Object *us = getObject(); + + // sanity + if( systemTemplate == NULL ) + return; + + // get the bones + enum { MAX_BONES = 16 }; + Coord3D bonePositions[ MAX_BONES ]; + Int numBones = us->getMultiLogicalBonePosition( boneBaseName.str(), + MAX_BONES, + bonePositions, + NULL, + FALSE ); + + // if no bones found nothing else to do + if( numBones == 0 ) + return; + + // + // if we don't have enough bones to go up to maxSystems, we will change maxSystems to be + // the number of bones we actually have (we don't want systems doubling up on bones) + // + if( numBones < maxSystems ) + maxSystems = numBones; + + // + // create an array that we'll use to mark which bone positions have already been used, + // this is necessary when we have more bones than particle systems we're going to + // create, in which case we place the particle systems at random bone locations + // but don't want to repeat any + // + Bool usedBoneIndices[ MAX_BONES ] = { FALSE }; + + // create the particle systems + const Coord3D *pos; + for( Int i = 0; i < maxSystems; ++i ) + { + + // pick a bone index to place this particle system at + // MDC: moving to GameLogicRandomValue. This does not need to be synced, but having it so makes searches *so* much nicer. + // DTEH: Moved back to GameClientRandomValue because of desync problems. July 27th 2003. + Int boneIndex = GameClientRandomValue( 0, maxSystems - i - 1 ); + + // find the actual bone location to use and mark that bone index as used + Int count = 0; + Int j = 0; + for( ; j < numBones; j++ ) + { + + // ignore bone positions that have already been used + if( usedBoneIndices[ j ] == TRUE ) + continue; + + // this spot is available, if count == boneIndex then use this index + if( count == boneIndex ) + { + + pos = &bonePositions[ j ]; + usedBoneIndices[ j ] = TRUE; + break; // exit for j + + } // end if + else + { + + // we won't use this index, increment count until we find a suitable index to use + ++count; + + } // end else + + } // end for, j + + // sanity + DEBUG_ASSERTCRASH( j != numBones, + ("ActiveBody::createParticleSystems, Unable to select particle system index\n") ); + + // create particle system here + ParticleSystem *particleSystem = TheParticleSystemManager->createParticleSystem( systemTemplate ); + if( particleSystem ) + { + + // set the position of the particle system in local object space + particleSystem->setPosition( pos ); + + // attach particle system to object + particleSystem->attachToObject( us ); + + // create a new body particle system entry and keep this particle system in it + BodyParticleSystem *newEntry = newInstance(BodyParticleSystem); + newEntry->m_particleSystemID = particleSystem->getSystemID(); + newEntry->m_next = m_particleSystems; + m_particleSystems = newEntry; + + } // end if + + } // end for, i + +} // end createParticleSystems + +// ------------------------------------------------------------------------------------------------ +/** Delete all the body particle systems */ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::deleteAllParticleSystems( void ) +{ + BodyParticleSystem *nextBodySystem; + ParticleSystem *particleSystem; + + while( m_particleSystems ) + { + + // get this particle system + particleSystem = TheParticleSystemManager->findParticleSystem( m_particleSystems->m_particleSystemID ); + if( particleSystem ) + particleSystem->destroy(); + + // get next system in the body + nextBodySystem = m_particleSystems->m_next; + + // destroy this entry + m_particleSystems->deleteInstance(); + + // set the body systems head to the next + m_particleSystems = nextBodySystem; + + } // end while + +} // end deleteAllParticleSystems + +// ------------------------------------------------------------------------------------------------ +/* This function is called on state changes only. Body Type or Aflameness. */ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::updateBodyParticleSystems( void ) +{ + static const ParticleSystemTemplate *fireSmallTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoFireParticleSmallSystem ); + static const ParticleSystemTemplate *fireMediumTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoFireParticleMediumSystem ); + static const ParticleSystemTemplate *fireLargeTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoFireParticleLargeSystem ); + static const ParticleSystemTemplate *smokeSmallTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoSmokeParticleSmallSystem ); + static const ParticleSystemTemplate *smokeMediumTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoSmokeParticleMediumSystem ); + static const ParticleSystemTemplate *smokeLargeTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoSmokeParticleLargeSystem ); + static const ParticleSystemTemplate *aflameTemplate = TheParticleSystemManager->findTemplate( TheGlobalData->m_autoAflameParticleSystem ); + Int countModifier; + const ParticleSystemTemplate *fireSmall; + const ParticleSystemTemplate *fireMedium; + const ParticleSystemTemplate *fireLarge; + const ParticleSystemTemplate *smokeSmall; + const ParticleSystemTemplate *smokeMedium; + const ParticleSystemTemplate *smokeLarge; + + // + // when we're aflame, we use a slightly different set of particle systems that are + // auto created that lends itself to more fire and bigger fire + // + if( getObject()->testStatus( OBJECT_STATUS_AFLAME ) ) + { + + fireSmall = fireMediumTemplate; // small fire becomes medium fire + fireMedium = fireLargeTemplate; // medium fire becomes large fire + fireLarge = fireLargeTemplate; // large fire stays large + smokeSmall = fireSmallTemplate; // small smoke becomes small fire + smokeMedium = fireSmallTemplate; // medium smoke becomes small fire + smokeLarge = fireSmallTemplate; // large smoke becomes small fire + + // we get to make more of them all too + countModifier = 2; + + } // end if + else + { + + // use regular templates + fireSmall = fireSmallTemplate; + fireMedium = fireMediumTemplate; + fireLarge = fireLargeTemplate; + smokeSmall = smokeSmallTemplate; + smokeMedium = smokeMediumTemplate; + smokeLarge = smokeLargeTemplate; + + // we make just the normal amount of these + countModifier = 1; + + } // end else + + // + // remove any particle systems we have currently in the list in favor of any new ones + // that we're going to autopopulate ourselves with + // + deleteAllParticleSystems(); + + // + // create particle systems for the new body state + // + + // small fire bones + createParticleSystems( TheGlobalData->m_autoFireParticleSmallPrefix, + fireSmall, TheGlobalData->m_autoFireParticleSmallMax * countModifier ); + + // medium fire bones + createParticleSystems( TheGlobalData->m_autoFireParticleMediumPrefix, + fireMedium, TheGlobalData->m_autoFireParticleMediumMax * countModifier ); + + // large fire bones + createParticleSystems( TheGlobalData->m_autoFireParticleLargePrefix, + fireLarge, TheGlobalData->m_autoFireParticleLargeMax * countModifier ); + + // small smoke bones + createParticleSystems( TheGlobalData->m_autoSmokeParticleSmallPrefix, + smokeSmall, TheGlobalData->m_autoSmokeParticleSmallMax * countModifier ); + + // medium smoke bones + createParticleSystems( TheGlobalData->m_autoSmokeParticleMediumPrefix, + smokeMedium, TheGlobalData->m_autoSmokeParticleMediumMax * countModifier ); + + // large smoke bones + createParticleSystems( TheGlobalData->m_autoSmokeParticleLargePrefix, + smokeLarge, TheGlobalData->m_autoSmokeParticleLargeMax * countModifier ); + + // actively on fire + if( getObject()->testStatus( OBJECT_STATUS_AFLAME ) ) + createParticleSystems( TheGlobalData->m_autoAflameParticlePrefix, + aflameTemplate, TheGlobalData->m_autoAflameParticleMax * countModifier ); + +} // end updatebodyParticleSystems + +//------------------------------------------------------------------------------------------------- +/** Simple changing of the health value, it does *NOT* track any transition + * states for the event of "damage" or the event of "death". If you + * with to kill an object and give these modules a chance to react + * to that event use the proper damage method calls. + * No game logic should go in here. This is the low level math and flag maintenance. + * Game stuff goes in attemptDamage and attemptHealing. +*/ +//------------------------------------------------------------------------------------------------- +void ActiveBody::internalChangeHealth( Real delta, Bool changeModelCondition) +{ + // save the current health as the previous health + m_prevHealth = m_currentHealth; + + // change the health by the delta, it can be positive or negative + m_currentHealth += delta; + + // high end cap + Real maxHealth = m_maxHealth; + if( m_currentHealth > maxHealth ) + m_currentHealth = maxHealth; + + // low end cap + const Real lowEndCap = 0.0f; // low end cap for health, don't go below this + if( m_currentHealth < lowEndCap ) + m_currentHealth = lowEndCap; + + if (changeModelCondition) { + // recalc the damage state + BodyDamageType oldState = m_curDamageState; + setCorrectDamageState(); + + // if our state has changed + if (m_curDamageState != oldState) + { + + // + // show a visual change in the model for the damage state, we do not show visual changes + // for damage states when things are under construction because we just don't have + // all the art states for that during buildup animation + // + if (!getObject()->getStatusBits().test(OBJECT_STATUS_UNDER_CONSTRUCTION)) + evaluateVisualCondition(); + + } // end if + } + + // mark the bit according to our health. (if our AI is dead but our health improves, it will + // still re-flag this bit in the AIDeadState every frame.) + getObject()->setEffectivelyDead(m_currentHealth <= 0); + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::internalAddSubdualDamage( Real delta ) +{ + const ActiveBodyModuleData *data = getActiveBodyModuleData(); + + m_currentSubdualDamage += delta; + m_currentSubdualDamage = min(m_currentSubdualDamage, data->m_subdualDamageCap); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::internalAddChronoDamage(Real delta) +{ + // Just increment, we don't need a cap. we kill once maxHealth is reached + //Real chronoDamageCap = m_maxHealth * 2.0; + m_currentChronoDamage += delta; + //m_currentChronoDamage = min(m_currentChronoDamage, chronoDamageCap); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool ActiveBody::canBeSubdued() const +{ + // Any body with subdue listings can be subdued. + return getActiveBodyModuleData()->m_subdualDamageCap > 0; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::onSubdualChange( Bool isNowSubdued ) +{ + if( !getObject()->isKindOf(KINDOF_PROJECTILE) ) + { + Object *me = getObject(); + + if( isNowSubdued ) + { + me->setDisabled(DISABLED_SUBDUED); + + ContainModuleInterface *contain = me->getContain(); + if ( contain ) + contain->orderAllPassengersToIdle( CMD_FROM_AI ); + + } + else + { + me->clearDisabled(DISABLED_SUBDUED); + + if( me->isKindOf( KINDOF_FS_INTERNET_CENTER ) ) + { + //Kris: October 20, 2003 - Patch 1.01 + //Any unit inside an internet center is a hacker! Order + //them to start hacking again. + ContainModuleInterface *contain = me->getContain(); + if ( contain ) + contain->orderAllPassengersToHackInternet( CMD_FROM_AI ); + } + } + } + else if( isNowSubdued )// There is no coming back from being jammed, and projectiles can't even heal, but this makes it clear. + { + ProjectileUpdateInterface *pui = getObject()->getProjectileUpdateInterface(); + if( pui ) + { + pui->projectileNowJammed(); + } + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::onSubdualChronoChange( Bool isNowSubdued ) +{ + Object *me = getObject(); + + if( isNowSubdued ) + { + me->setDisabled(DISABLED_CHRONO); + + // Apply Chrono Particles + applyChronoParticleSystems(); + + m_chronoDisabledSoundLoop = TheAudio->getMiscAudio()->m_chronoDisabledSoundLoop; + m_chronoDisabledSoundLoop.setObjectID(me->getID()); + m_chronoDisabledSoundLoop.setPlayingHandle(TheAudio->addAudioEvent(&m_chronoDisabledSoundLoop)); + + ContainModuleInterface *contain = me->getContain(); + if ( contain ) + contain->orderAllPassengersToIdle( CMD_FROM_AI ); + } + else + { + me->clearDisabled(DISABLED_CHRONO); + + // Remove Chrono Particles, i.e. restore default particles + updateBodyParticleSystems(); + + TheAudio->removeAudioEvent(m_chronoDisabledSoundLoop.getPlayingHandle()); + + if (me->isKindOf(KINDOF_FS_INTERNET_CENTER)) + { + //Kris: October 20, 2003 - Patch 1.01 + //Any unit inside an internet center is a hacker! Order + //them to start hacking again. + ContainModuleInterface* contain = me->getContain(); + if (contain) + contain->orderAllPassengersToHackInternet(CMD_FROM_AI); + } + } +} + +// ------------------------------------------------------------------------------------------------ +/* This function is called on state changes only. Body Type or Aflameness. */ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::applyChronoParticleSystems(void) +{ + deleteAllParticleSystems(); + + static const ParticleSystemTemplate* chronoEffectsLargeTemplate = TheParticleSystemManager->findTemplate(TheGlobalData->m_chronoDisableParticleSystemLarge); + static const ParticleSystemTemplate* chronoEffectsMediumTemplate = TheParticleSystemManager->findTemplate(TheGlobalData->m_chronoDisableParticleSystemMedium); + static const ParticleSystemTemplate* chronoEffectsSmallTemplate = TheParticleSystemManager->findTemplate(TheGlobalData->m_chronoDisableParticleSystemSmall); + + const ParticleSystemTemplate* chronoEffects; + + // TODO: select particles + Object* obj = getObject(); + + if (obj->isKindOf(KINDOF_INFANTRY)) { + chronoEffects = chronoEffectsSmallTemplate; + } + else if (obj->isKindOf(KINDOF_STRUCTURE)) { + chronoEffects = chronoEffectsLargeTemplate; + } + // Use Medium as default + else { + chronoEffects = chronoEffectsMediumTemplate; + } + + ParticleSystem* particleSystem = TheParticleSystemManager->createParticleSystem(chronoEffects); + if (particleSystem) + { + // set the position of the particle system in local object space + // particleSystem->setPosition(obj->getPosition()); + + // attach particle system to object + particleSystem->attachToObject(obj); + + // Scale particle count based on size + Real x = obj->getGeometryInfo().getMajorRadius(); + Real y = obj->getGeometryInfo().getMinorRadius(); + Real z = obj->getGeometryInfo().getMaxHeightAbovePosition() * 0.5; + particleSystem->setEmissionBoxHalfSize(x, y, z); + //Real size = x * y; + //particleSystem->setBurstCountMultiplier(MAX(1.0, sqrt(size * 0.02f))); // these are somewhat tweaked right now + //particleSystem->setBurstDelayMultiplier(MIN(5.0, sqrt(500.0f / size))); + + // create a new body particle system entry and keep this particle system in it + BodyParticleSystem* newEntry = newInstance(BodyParticleSystem); + newEntry->m_particleSystemID = particleSystem->getSystemID(); + newEntry->m_next = m_particleSystems; + m_particleSystems = newEntry; + + // DEBUG_LOG(("ActiveBody::applyChronoParticleSystems - created particleSystem.\n")); + } + else { + // DEBUG_LOG(("ActiveBody::applyChronoParticleSystems - Failed to create particleSystem?!\n")); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool ActiveBody::isSubduedChrono() const +{ + return (m_maxHealth * TheGlobalData->m_chronoDamageDisableThreshold) <= m_currentChronoDamage; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool ActiveBody::isSubdued() const +{ + return m_maxHealth <= m_currentSubdualDamage; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Real ActiveBody::getHealth() const +{ + return m_currentHealth; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +BodyDamageType ActiveBody::getDamageState() const +{ + return m_curDamageState; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Real ActiveBody::getMaxHealth() const +{ + return m_maxHealth; +} ///< return max health + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +UnsignedInt ActiveBody::getSubdualDamageHealRate() const +{ + return getActiveBodyModuleData()->m_subdualDamageHealRate; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Real ActiveBody::getSubdualDamageHealAmount() const +{ + return getActiveBodyModuleData()->m_subdualDamageHealAmount; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool ActiveBody::hasAnySubdualDamage() const +{ + return m_currentSubdualDamage > 0; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +UnsignedInt ActiveBody::getChronoDamageHealRate() const +{ + return TheGlobalData->m_chronoDamageHealRate; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Real ActiveBody::getChronoDamageHealAmount() const +{ + // DEBUG_LOG(("ActiveBody::getChronoDamageHealAmount() - maxHealth = %f\n", m_maxHealth)); + return m_maxHealth * TheGlobalData->m_chronoDamageHealAmount; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool ActiveBody::hasAnyChronoDamage() const +{ + return m_currentChronoDamage > 0; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Real ActiveBody::getInitialHealth() const +{ + return m_initialHealth; +} // return initial health + + +// ------------------------------------------------------------------------------------------------ +/** Set or unset the overridable indestructible flag in the body */ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::setIndestructible( Bool indestructible ) +{ + + m_indestructible = indestructible; + + // for bridges, we mirror this state on its towers + Object *us = getObject(); + if( us->isKindOf( KINDOF_BRIDGE ) ) + { + BridgeBehaviorInterface *bbi = BridgeBehavior::getBridgeBehaviorInterfaceFromObject( us ); + if( bbi ) + { + Object *tower; + + // get tower + for( Int i = 0; i < BRIDGE_MAX_TOWERS; ++i ) + { + + tower = TheGameLogic->findObjectByID( bbi->getTowerID( BridgeTowerType(i) ) ); + if( tower ) + { + BodyModuleInterface *body = tower->getBodyModule(); + + if( body ) + body->setIndestructible( indestructible ); + + } // end if + + } // end for, i + + } // end if + + } // end if + +} // end setIndestructible + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void ActiveBody::onVeterancyLevelChanged( VeterancyLevel oldLevel, VeterancyLevel newLevel, Bool provideFeedback ) +{ + if (oldLevel == newLevel) + return; + + if (oldLevel < newLevel) + { + if( provideFeedback ) + { + AudioEventRTS veterancyChanged; + switch (newLevel) + { + case LEVEL_VETERAN: + veterancyChanged = *getObject()->getTemplate()->getSoundPromotedVeteran(); + break; + case LEVEL_ELITE: + veterancyChanged = *getObject()->getTemplate()->getSoundPromotedElite(); + break; + case LEVEL_HEROIC: + veterancyChanged = *getObject()->getTemplate()->getSoundPromotedHero(); + break; + } + + veterancyChanged.setObjectID(getObject()->getID()); + TheAudio->addAudioEvent(&veterancyChanged); + } + + //Also mark the UI dirty -- incase the object is selected or contained. + Object *obj = getObject(); + Drawable *draw = TheInGameUI->getFirstSelectedDrawable(); + if( draw ) + { + Object *checkOwner = draw->getObject(); + if( checkOwner == obj ) + { + //Our selected object has been promoted! + TheControlBar->markUIDirty(); + } + else + { + const Object *containedBy = obj->getContainedBy(); + if( containedBy && TheInGameUI->getSelectCount() == 1 ) + { + Object *checkOwner = draw->getObject(); + if( checkOwner == containedBy ) + { + //But only if the contained by object is containing me! + TheControlBar->markUIDirty(); + } + } + } + } + } + + Real oldBonus = TheGlobalData->m_healthBonus[oldLevel]; + Real newBonus = TheGlobalData->m_healthBonus[newLevel]; + Real mult = newBonus / oldBonus; + + // get this before calling setMaxHealth, since it can clip curHealth + //Real newHealth = m_currentHealth * mult; + + // change the max + setMaxHealth(m_maxHealth * mult, PRESERVE_RATIO ); + + // now change the cur (setMaxHealth now handles it) + //internalChangeHealth( newHealth - m_currentHealth ); + + switch (newLevel) + { + case LEVEL_REGULAR: + clearArmorSetFlag(ARMORSET_VETERAN); + clearArmorSetFlag(ARMORSET_ELITE); + clearArmorSetFlag(ARMORSET_HERO); + break; + case LEVEL_VETERAN: + setArmorSetFlag(ARMORSET_VETERAN); + clearArmorSetFlag(ARMORSET_ELITE); + clearArmorSetFlag(ARMORSET_HERO); + break; + case LEVEL_ELITE: + clearArmorSetFlag(ARMORSET_VETERAN); + setArmorSetFlag(ARMORSET_ELITE); + clearArmorSetFlag(ARMORSET_HERO); + break; + case LEVEL_HEROIC: + clearArmorSetFlag(ARMORSET_VETERAN); + clearArmorSetFlag(ARMORSET_ELITE); + setArmorSetFlag(ARMORSET_HERO); + break; + } +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::setAflame( Bool ) +{ + + // + // All this does now is act like a major body state change. It is called after Aflame has been + // set or cleared as an Object Status + // + updateBodyParticleSystems(); + +} +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::overrideDamageFX(DamageFX* damageFX) +{ + if (damageFX != NULL) { + m_curDamageFX = damageFX; + m_damageFXOverride = true; + } + else { + m_curDamageFX = NULL; + m_damageFXOverride = false; + + // Restore DamageFX from current armorset + const ArmorTemplateSet* set = getObject()->getTemplate()->findArmorTemplateSet(m_curArmorSetFlags); + if (set) + { + m_curDamageFX = set->getDamageFX(); + } + } + //DEBUG_LOG((">>>ActiveBody: overrideDamageFX - new m_curDamageFX = %d, m_damageFXOverride = %d\n", + // m_curDamageFX, m_damageFXOverride)); +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::crc( Xfer *xfer ) +{ + + // extend base class + BodyModule::crc( xfer ); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // base class + BodyModule::xfer( xfer ); + + // current health + xfer->xferReal( &m_currentHealth ); + + xfer->xferReal( &m_currentSubdualDamage ); + + // previous health + xfer->xferReal( &m_prevHealth ); + + // max health + xfer->xferReal( &m_maxHealth ); + + // initial health + xfer->xferReal( &m_initialHealth ); + + // current damage state + xfer->xferUser( &m_curDamageState, sizeof( BodyDamageType ) ); + + // next damage fx time + xfer->xferUnsignedInt( &m_nextDamageFXTime ); + + // last damage fx done + xfer->xferUser( &m_lastDamageFXDone, sizeof( DamageType ) ); + + // last damage info + xfer->xferSnapshot( &m_lastDamageInfo ); + + // last damage timestamp + xfer->xferUnsignedInt( &m_lastDamageTimestamp ); + + // last damage timestamp + xfer->xferUnsignedInt( &m_lastHealingTimestamp ); + + // front crushed + xfer->xferBool( &m_frontCrushed ); + + // back crushed + xfer->xferBool( &m_backCrushed ); + + // last damaged cleared + xfer->xferBool( &m_lastDamageCleared ); + + // indestructible + xfer->xferBool( &m_indestructible ); + + // particle system count + BodyParticleSystem *system; + UnsignedShort particleSystemCount = 0; + for( system = m_particleSystems; system; system = system->m_next ) + particleSystemCount++; + xfer->xferUnsignedShort( &particleSystemCount ); + + // particle systems + if( xfer->getXferMode() == XFER_SAVE ) + { + + // walk the particle systems + for( system = m_particleSystems; system; system = system->m_next ) + { + + // write particle system ID + xfer->xferUser( &system->m_particleSystemID, sizeof( ParticleSystemID ) ); + + } // end for, system + + } // end if, save + else + { + ParticleSystemID particleSystemID; + + // the list should be empty at this time + if( m_particleSystems != NULL ) + { + + DEBUG_CRASH(( "ActiveBody::xfer - m_particleSystems should be empty, but is not\n" )); + throw SC_INVALID_DATA; + + } // end if + + // read all data elements + BodyParticleSystem *newEntry; + for( UnsignedShort i = 0; i < particleSystemCount; ++i ) + { + + // read particle system ID + xfer->xferUser( &particleSystemID, sizeof( ParticleSystemID ) ); + + // allocate entry and add to list + newEntry = newInstance(BodyParticleSystem); + newEntry->m_particleSystemID = particleSystemID; + newEntry->m_next = m_particleSystems; // the list will be reversed, but we don't care + m_particleSystems = newEntry; + + } // end for, i + + } // end else, load + + // armor set flags + m_curArmorSetFlags.xfer( xfer ); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void ActiveBody::loadPostProcess( void ) +{ + + // extend base class + BodyModule::loadPostProcess(); + +} // end loadPostProcess diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp index b935559a4ed..5658e8b600e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp @@ -1,2844 +1,2844 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: Locomotor.cpp /////////////////////////////////////////////////////////////////////////////// -// Author: Steven Johnson, Feb 2002 -// Desc: Locomotor descriptions -/////////////////////////////////////////////////////////////////////////////////////////////////// - - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -#define DEFINE_SURFACECATEGORY_NAMES -#define DEFINE_LOCO_Z_NAMES -#define DEFINE_LOCO_APPEARANCE_NAMES - -#include "Common/INI.h" -#include "GameLogic/GameLogic.h" -#include "GameLogic/PartitionManager.h" -#include "GameLogic/Locomotor.h" -#include "GameLogic/Object.h" -#include "GameLogic/AI.h" -#include "GameLogic/AIPathfind.h" -#include "GameLogic/Module/PhysicsUpdate.h" -#include "GameLogic/Module/BodyModule.h" -#include "GameLogic/Module/AIUpdate.h" - -#ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -static const Real DONUT_TIME_DELAY_SECONDS=2.5f; -static const Real DONUT_DISTANCE=4.0*PATHFIND_CELL_SIZE_F; - - -#define MAX_BRAKING_FACTOR 5.0f -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -LocomotorStore *TheLocomotorStore = NULL; ///< the Locomotor store definition - -const Real BIGNUM = 99999.0f; - -static const char *TheLocomotorPriorityNames[] = -{ - "MOVES_BACK", - "MOVES_MIDDLE", - "MOVES_FRONT", - - NULL -}; - - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------------- -static Real calcSlowDownDist(Real curSpeed, Real desiredSpeed, Real maxBraking) -{ - Real delta = curSpeed - desiredSpeed; - if (delta <= 0) - return 0.0f; - - Real dist = (sqr(delta) / fabs(maxBraking)) * 0.5f; - - // use a little fudge so that things can stop "on a dime" more easily... - const Real FUDGE = 1.05f; - return dist * FUDGE; -} - -//----------------------------------------------------------------------------- -inline Bool isNearlyZero(Real a) -{ - const Real TINY_EPSILON = 0.001f; - return fabs(a) < TINY_EPSILON; -} - -//----------------------------------------------------------------------------- -inline Bool isNearly(Real a, Real val) -{ - const Real TINY_EPSILON = 0.001f; - return fabs(a - val) < TINY_EPSILON; -} - -//----------------------------------------------------------------------------- -// return the angle delta (in 3-space) we turned. -static Real tryToRotateVector3D( - Real maxAngle, // if negative, it's a percent (0...1) of the dist to rotate 'em - const Vector3& inCurDir, - const Vector3& inGoalDir, - Vector3& actualDir -) -{ - if (isNearlyZero(maxAngle)) - { - actualDir = inCurDir; - return 0.0f; - } - - Vector3 curDir = inCurDir; - curDir.Normalize(); - - Vector3 goalDir = inGoalDir; - goalDir.Normalize(); - - // dot of two unit vectors is cos of angle between them. - Real cosine = Vector3::Dot_Product(curDir, goalDir); - // bound it in case of numerical error - Real angleBetween = (Real)ACos(clamp(-1.0f, cosine, 1.0f)); - - if (maxAngle < 0) - { - maxAngle = -maxAngle * angleBetween; - if (isNearlyZero(maxAngle)) - { - actualDir = inCurDir; - return 0.0f; - } - } - - if (fabs(angleBetween) <= maxAngle) - { - // close enough - actualDir = goalDir; - } - else - { - // nah, try as much as we can in the right dir. - // we need to rotate around the axis perpendicular to these two vecs. - // but: cross of two vectors is the perpendicular axis! -#ifdef ALLOW_TEMPORARIES - Vector3 objCrossGoal = Vector3::Cross_Product(curDir, goalDir); - objCrossGoal.Normalize(); -#else - Vector3 objCrossGoal; - Vector3::Normalized_Cross_Product(curDir, goalDir, &objCrossGoal); -#endif - - angleBetween = maxAngle; - Matrix3D rotMtx(objCrossGoal, angleBetween); - actualDir = rotMtx.Rotate_Vector(curDir); - } - - return angleBetween; -} - -//------------------------------------------------------------------------------------------------- -static Real tryToOrientInThisDirection3D(Object* obj, Real maxTurnRate, const Vector3& desiredDir) -{ - Vector3 actualDir; - Real relAngle = tryToRotateVector3D(maxTurnRate, obj->getTransformMatrix()->Get_X_Vector(), desiredDir, actualDir); - if (relAngle != 0.0f) - { - Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z); - - Matrix3D newXform; - newXform.buildTransformMatrix( objPos, actualDir ); - - obj->setTransformMatrix( &newXform ); - } - return relAngle; -} - -//------------------------------------------------------------------------------------------------- -inline Real tryToOrientInThisDirection3D(Object* obj, Real maxTurnRate, const Coord3D* dir) -{ - return tryToOrientInThisDirection3D(obj, maxTurnRate, Vector3(dir->x, dir->y, dir->z)); -} - -//----------------------------------------------------------------------------- -static void calcDirectionToApplyThrust( - const Object* obj, - const PhysicsBehavior* physics, - const Coord3D& ingoalPos, - Real maxAccel, - Vector3& goalDir -) -{ - /* - our meta-goal here is to calculate the direction we should apply our motive force - in order to minimize the angle between (our velocity) and (direction towards goalpos). - - this is complicated by the fact that we generally have an intrinsic velocity already, - that must be accounted for, and by the fact that we can only apply force in our - forward-x-direction (with a thrust-angle-range), and (due to limited range) might not - be able to apply the force in the optimal direction! - */ - - // convert to Vector3, to use all its handy stuff - Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z); - Vector3 goalPos(ingoalPos.x, ingoalPos.y, ingoalPos.z); - - Vector3 vecToGoal = goalPos - objPos; - if (isNearlyZero(vecToGoal.Length2())) - { - // goal pos is essentially same as current pos, so just stay the same & return - goalDir = obj->getTransformMatrix()->Get_X_Vector(); - return; - } - - /* - get our cur vel into a useful Vector3 form - */ - Vector3 curVel(physics->getVelocity()->x, physics->getVelocity()->y, physics->getVelocity()->z); - - // add gravity to our vel so that we account for it in our calcs - curVel.Z += TheGlobalData->m_gravity; - - Bool foundSolution = false; - Real distToGoalSqr = vecToGoal.Length2(); - Real distToGoal = sqrt(distToGoalSqr); - Real curVelMagSqr = curVel.Length2(); - Real curVelMag = sqrt(curVelMagSqr); - Real maxAccelSqr = sqr(maxAccel); - - Real denom = curVelMagSqr - maxAccelSqr; - if (!isNearlyZero(denom)) - { - // solve the (greatly simplified) quadratic... - Real t = (distToGoal * (curVelMag + maxAccel)) / denom; - Real t2 = (distToGoal * (curVelMag - maxAccel)) / denom; - if (t >= 0 || t2 >= 0) - { - // choose the smallest positive t. - if (t < 0 || (t2 >= 0 && t2 < t)) - t = t2; - - // plug it in. - if (!isNearlyZero(t)) - { - goalDir.X = (vecToGoal.X / t) - curVel.X; - goalDir.Y = (vecToGoal.Y / t) - curVel.Y; - goalDir.Z = (vecToGoal.Z / t) - curVel.Z; - goalDir.Normalize(); - foundSolution = true; - } - } - } - if (!foundSolution) - { - // Doh... no (useful) solution. revert to dumb. - goalDir = vecToGoal; - goalDir.Normalize(); - } - -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------------- -LocomotorTemplate::LocomotorTemplate() -{ - // these values mean "make the same as undamaged if not explicitly specified" - m_maxSpeedDamaged = -1.0f; - m_maxTurnRateDamaged = -1.0f; - m_accelerationDamaged = -1.0f; - m_liftDamaged = -1.0f; - - m_surfaces = 0; - m_maxSpeed = 0.0f; - m_maxTurnRate = 0.0f; - m_acceleration = 0.0f; - m_lift = 0.0f; - m_braking = BIGNUM; - m_minSpeed = 0.0f; - m_minTurnSpeed = BIGNUM; - m_behaviorZ = Z_NO_Z_MOTIVE_FORCE; - m_appearance = LOCO_OTHER; - m_movePriority = LOCO_MOVES_MIDDLE; - m_preferredHeight = 0; - m_preferredHeightDamping = 1.0f; - m_circlingRadius = 0; - - m_maxThrustAngle = 0; - m_speedLimitZ = 999999.0f; - m_extra2DFriction = 0.0f; - - m_accelPitchLimit = 0; - m_decelPitchLimit = 0; - m_bounceKick = 0; - -// m_pitchStiffness = 0; -// m_rollStiffness = 0; -// m_pitchDamping = 0; -// m_rollDamping = 0; -// it's highly unlikely you want zero for the defaults for stiffness and damping... (srj) -// for stiffness: stiffness of the "springs" in the suspension 0 = no stiffness, 1 = totally stiff (huh huh, he said "stiff") -// for damping: 0=perfect spring, bounces forever. 1=glued to terrain. - m_pitchStiffness = 0.1f; - m_rollStiffness = 0.1f; - m_pitchDamping = 0.9f; - m_rollDamping = 0.9f; - m_forwardVelCoef = 0; - m_pitchByZVelCoef = 0; - m_thrustRoll = 0.0f; - m_wobbleRate = 0.0f; - m_minWobble = 0.0f; - m_maxWobble = 0.0f; - m_lateralVelCoef = 0; - m_forwardAccelCoef = 0; - m_lateralAccelCoef = 0; - m_uniformAxialDamping = 1.0f; - m_turnPivotOffset = 0; - m_apply2DFrictionWhenAirborne = false; - m_downhillOnly = false; - m_allowMotiveForceWhileAirborne = false; - m_locomotorWorksWhenDead = false; - m_airborneTargetingHeight = INT_MAX; - m_stickToGround = false; - m_canMoveBackward = false; - m_hasSuspension = false; - m_wheelTurnAngle = 0; - m_maximumWheelExtension = 0; - m_maximumWheelCompression = 0; - m_closeEnoughDist = 1.0f; - m_isCloseEnoughDist3D = FALSE; - m_ultraAccurateSlideIntoPlaceFactor = 0.0f; - - m_wanderWidthFactor = 0.0f; - m_wanderLengthFactor = 1.0f; - m_wanderAboutPointRadius = 0.0f; - - m_rudderCorrectionDegree = 0.0f; - m_rudderCorrectionRate = 0.0f; - m_elevatorCorrectionDegree = 0.0f; - m_elevatorCorrectionRate = 0.0f; - -} - -//------------------------------------------------------------------------------------------------- -LocomotorTemplate::~LocomotorTemplate() -{ - -} - -//------------------------------------------------------------------------------------------------- -void LocomotorTemplate::validate() -{ - // this is ok; parachutes need it! - //DEBUG_ASSERTCRASH(m_lift == 0.0f || m_lift > fabs(TheGlobalData->m_gravity), ("Lift is too low to counteract gravity!")); - //DEBUG_ASSERTCRASH(m_liftDamaged == 0.0f || m_liftDamaged > fabs(TheGlobalData->m_gravity), ("LiftDamaged is too low to counteract gravity!")); - //DEBUG_ASSERTCRASH(m_preferredHeight == 0.0f || (m_behaviorZ == Z_SURFACE_RELATIVE_HEIGHT || m_behaviorZ == Z_ABSOLUTE_HEIGHT || m_appearance == LOCO_THRUST), - // ("You must use Z_SURFACE_RELATIVE_HEIGHT or Z_ABSOLUTE_HEIGHT (or THRUST) to use preferredHeight")); - - // for 'damaged' stuff that was omitted, set 'em to be the same as 'undamaged'... - if (m_maxSpeedDamaged < 0.0f) - m_maxSpeedDamaged = m_maxSpeed; - - if (m_maxTurnRateDamaged < 0.0f) - m_maxTurnRateDamaged = m_maxTurnRate; - - if (m_accelerationDamaged < 0.0f) - m_accelerationDamaged = m_acceleration; - - if (m_liftDamaged < 0.0f) - m_liftDamaged = m_lift; - - if (m_appearance == LOCO_WINGS) - { - if (m_minSpeed <= 0.0f) - { - DEBUG_CRASH(("WINGS should always have positive minSpeeds (otherwise, they hover)")); - m_minSpeed = 0.01f; - } - if (m_minTurnSpeed <= 0.0f) - { - DEBUG_CRASH(("WINGS should always have positive minTurnSpeed")); - m_minTurnSpeed = 0.01f; - } - } - - if (m_appearance == LOCO_THRUST) - { - if (m_behaviorZ != Z_NO_Z_MOTIVE_FORCE || - m_lift != 0.0f || - m_liftDamaged != 0.0f) - { - DEBUG_CRASH(("THRUST locos may not use ZAxisBehavior or lift!\n")); - throw INI_INVALID_DATA; - } - if (m_maxSpeed <= 0.0f) - { - // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing - DEBUG_LOG(("THRUST locos may not have zero m_maxSpeed; healing...\n")); - m_maxSpeed = 0.01f; - } - if (m_maxSpeedDamaged <= 0.0f) - { - // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing - DEBUG_LOG(("THRUST locos may not have zero m_maxSpeedDamaged; healing...\n")); - m_maxSpeedDamaged = 0.01f; - } - if (m_minSpeed <= 0.0f) - { - // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing - DEBUG_LOG(("THRUST locos may not have zero m_minSpeed; healing...\n")); - m_minSpeed = 0.01f; - } - } -} - -//------------------------------------------------------------------------------------------------- -static void parseFrictionPerSec( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) -{ - Real fricPerSec = INI::scanReal(ini->getNextToken()); - Real fricPerFrame = fricPerSec * SECONDS_PER_LOGICFRAME_REAL; - *(Real *)store = fricPerFrame; -} - -//------------------------------------------------------------------------------------------------- -const FieldParse* LocomotorTemplate::getFieldParse() const -{ - static const FieldParse TheFieldParse[] = - { - { "Surfaces", INI::parseBitString32, TheLocomotorSurfaceTypeNames, offsetof(LocomotorTemplate, m_surfaces) }, - { "Speed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_maxSpeed) }, - { "SpeedDamaged", INI::parseVelocityReal, NULL, offsetof( LocomotorTemplate, m_maxSpeedDamaged ) }, - { "TurnRate", INI::parseAngularVelocityReal, NULL, offsetof(LocomotorTemplate, m_maxTurnRate) }, - { "TurnRateDamaged", INI::parseAngularVelocityReal, NULL, offsetof( LocomotorTemplate, m_maxTurnRateDamaged ) }, - { "Acceleration", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_acceleration) }, - { "AccelerationDamaged", INI::parseAccelerationReal, NULL, offsetof( LocomotorTemplate, m_accelerationDamaged ) }, - { "Lift", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_lift) }, - { "LiftDamaged", INI::parseAccelerationReal, NULL, offsetof( LocomotorTemplate, m_liftDamaged ) }, - { "Braking", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_braking) }, - { "MinSpeed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_minSpeed) }, - { "MinTurnSpeed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_minTurnSpeed) }, - { "PreferredHeight", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_preferredHeight) }, - { "PreferredHeightDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_preferredHeightDamping) }, - { "CirclingRadius", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_circlingRadius) }, - { "Extra2DFriction", parseFrictionPerSec, NULL, offsetof(LocomotorTemplate, m_extra2DFriction) }, - { "SpeedLimitZ", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_speedLimitZ) }, - { "MaxThrustAngle", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_maxThrustAngle) }, // yes, angle, not angular-vel - { "ZAxisBehavior", INI::parseIndexList, TheLocomotorBehaviorZNames, offsetof(LocomotorTemplate, m_behaviorZ) }, - { "Appearance", INI::parseIndexList, TheLocomotorAppearanceNames, offsetof(LocomotorTemplate, m_appearance) }, \ - { "GroupMovementPriority", INI::parseIndexList, TheLocomotorPriorityNames, offsetof(LocomotorTemplate, m_movePriority) }, \ - - { "AccelerationPitchLimit", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_accelPitchLimit) }, - { "DecelerationPitchLimit", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_decelPitchLimit) }, - { "BounceAmount", INI::parseAngularVelocityReal, NULL, offsetof(LocomotorTemplate, m_bounceKick) }, - { "PitchStiffness", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchStiffness) }, - { "RollStiffness", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rollStiffness) }, - { "PitchDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchDamping) }, - { "RollDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rollDamping) }, - { "ThrustRoll", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_thrustRoll) }, - { "ThrustWobbleRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wobbleRate) }, - { "ThrustMinWobble", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_minWobble) }, - { "ThrustMaxWobble", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maxWobble) }, - { "PitchInDirectionOfZVelFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchByZVelCoef) }, - { "ForwardVelocityPitchFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_forwardVelCoef) }, - { "LateralVelocityRollFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_lateralVelCoef) }, - { "ForwardAccelerationPitchFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_forwardAccelCoef) }, - { "LateralAccelerationRollFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_lateralAccelCoef) }, - { "UniformAxialDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_uniformAxialDamping) }, - { "TurnPivotOffset", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_turnPivotOffset) }, - { "Apply2DFrictionWhenAirborne", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_apply2DFrictionWhenAirborne) }, - { "DownhillOnly", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_downhillOnly) }, - { "AllowAirborneMotiveForce", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_allowMotiveForceWhileAirborne) }, - { "LocomotorWorksWhenDead", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_locomotorWorksWhenDead) }, - { "AirborneTargetingHeight", INI::parseInt, NULL, offsetof( LocomotorTemplate, m_airborneTargetingHeight ) }, - { "StickToGround", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_stickToGround) }, - { "CanMoveBackwards", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_canMoveBackward) }, - { "HasSuspension", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_hasSuspension) }, - { "FrontWheelTurnAngle", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_wheelTurnAngle) }, - { "MaximumWheelExtension", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maximumWheelExtension) }, - { "MaximumWheelCompression", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maximumWheelCompression) }, - { "CloseEnoughDist", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_closeEnoughDist) }, - { "CloseEnoughDist3D", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_isCloseEnoughDist3D) }, - { "SlideIntoPlaceTime", INI::parseDurationReal, NULL, offsetof(LocomotorTemplate, m_ultraAccurateSlideIntoPlaceFactor) }, - - { "WanderWidthFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderWidthFactor) }, - { "WanderLengthFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderLengthFactor) }, - { "WanderAboutPointRadius", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderAboutPointRadius) }, - - { "RudderCorrectionDegree", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rudderCorrectionDegree) }, - { "RudderCorrectionRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rudderCorrectionRate) }, - { "ElevatorCorrectionDegree", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_elevatorCorrectionDegree) }, - { "ElevatorCorrectionRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_elevatorCorrectionRate) }, - { NULL, NULL, NULL, 0 } // keep this last - - }; - return TheFieldParse; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -LocomotorStore::LocomotorStore() -{ -} - -//------------------------------------------------------------------------------------------------- -LocomotorStore::~LocomotorStore() -{ - // delete all the templates, then clear out the table. - LocomotorTemplateMap::iterator it; - for (it = m_locomotorTemplates.begin(); it != m_locomotorTemplates.end(); ++it) { - it->second->deleteInstance(); - } - - m_locomotorTemplates.clear(); -} - -//------------------------------------------------------------------------------------------------- -LocomotorTemplate* LocomotorStore::findLocomotorTemplate(NameKeyType namekey) -{ - if (namekey == NAMEKEY_INVALID) - return NULL; - - LocomotorTemplateMap::iterator it = m_locomotorTemplates.find(namekey); - if (it == m_locomotorTemplates.end()) - return NULL; - else - return (*it).second; -} - -//------------------------------------------------------------------------------------------------- -const LocomotorTemplate* LocomotorStore::findLocomotorTemplate(NameKeyType namekey) const -{ - if (namekey == NAMEKEY_INVALID) - return NULL; - - LocomotorTemplateMap::const_iterator it = m_locomotorTemplates.find(namekey); - if (it == m_locomotorTemplates.end()) - { - return NULL; - } - else - { - return (*it).second; - } -} - -//------------------------------------------------------------------------------------------------- -void LocomotorStore::update() -{ -} - -//------------------------------------------------------------------------------------------------- -void LocomotorStore::reset() -{ - // cleanup overrides. - LocomotorTemplateMap::iterator it; - for (it = m_locomotorTemplates.begin(); it != m_locomotorTemplates.end(); ) { - Overridable *locoTemp = it->second->deleteOverrides(); - if (!locoTemp) - { - m_locomotorTemplates.erase(it); - } - else - { - ++it; - } - } -} - -//------------------------------------------------------------------------------------------------- -LocomotorTemplate *LocomotorStore::newOverride( LocomotorTemplate *locoTemplate ) -{ - if (locoTemplate == NULL) - return NULL; - - // allocate new template - LocomotorTemplate *newTemplate = newInstance(LocomotorTemplate); - - // copy data from final override to 'newTemplate' as a set of initial default values - *newTemplate = *locoTemplate; - locoTemplate->setNextOverride(newTemplate); - - newTemplate->markAsOverride(); - - // return the newly created override for us to set values with etc - return newTemplate; - -} // end newOverride - -//------------------------------------------------------------------------------------------------- -/*static*/ void LocomotorStore::parseLocomotorTemplateDefinition(INI* ini) -{ - if (!TheLocomotorStore) - throw INI_INVALID_DATA; - - Bool isOverride = false; - // read the Locomotor name - const char* token = ini->getNextToken(); - NameKeyType namekey = NAMEKEY(token); - - LocomotorTemplate *loco = TheLocomotorStore->findLocomotorTemplate(namekey); - if (loco) { - if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES) { - loco = TheLocomotorStore->newOverride((LocomotorTemplate*) loco->friend_getFinalOverride()); - } - isOverride = true; - } else { - loco = newInstance(LocomotorTemplate); - if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES) { - loco->markAsOverride(); - } - } - - loco->friend_setName(token); - ini->initFromINI(loco, loco->getFieldParse()); - loco->validate(); - - // if this is an override, then we want the pointer on the existing named locomotor to point us - // to the override, so don't add it to the map. - if (!isOverride) - TheLocomotorStore->m_locomotorTemplates[namekey] = loco; -} - -//------------------------------------------------------------------------------------------------- -/*static*/ void INI::parseLocomotorTemplateDefinition( INI* ini ) -{ - LocomotorStore::parseLocomotorTemplateDefinition(ini); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- -Locomotor::Locomotor(const LocomotorTemplate* tmpl) -{ - m_template = tmpl; - m_brakingFactor = 1.0f; - m_maxLift = BIGNUM; - m_maxSpeed = BIGNUM; - m_maxAccel = BIGNUM; - m_maxBraking = BIGNUM; - m_maxTurnRate = BIGNUM; - m_flags = 0; - m_closeEnoughDist = m_template->m_closeEnoughDist; - setFlag(IS_CLOSE_ENOUGH_DIST_3D, m_template->m_isCloseEnoughDist3D); -#ifdef CIRCLE_FOR_LANDING - m_circleThresh = 0.0f; -#endif - m_preferredHeight = m_template->m_preferredHeight; - m_preferredHeightDamping = m_template->m_preferredHeightDamping; - - m_angleOffset = GameLogicRandomValueReal(-PI/6, PI/6); - m_offsetIncrement = (PI/40) * (GameLogicRandomValueReal(0.8f, 1.2f)/m_template->m_wanderLengthFactor); - setFlag(OFFSET_INCREASING, GameLogicRandomValue(0,1)); - m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; - - m_speedMultiplier = 1.0; -} - -//------------------------------------------------------------------------------------------------- -Locomotor::Locomotor(const Locomotor& that) -{ - //Added By Sadullah Nader - //Initializations - m_angleOffset = 0.0f; - m_maintainPos.zero(); - - // - - m_template = that.m_template; - m_brakingFactor = that.m_brakingFactor; - m_maxLift = that.m_maxLift; - m_maxSpeed = that.m_maxSpeed; - m_maxAccel = that.m_maxAccel; - m_maxBraking = that.m_maxBraking; - m_maxTurnRate = that.m_maxTurnRate; - m_flags = that.m_flags; - m_closeEnoughDist = that.m_closeEnoughDist; -#ifdef CIRCLE_FOR_LANDING - m_circleThresh = that.m_circleThresh; -#endif - m_preferredHeight = that.m_preferredHeight; - m_preferredHeightDamping = that.m_preferredHeightDamping; - m_angleOffset = that.m_angleOffset; - m_offsetIncrement = that.m_offsetIncrement; -} - -//------------------------------------------------------------------------------------------------- -Locomotor& Locomotor::operator=(const Locomotor& that) -{ - if (this != &that) - { - m_template = that.m_template; - m_brakingFactor = that.m_brakingFactor; - m_maxLift = that.m_maxLift; - m_maxSpeed = that.m_maxSpeed; - m_maxAccel = that.m_maxAccel; - m_maxBraking = that.m_maxBraking; - m_maxTurnRate = that.m_maxTurnRate; - m_flags = that.m_flags; - m_closeEnoughDist = that.m_closeEnoughDist; -#ifdef CIRCLE_FOR_LANDING - m_circleThresh = that.m_circleThresh; -#endif - m_preferredHeight = that.m_preferredHeight; - m_preferredHeightDamping = that.m_preferredHeightDamping; - } - return *this; -} - -//------------------------------------------------------------------------------------------------- -Locomotor::~Locomotor() -{ -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void Locomotor::crc( Xfer *xfer ) -{ - -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void Locomotor::xfer( Xfer *xfer ) -{ - // version - const XferVersion currentVersion = 2; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - if (version>=2) { - xfer->xferUnsignedInt(&m_donutTimer); - } - - xfer->xferCoord3D(&m_maintainPos); - xfer->xferReal(&m_brakingFactor); - xfer->xferReal(&m_maxLift); - xfer->xferReal(&m_maxSpeed); - xfer->xferReal(&m_maxAccel); - xfer->xferReal(&m_maxBraking); - xfer->xferReal(&m_maxTurnRate); - xfer->xferReal(&m_closeEnoughDist); -#ifdef CIRCLE_FOR_LANDING - DEBUG_CRASH(("not supported, must fix me")); -#endif - xfer->xferUnsignedInt(&m_flags); - xfer->xferReal(&m_preferredHeight); - xfer->xferReal(&m_preferredHeightDamping); - xfer->xferReal(&m_angleOffset); - xfer->xferReal(&m_offsetIncrement); - - xfer->xferReal(&m_speedMultiplier); - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void Locomotor::loadPostProcess( void ) -{ - -} // end loadPostProcess - -//------------------------------------------------------------------------------------------------- -void Locomotor::startMove(void) -{ - // Reset the donut timer. - m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::getMaxSpeedForCondition(BodyDamageType condition) const -{ - Real speed; - - if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) - speed = m_template->m_maxSpeed; - else - speed = m_template->m_maxSpeedDamaged; - - speed *= m_speedMultiplier; - - if (speed > m_maxSpeed) - speed = m_maxSpeed; - - return speed; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::getMaxTurnRate(BodyDamageType condition) const -{ - Real turn; - - if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) - turn = m_template->m_maxTurnRate; - else - turn = m_template->m_maxTurnRateDamaged; - - turn *= m_speedMultiplier; - - if (turn > m_maxTurnRate) - turn = m_maxTurnRate; - - const Real TURN_FACTOR = 2; - if (getFlag(ULTRA_ACCURATE)) - turn *= TURN_FACTOR; // monster turning ability - - return turn; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::getMaxAcceleration(BodyDamageType condition) const -{ - Real accel; - - if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) - accel = m_template->m_acceleration; - else - accel = m_template->m_accelerationDamaged; - - accel *= m_speedMultiplier; - - if (accel > m_maxAccel) - accel = m_maxAccel; - - return accel; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::getBraking() const -{ - Real braking = m_template->m_braking; - - braking *= m_speedMultiplier; - - if (braking > m_maxBraking) - braking = m_maxBraking; - - return braking; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::getMaxLift(BodyDamageType condition) const -{ - Real lift; - - if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) - lift = m_template->m_lift; - else - lift = m_template->m_liftDamaged; - - lift *= m_speedMultiplier; - - if (lift > m_maxLift) - lift = m_maxLift; - - return lift; -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::locoUpdate_moveTowardsAngle(Object* obj, Real goalAngle) -{ - setFlag(MAINTAIN_POS_IS_VALID, false); - - if (obj == NULL || m_template == NULL) - return; - - PhysicsBehavior *physics = obj->getPhysics(); - if (physics == NULL) - { - DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); - return; - } - - // Skip moveTowardsAngle if physics say you're stunned - if(physics->getIsStunned()) - { - return; - } - -#ifdef DEBUG_OBJECT_ID_EXISTS -// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_moveTowardsAngle %f (%f deg), spd %f (%f)\n",goalAngle,goalAngle*180/PI,physics->getSpeed(),physics->getForwardSpeed2D())); -#endif - - Real minSpeed = getMinSpeed(); - if (minSpeed > 0) - { - // can't stay in one place; move in the desired direction at min speed. - Coord3D desiredPos = *obj->getPosition(); - desiredPos.x += Cos(goalAngle) * minSpeed * 2; - desiredPos.y += Sin(goalAngle) * minSpeed * 2; - // pass a huge num for "dist to goal", so that we don't think we're nearing - // our destination and thus slow down... - const Real onPathDistToGoal = 99999.0f; - Bool blocked = false; - locoUpdate_moveTowardsPosition(obj, desiredPos, onPathDistToGoal, minSpeed, &blocked); - - // don't need to call handleBehaviorZ() here, since locoUpdate_moveTowardsPosition() will do so - return; - } - else - { - DEBUG_ASSERTCRASH(m_template->m_appearance != LOCO_THRUST, ("THRUST should always have minspeeds!\n")); - Coord3D desiredPos = *obj->getPosition(); - desiredPos.x += Cos(goalAngle) * 1000.0f; - desiredPos.y += Sin(goalAngle) * 1000.0f; - PhysicsTurningType rotating = rotateTowardsPosition(obj, desiredPos); - physics->setTurning(rotating); - handleBehaviorZ(obj, physics, *obj->getPosition()); - } - -} - -//------------------------------------------------------------------------------------------------- -PhysicsTurningType Locomotor::rotateTowardsPosition(Object* obj, const Coord3D& goalPos, Real *relAngle) -{ - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real turnRate = getMaxTurnRate(bdt); - - PhysicsTurningType rotating = rotateObjAroundLocoPivot(obj, goalPos, turnRate, relAngle); - return rotating; -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::setPhysicsOptions(Object* obj) -{ - PhysicsBehavior *physics = obj->getPhysics(); - if (physics == NULL) - { - DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); - return; - } - - // crank up the friction in ultra-accurate mode to increase movement precision. - const Real EXTRA_FRIC = 0.5f; - Real extraExtraFriction = getFlag(ULTRA_ACCURATE) ? EXTRA_FRIC : 0.0f; - physics->setExtraFriction(m_template->m_extra2DFriction + extraExtraFriction); - physics->setAllowAirborneFriction(getApply2DFrictionWhenAirborne()); // you'd think we wouldn't want friction in the air, but it's needed for realistic behavior. - physics->setStickToGround(getStickToGround()); // walking guys aren't allowed to catch huge (or even small) air. -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalPos, - Real onPathDistToGoal, Real desiredSpeed, Bool *blocked) -{ - setFlag(MAINTAIN_POS_IS_VALID, false); - - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxSpeed = getMaxSpeedForCondition(bdt); - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - Real distToStopAtMaxSpeed = (maxSpeed/getBraking()) * (maxSpeed)/2.0f; - if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > distToStopAtMaxSpeed) - { - setFlag(IS_BRAKING, false); - m_brakingFactor = 1.0f; - } - PhysicsBehavior *physics = obj->getPhysics(); - if (physics == NULL) - { - DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); - return; - } - - // Skip moveTowardsPosition if physics say you're stunned - if(physics->getIsStunned()) - { - return; - } - -#ifdef DEBUG_OBJECT_ID_EXISTS -// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_moveTowardsPosition %f %f %f (dtg %f, spd %f), speed %f (%f)\n",goalPos.x,goalPos.y,goalPos.z,onPathDistToGoal,desiredSpeed,physics->getSpeed(),physics->getForwardSpeed2D())); -#endif - - // - // do not allow for invalid positions that the pathfinder cannot handle ... for airborne - // objects we don't need the pathfinder so we'll ignore this - // - if( BitIsSet( m_template->m_surfaces, LOCOMOTORSURFACE_AIR ) == false && - !TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, obj->getPosition()) && - !getFlag(ALLOW_INVALID_POSITION)) - { - // Somehow, we have gotten to an invalid location. - if (fixInvalidPosition(obj, physics)) - { - // the we adjusted us toward a legal position, so just return. - return; - } - } - - // If the actual distance is farther, then use the actual distance so we get there. - Real dx = goalPos.x - obj->getPosition()->x; - Real dy = goalPos.y - obj->getPosition()->y; - Real dz = goalPos.z - obj->getPosition()->z; - Real dist = sqrt(dx*dx+dy*dy); - if (dist>onPathDistToGoal) - { - if (!obj->isKindOf(KINDOF_PROJECTILE) && dist>2*onPathDistToGoal) - { - setFlag(IS_BRAKING, true); - } - onPathDistToGoal = dist; - } - - Coord3D nullAccel; - - Bool treatAsAirborne = false; - Coord3D pos = *obj->getPosition(); - Real heightAboveSurface = pos.z - TheTerrainLogic->getLayerHeight(pos.x, pos.y, obj->getLayer()); - - if( obj->getStatusBits().test( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) ) - { - heightAboveSurface -= obj->getCarrierDeckHeight(); - } - - if (heightAboveSurface > -(3*3)*TheGlobalData->m_gravity) - { - // If we get high enough to stay up for 3 frames, then we left the ground. - treatAsAirborne = true; - } - // We apply a zero acceleration to all units, as the call to - // applyMotiveForce flags an object as being "driven" by a locomotor, rather - // than being pushed around by objects bumping it. - nullAccel.x = nullAccel.y = nullAccel.z = 0; - physics->applyMotiveForce(&nullAccel); - - if (*blocked) - { - if (desiredSpeed > physics->getVelocityMagnitude()) - { - *blocked = false; - } - if (treatAsAirborne && BitIsSet( m_template->m_surfaces, LOCOMOTORSURFACE_AIR ) ) - { - // Airborne flying objects don't collide for now. jba. - *blocked = false; - } - } - - if (*blocked) - { - physics->scrubVelocity2D(desiredSpeed); // stop if we are about to run into the blocking object. - Real turnRate = getMaxTurnRate(obj->getBodyModule()->getDamageState()); - if (m_template->m_wanderWidthFactor == 0.0f) - { - *blocked = (TURN_NONE != rotateObjAroundLocoPivot(obj, goalPos, turnRate)); - } - - // it is very important to be sure to call this in all situations, even if not moving in 2d space. - handleBehaviorZ(obj, physics, goalPos); - return; - } - - if ( -// srj sez: I don't know why we didn't want HOVERs to allow to "brake". -// we actually really want them to, because it allows much more precise destination positioning. -// m_template->m_appearance == LOCO_HOVER || - m_template->m_appearance == LOCO_WINGS) - { - setFlag(IS_BRAKING, false); - } - - Bool wasBraking = obj->getStatusBits().test( OBJECT_STATUS_BRAKING ); - - physics->setTurning(TURN_NONE); - if (getAllowMotiveForceWhileAirborne() || !treatAsAirborne) - { - switch (m_template->m_appearance) - { - case LOCO_LEGS_TWO: - moveTowardsPositionLegs(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_CLIMBER: - moveTowardsPositionClimb(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_WHEELS_FOUR: - case LOCO_MOTORCYCLE: - moveTowardsPositionWheels( obj, physics, goalPos, onPathDistToGoal, desiredSpeed ); - break; - case LOCO_TREADS: - moveTowardsPositionTreads(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_HOVER: - moveTowardsPositionHover(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_WINGS: - moveTowardsPositionWings(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_THRUST: - moveTowardsPositionThrust(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - case LOCO_OTHER: - default: - moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - break; - } - } - - handleBehaviorZ(obj, physics, goalPos); - // Objects that are braking don't follow the normal physics, so they end up at their destination exactly. - obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ), getFlag(IS_BRAKING) ); - - if (wasBraking) - { - #define MIN_VEL (PATHFIND_CELL_SIZE_F/(LOGICFRAMES_PER_SECOND)) - - Coord3D pos = *obj->getPosition(); - if (obj->isKindOf(KINDOF_PROJECTILE)) - { - // Projectiles never stop braking once they start. jba. - obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ) ); - // Projectiles cheat in 3 dimensions. - dist = sqrt(dx*dx+dy*dy+dz*dz); - Real vel = physics->getVelocityMagnitude(); - if (vel < MIN_VEL) - vel = MIN_VEL; - if (vel > dist) - vel = dist; // do not overcompensate! - // Normalize. - if (dist > 0.001f) - { - dist = 1.0f / dist; - dx *= dist; - dy *= dist; - dz *= dist; - - // DEBUG_LOG((">>> Locomotor Braking - d(xyz) = %f / %f / %f\n", dx * vel, dy * vel, dz * vel)); - - pos.x += dx * vel; - pos.y += dy * vel; - pos.z += dz * vel; - } - } - else - { - // not projectiles only cheat in x & y. - // Normalize. - if (dist > 0.001f) - { - Real vel = fabs(physics->getForwardSpeed2D()); - if (vel < MIN_VEL) - vel = MIN_VEL; - if (vel > dist) - vel = dist; // do not overcompensate! - dist = 1.0f / dist; - dx *= dist; - dy *= dist; - pos.x += dx * vel; - pos.y += dy * vel; - } - } - obj->setPosition(&pos); - } - -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxSpeed = getMaxSpeedForCondition(bdt); - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - Real maxAcceleration = getMaxAcceleration(bdt); - - // Locomotion for treaded vehicles, ie tanks. - - // - // Orient toward goal position - // -// Real angle = obj->getOrientation(); -// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); -// Real desiredAngle = angle + relAngle; - Real relAngle ; - PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos, &relAngle); - physics->setTurning(rotating); - - // - // Modulate speed according to turning. The more we have to turn, the slower we go - // - const Real QUAETERPI = PI / 4.0f; - Real angleCoeff = (Real)fabs( relAngle ) / QUAETERPI; - if (angleCoeff > 1.0f) - angleCoeff = 1.0; - - - Real dx = obj->getPosition()->x - goalPos.x; - Real dy = obj->getPosition()->y - goalPos.y; - - - Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; - - -// if (speed < m_minTurnSpeed) -// speed = m_minTurnSpeed; - - Real actualSpeed = physics->getForwardSpeed2D(); - Real slowDownTime = actualSpeed / getBraking(); - Real slowDownDist = (actualSpeed/1.50f) * slowDownTime; - - if (sqr(dx)+sqr(dy) 0.05) { - goalSpeed = actualSpeed*0.6f; - } - - if (onPathDistToGoal < slowDownDist && !getFlag(IS_BRAKING) && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - { - setFlag(IS_BRAKING, true); - m_brakingFactor = 1.1f; - } - - if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > 2.0*slowDownDist) - { - setFlag(IS_BRAKING, false); - } - - if (getFlag(IS_BRAKING)) - { - m_brakingFactor = slowDownDist/onPathDistToGoal; - m_brakingFactor *= m_brakingFactor; - if (m_brakingFactor>MAX_BRAKING_FACTOR) { - m_brakingFactor = MAX_BRAKING_FACTOR; - } - if (slowDownDist>onPathDistToGoal) { - goalSpeed = actualSpeed-getBraking(); - if (goalSpeed<0.0f) goalSpeed= 0.0f; - } else if (slowDownDist>onPathDistToGoal*0.75f) { - goalSpeed = actualSpeed-getBraking()/2.0f; - if (goalSpeed<0.0f) goalSpeed = 0.0f; - } else { - goalSpeed = actualSpeed; - } - } - - - //DEBUG_LOG(("Actual speed %f, Braking factor %f, slowDownDist %f, Pathdist %f, goalSpeed %f\n", - // actualSpeed, m_brakingFactor, slowDownDist, onPathDistToGoal, goalSpeed)); - - // - // Maintain goal speed - // - Real speedDelta = goalSpeed - actualSpeed; - if (speedDelta != 0.0f) - { - Real mass = physics->getMass(); - Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -m_brakingFactor*getBraking(); - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - const Coord3D *dir = obj->getUnitDirectionVector2D(); - - Coord3D force; - force.x = accelForce * dir->x; - force.y = accelForce * dir->y; - force.z = 0.0f; - - // apply forces to object - physics->applyMotiveForce( &force ); - } -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxSpeed = getMaxSpeedForCondition(bdt); - Real maxTurnRate = getMaxTurnRate(bdt); - Real maxAcceleration = getMaxAcceleration(bdt); - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - // Locomotion for wheeled vehicles, ie trucks. - // - // See if we are turning. If so, use the min turn speed. - // - Real turnSpeed = m_template->m_minTurnSpeed; - Real angle = obj->getOrientation(); -// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); -// Real desiredAngle = angle + relAngle; - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); - Real relAngle = stdAngleDiff(desiredAngle, angle); - - Bool moveBackwards = false; - - // Wheeled vehicles can only turn while moving, so make sure the turn speed is reasonable. - if (turnSpeed < maxSpeed/4.0f) - { - turnSpeed = maxSpeed/4.0f; - } - - - Real actualSpeed = physics->getForwardSpeed2D(); - Bool do3pointTurn = false; -#if 1 - if (actualSpeed==0.0f) { - setFlag(MOVING_BACKWARDS, false); - if (m_template->m_canMoveBackward && fabs(relAngle) > PI/2) { - setFlag(MOVING_BACKWARDS, true ); - setFlag(DOING_THREE_POINT_TURN, onPathDistToGoal>5*obj->getGeometryInfo().getMajorRadius()); - } - - } - if (getFlag(MOVING_BACKWARDS)) { - if (fabs(relAngle) < PI/2) { - moveBackwards = false; - setFlag(MOVING_BACKWARDS, false); - } else { - moveBackwards = true; - setFlag(DOING_THREE_POINT_TURN, onPathDistToGoal>5*obj->getGeometryInfo().getMajorRadius()); - do3pointTurn = getFlag(DOING_THREE_POINT_TURN); - if (!do3pointTurn) { - desiredAngle = stdAngleDiff(desiredAngle, PI); - relAngle = stdAngleDiff(desiredAngle, angle); - } - } - } -#endif - - const Real SMALL_TURN = PI / 20.0f; - if ((Real)fabs( relAngle ) > SMALL_TURN) - { - if (desiredSpeed>turnSpeed) - { - desiredSpeed = turnSpeed; - } - } - - Real goalSpeed = desiredSpeed; - if (moveBackwards) { - actualSpeed = -actualSpeed; - } - - - - Real slowDownTime = actualSpeed / getBraking() + 1.0f; - Real slowDownDist = (actualSpeed/1.5f) * slowDownTime + actualSpeed; - Real effectiveSlowDownDist = slowDownDist; - if (effectiveSlowDownDist < 1*PATHFIND_CELL_SIZE) { - effectiveSlowDownDist = 1*PATHFIND_CELL_SIZE; - } - - - const Real FIFTEEN_DEGREES = PI / 12.0f; - const Real PROJECT_FRAMES = LOGICFRAMES_PER_SECOND/2; // Project out 1/2 second. - if (fabs( relAngle ) > FIFTEEN_DEGREES) - { - // If we're turning more than 10 degrees, check & see if we're moving into "impassable territory" - Real distance = PROJECT_FRAMES * (goalSpeed+actualSpeed)/2.0f; - Real targetAngle = obj->getOrientation(); - Real turnFactor = ((goalSpeed+actualSpeed)/2.0f)/turnSpeed; - if (turnFactor > 1.0f) - turnFactor = 1.0f; - Real turnAmount = PROJECT_FRAMES*turnFactor*maxTurnRate/4.0f; - if (relAngle < 0) - { - targetAngle -= turnAmount; - } - else - { - targetAngle += turnAmount; - } - Coord3D offset; - offset.x = Cos(targetAngle)*distance; - offset.y = Sin(targetAngle)*distance; - offset.z = 0; - - const Coord3D* pos = obj->getPosition(); - - Coord3D nextPos; - nextPos.x = pos->x+offset.x; - nextPos.y = pos->y+offset.y; - nextPos.z = pos->z; - - pos = obj->getPosition(); - - Coord3D halfPos; - halfPos.x = pos->x+offset.x/2; - halfPos.y = pos->y+offset.y/2; - halfPos.z = pos->z; - - if (!TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &halfPos) || - !TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &nextPos)) - { - PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos); - physics->setTurning(rotating); - - // apply a zero force to object so that it acts "driven" - Coord3D force; - force.zero(); - physics->applyMotiveForce( &force ); - return; - } - - } - - if (onPathDistToGoal < effectiveSlowDownDist && !getFlag(IS_BRAKING) && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - { - setFlag(IS_BRAKING, true); - m_brakingFactor = 1.1f; - } - - - if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > 2.0*slowDownDist) - { - setFlag(IS_BRAKING, false); - } - - if (onPathDistToGoal > DONUT_DISTANCE) { - m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; - } else { - if (m_donutTimer < TheGameLogic->getFrame()) { - setFlag(IS_BRAKING, true); - } - } - - if (getFlag(IS_BRAKING)) - { - m_brakingFactor = slowDownDist/onPathDistToGoal; - m_brakingFactor *= m_brakingFactor; - if (m_brakingFactor>MAX_BRAKING_FACTOR) { - m_brakingFactor = MAX_BRAKING_FACTOR; - } - m_brakingFactor = 1.0f; - if (slowDownDist>onPathDistToGoal) { - goalSpeed = actualSpeed-getBraking(); - if (goalSpeed<0.0f) goalSpeed= 0.0f; - } else if (slowDownDist>onPathDistToGoal*0.75f) { - goalSpeed = actualSpeed-getBraking()/2.0f; - if (goalSpeed<0.0f) goalSpeed = 0.0f; - } else { - goalSpeed = actualSpeed; - } - } - - - //DEBUG_LOG(("Actual speed %f, Braking factor %f, slowDownDist %f, Pathdist %f, goalSpeed %f\n", - // actualSpeed, m_brakingFactor, slowDownDist, onPathDistToGoal, goalSpeed)); - - - // Wheeled can only turn while moving. - Real turnFactor = actualSpeed/turnSpeed; - if (turnFactor<0) { - turnFactor = -turnFactor; // in case we're sliding backwards in a 3 pt turn. - } - if (turnFactor > 1.0f) - turnFactor = 1.0f; - Real turnAmount = turnFactor*maxTurnRate; - - PhysicsTurningType rotating; - if (moveBackwards && !do3pointTurn) { - Coord3D backwardPos = *obj->getPosition(); - backwardPos.x += -(goalPos.x - obj->getPosition()->x); - backwardPos.y += -(goalPos.y - obj->getPosition()->y); - rotating = rotateObjAroundLocoPivot(obj, backwardPos, turnAmount); - } else { - rotating = rotateObjAroundLocoPivot(obj, goalPos, turnAmount); - } - - physics->setTurning(rotating); - - // - // Maintain goal speed - // - Real speedDelta = goalSpeed - actualSpeed; - if (moveBackwards) { - speedDelta = -goalSpeed+actualSpeed; - } - if (speedDelta != 0.0f) - { - Real mass = physics->getMass(); - Real acceleration; - if (moveBackwards) { - acceleration = (speedDelta < 0.0f) ? -maxAcceleration : m_brakingFactor*getBraking(); - } else { - acceleration = (speedDelta > 0.0f) ? maxAcceleration : -m_brakingFactor*getBraking(); - } - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - //DEBUG_LOG(("Braking %d, actualSpeed %f, goalSpeed %f, delta %f, accel %f\n", getFlag(IS_BRAKING), - //actualSpeed, goalSpeed, speedDelta, accelForce)); - - const Coord3D *dir = obj->getUnitDirectionVector2D(); - - Coord3D force; - force.x = accelForce * dir->x; - force.y = accelForce * dir->y; - force.z = 0.0f; - - // apply forces to object - physics->applyMotiveForce( &force ); - } - -} -//------------------------------------------------------------------------------------------------- -Bool Locomotor::fixInvalidPosition(Object* obj, PhysicsBehavior *physics) -{ - if (obj->isKindOf(KINDOF_DOZER)) { - // don't fix him. - return false; - } -#define no_IGNORE_INVALID -#ifdef IGNORE_INVALID - // Right now we ignore invalid positions, so when units clip the edge of a building or cliff - // they don't get stuck. jba. 12SEPT02 - return false; -#else - Int dx = 0; - Int dy = 0; - Int i, j; - for (j=-1; j<2; j++) { - for (i=-1; i<2; i++) { - Coord3D thePos = *obj->getPosition(); - thePos.x += i*PATHFIND_CELL_SIZE_F; - thePos.y += j*PATHFIND_CELL_SIZE_F; - if (!TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &thePos)) { - if (i<0) dx += 1; - if (i>0) dx -= 1; - if (j<0) dy += 1; - if (j>0) dy -= 1; - } - } - } - if (dx || dy) { - - Coord3D correction; - correction.x = dx*physics->getMass()/5; - correction.y = dy*physics->getMass()/5; - correction.z = 0; - - Coord3D correctionNormalized = correction; - correctionNormalized.normalize(); - - Coord3D velocity; - // Kill current velocity in the direction of the correction. - velocity = *physics->getVelocity(); - Real dot = (velocity.x*correctionNormalized.x) + (velocity.y*correctionNormalized.y); - if (dot>.25f) { - // It was already leaving. - return false; - } - - - // Kill current accel - //physics->clearAcceleration(); - - if (dot<0) { - dot = sqrt(-dot); - correctionNormalized.x *= dot*physics->getMass(); - correctionNormalized.y *= dot*physics->getMass(); - physics->applyMotiveForce(&correctionNormalized); - } - - // apply correction. - physics->applyMotiveForce(&correction); - return true; - } - return false; -#endif -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::calcMinTurnRadius(BodyDamageType condition, Real* timeToTravelThatDist) const -{ - Real minSpeed = getMinSpeed(); // in dist/frame - Real maxTurnRate = getMaxTurnRate(condition); // in rads/frame - - /* - our minimum circumference will be like so: - - Real minTurnCircum = maxSpeed * (2*PI / maxTurnRate); - - so therefore our minimum turn radius is: - - Real minTurnRadius = minTurnCircum / 2*PI; - - so we just eliminate the middleman: - */ - // if we can't turn, return a huge-but-finite radius rather than NAN... - Real minTurnRadius = (maxTurnRate > 0.0f) ? minSpeed / maxTurnRate : BIGNUM; - - if (timeToTravelThatDist) - *timeToTravelThatDist = (minSpeed > 0.0f) ? (minTurnRadius / minSpeed) : 0.0f; - - return minTurnRadius; -} - - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - if (getIsDownhillOnly() && obj->getPosition()->z < goalPos.z) - { - return; - } - - Real maxAcceleration = getMaxAcceleration( obj->getBodyModule()->getDamageState() ); - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState() ); - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - // Locomotion for infantry. - // - // Orient toward goal position - // - Real actualSpeed = physics->getForwardSpeed2D(); - Real angle = obj->getOrientation(); -// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); -// Real desiredAngle = angle + relAngle; - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); - - if (m_template->m_wanderWidthFactor != 0.0f) { - Real angleLimit = PI/8 * m_template->m_wanderWidthFactor; - // This is the wander offline code - it forces the desired angle away from the goal, so we wander back & forth. jba. - if (getFlag(OFFSET_INCREASING)) { - m_angleOffset += m_offsetIncrement*actualSpeed; - if (m_angleOffset > angleLimit) { - setFlag(OFFSET_INCREASING, false); - } - } else { - m_angleOffset -= m_offsetIncrement*actualSpeed; - if (m_angleOffset<-angleLimit) { - setFlag(OFFSET_INCREASING, true); - } - } - desiredAngle = normalizeAngle(desiredAngle+m_angleOffset); - } - - Real relAngle = stdAngleDiff(desiredAngle, angle); - locoUpdate_moveTowardsAngle(obj, desiredAngle); - - // - // Modulate speed according to turning. The more we have to turn, the slower we go - // - const Real QUARTERPI = PI / 4.0f; - Real angleCoeff = (Real)fabs( relAngle ) / (QUARTERPI); - if (angleCoeff > 1.0f) - angleCoeff = 1.0; - - Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; - - //Real slowDownDist = (actualSpeed - m_template->m_minSpeed) / getBraking(); - Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); - if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - { - goalSpeed = m_template->m_minSpeed; - } - - - - // - // Maintain goal speed - // - Real speedDelta = goalSpeed - actualSpeed; - if (speedDelta != 0.0f) - { - Real mass = physics->getMass(); - Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - const Coord3D *dir = obj->getUnitDirectionVector2D(); - - Coord3D force; - force.x = accelForce * dir->x; - force.y = accelForce * dir->y; - force.z = 0.0f; - - - - // apply forces to object - physics->applyMotiveForce( &force ); - } -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - Real maxAcceleration = getMaxAcceleration( obj->getBodyModule()->getDamageState() ); - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState() ); - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - // Locomotion for climbing infantry. - - - Bool moveBackwards = false; - - Real dx, dy, dz; - - Coord3D pos = *obj->getPosition(); - - dx = pos.x - goalPos.x; - dy = pos.y - goalPos.y; - dz = pos.z - goalPos.z; - if (dz*dz > sqr(PATHFIND_CELL_SIZE_F)) { - setFlag(CLIMBING, true); - } - if (fabs(dz)<1) { - setFlag(CLIMBING, false); - } - - - //setFlag(CLIMBING, true); - - if (getFlag(CLIMBING)) { - Coord3D delta = goalPos; - delta.x -= pos.x; - delta.y -= pos.y; - delta.z = 0; - delta.normalize(); - delta.x += pos.x; - delta.y += pos.y; - delta.z = TheTerrainLogic->getGroundHeight(delta.x, delta.y); - if (delta.z < pos.z-0.1) { - moveBackwards = true; - } - - Real groundSlope = fabs(delta.z - pos.z); - if (groundSlope<1.0f) groundSlope = 1.0f; - - if (groundSlope>1.0f) { - desiredSpeed /= groundSlope*4; - } - } - setFlag(MOVING_BACKWARDS, moveBackwards); - - // - // Orient toward goal position - // - Real angle = obj->getOrientation(); -// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); -// Real desiredAngle = angle + relAngle; - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); - Real relAngle = stdAngleDiff(desiredAngle, angle); - - if (moveBackwards) { - desiredAngle = stdAngleDiff(desiredAngle, PI); - relAngle = stdAngleDiff(desiredAngle, angle); - } - - locoUpdate_moveTowardsAngle(obj, desiredAngle); - - // - // Modulate speed according to turning. The more we have to turn, the slower we go - // - const Real QUARTERPI = PI / 4.0f; - Real angleCoeff = (Real)fabs( relAngle ) / (QUARTERPI); - if (angleCoeff > 1.0f) - angleCoeff = 1.0; - - Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; - - Real actualSpeed = physics->getForwardSpeed2D(); - - if (moveBackwards) { - actualSpeed = -actualSpeed; - } - - //Real slowDownDist = (actualSpeed - m_template->m_minSpeed) / getBraking(); - Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); - if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - { - goalSpeed = m_template->m_minSpeed; - } - - // - // Maintain goal speed - // - Real speedDelta = goalSpeed - actualSpeed; - if (moveBackwards) { - speedDelta = -goalSpeed+actualSpeed; - } - if (speedDelta != 0.0f) - { - Real mass = physics->getMass(); - Real acceleration; - if (moveBackwards) { - acceleration = (speedDelta < 0.0f) ? -maxAcceleration : getBraking(); - } else { - acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); - } - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - const Coord3D *dir = obj->getUnitDirectionVector2D(); - - Coord3D force; - force.x = accelForce * dir->x; - force.y = accelForce * dir->y; - force.z = 0.0f; - - // apply forces to object - physics->applyMotiveForce( &force ); - } -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ -#ifdef CIRCLE_FOR_LANDING - if (m_circleThresh > 0.0f) - { - // if we are going a mostly-vertical maneuver, circle in order to - // gain/lose altitude, then resume course... - const Coord3D* pos = obj->getPosition(); - Real dx = goalPos.x - pos->x; - Real dy = goalPos.y - pos->y; - Real dz = goalPos.z - pos->z; - if (fabs(dz) > m_circleThresh) - { - // aim for the spot on the opposite side of the circle. - - // find the direction towards our goal pos - Real angleTowardPos = - (isNearlyZero(dx) && isNearlyZero(dy)) ? - obj->getOrientation() : - atan2(dy, dx); - - Real aimDir = (PI - PI/8); - angleTowardPos += aimDir; - - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real turnRadius = calcMinTurnRadius(bdt, NULL) * 4; - - // project a spot "radius" dist away from it, in that dir - Coord3D desiredPos = goalPos; - desiredPos.x += Cos(angleTowardPos) * turnRadius; - desiredPos.y += Sin(angleTowardPos) * turnRadius; - moveTowardsPositionOther(obj, physics, desiredPos, 0, desiredSpeed); - return; - } - } -#endif - - // handle the 2D component. - moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionHover(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - // handle the 2D component. - moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); - - // Only hover locomotors care about their OverWater special effects. (OverWater also affects speed, so this is not a client thing) - Coord3D newPosition = *obj->getPosition(); - if( TheTerrainLogic->isUnderwater( newPosition.x, newPosition.y ) ) - { - if( ! getFlag( OVER_WATER ) ) - { - // Change my model condition because I used to not be over water, but now I am - setFlag( OVER_WATER, TRUE ); - obj->setModelConditionState( MODELCONDITION_OVER_WATER ); - } - } - else - { - if( getFlag( OVER_WATER ) ) - { - // Here, I was, but now I'm not - setFlag( OVER_WATER, FALSE ); - obj->clearModelConditionState( MODELCONDITION_OVER_WATER ); - } - } -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - - Real maxForwardSpeed = getMaxSpeedForCondition(bdt); - desiredSpeed = clamp(m_template->m_minSpeed, desiredSpeed, maxForwardSpeed); - Real actualForwardSpeed = physics->getForwardSpeed3D(); - - if (getBraking() > 0) - { - //Real slowDownDist = (actualForwardSpeed - m_template->m_minSpeed) / getBraking(); - Real slowDownDist = calcSlowDownDist(actualForwardSpeed, m_template->m_minSpeed, getBraking()); - if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - desiredSpeed = m_template->m_minSpeed; - } - - Coord3D localGoalPos = goalPos; -#ifdef USE_ZDIR_DAMPING - Real zDirDamping = 0.0f; -#endif - - //out of the handleBehaviorZ() function - Coord3D pos = *obj->getPosition(); - if( m_preferredHeight != 0.0f && !getFlag(PRECISE_Z_POS) ) - { - // If we have a preferred flight height, and we haven't been told explicitly to ignore it... - Real surfaceHt = getSurfaceHtAtPt(pos.x, pos.y); - localGoalPos.z = m_preferredHeight + surfaceHt; -// localGoalPos.z = goalPos.z; - Real delta = localGoalPos.z - pos.z; - delta *= getPreferredHeightDamping(); - localGoalPos.z = pos.z + delta; - -#ifdef USE_ZDIR_DAMPING - // closer we get to the preferred height, less we adjust z-thrust, - // so we tend to "level out" at that height. we don't use this till - // below, but go ahead and calc it now... - Real MAX_VERTICAL_DAMP_RANGE = m_preferredHeight * 0.5; - delta = fabs(delta); - if (delta > MAX_VERTICAL_DAMP_RANGE) - delta = MAX_VERTICAL_DAMP_RANGE; - zDirDamping = 1.0f - (delta / MAX_VERTICAL_DAMP_RANGE); -#endif - } - - Vector3 forwardDir = obj->getTransformMatrix()->Get_X_Vector(); - - // Maintain goal speed - Real forwardSpeedDelta = desiredSpeed - actualForwardSpeed; - Real maxAccel = (forwardSpeedDelta > 0.0f || getBraking() == 0) ? getMaxAcceleration(bdt) : -getBraking(); - Real maxTurnRate = getMaxTurnRate(bdt); - - // what direction do we need to thrust in, in order to reach the goalpos? - Vector3 desiredThrustDir; - calcDirectionToApplyThrust(obj, physics, localGoalPos, maxAccel, desiredThrustDir); - - // we might not be able to thrust in that dir, so thrust as closely as we can - Real maxThrustAngle = (maxTurnRate > 0) ? (m_template->m_maxThrustAngle) : 0; - Vector3 thrustDir; - Real thrustAngle = tryToRotateVector3D(maxThrustAngle, forwardDir, desiredThrustDir, thrustDir); - - // note that we are trying to orient in the direction of our vel, not the dir of our thrust. - if (!isNearlyZero(physics->getVelocityMagnitude())) - { - const Coord3D* veltmp = physics->getVelocity(); - Vector3 vel(veltmp->x, veltmp->y, veltmp->z); - Bool adjust = true; - if( obj->getStatusBits().test( OBJECT_STATUS_BRAKING ) ) - { - //Real closeInDist = 150.0f; // TODO: get/set this from missileAI? - //Real af = 1.0f - __min((onPathDistToGoal / closeInDist), 1.0); - - //if (af > 0.0f) { - - // vel.Set( - // vel.X * (1.0f - af) + (goalPos.x - pos.x) * af, - // vel.Y * (1.0f - af) + (goalPos.y - pos.y) * af, - // vel.Z * (1.0f - af) + (goalPos.z - pos.z) * af - // ); - // if (isNearlyZero(sqr(vel.X) + sqr(vel.Y) + sqr(vel.Z))) { - // // we are at target. - // adjust = false; - // } - // maxTurnRate = (1.0f + (af * 2.0f) ) * maxTurnRate; - //} - - // DEBUG_LOG((">>> moveTowardsPositionThrust - Braking - maxTurnRate = %f\n", maxTurnRate)); - - // align to target, cause that's where we're going anyway. - - vel.Set(goalPos.x - pos.x, goalPos.y-pos.y, goalPos.z-pos.z); - if (isNearlyZero(sqr(vel.X)+sqr(vel.Y)+sqr(vel.Z))) { - // we are at target. - adjust = false; - } - maxTurnRate = 3*maxTurnRate; - } -#ifdef USE_ZDIR_DAMPING - if (zDirDamping != 0.0f) - { - Vector3 vel2D(veltmp->x, veltmp->y, 0); - // no need to normalize -- this call does that internally - tryToRotateVector3D(-zDirDamping, vel, vel2D, vel); - } -#endif - if (adjust) { - /*Real orient =*/ tryToOrientInThisDirection3D(obj, maxTurnRate, vel); - } - } - - if (forwardSpeedDelta != 0.0f || thrustAngle != 0.0f) - { - if (maxForwardSpeed <= 0.0f) - { - maxForwardSpeed = 0.01f; // In some cases, this is 0, hack for now. jba. - } - Real damping = clamp(0.0f, maxAccel / maxForwardSpeed, 1.0f); - Vector3 curVel(physics->getVelocity()->x, physics->getVelocity()->y, physics->getVelocity()->z); - - Vector3 accelVec = thrustDir * maxAccel - curVel * damping; - //DEBUG_LOG(("accel %f (max %f) vel %f (max %f) damping %f\n",accelVec.Length(),maxAccel,curVel.Length(),maxForwardSpeed,damping)); - - Real mass = physics->getMass(); - - Coord3D force; - force.x = mass * accelVec.X; - force.y = mass * accelVec.Y; - force.z = mass * accelVec.Z; - - // apply forces to object - physics->applyMotiveForce( &force ); - } -} - -//------------------------------------------------------------------------------------------------- -/*static*/ Real Locomotor::getSurfaceHtAtPt(Real x, Real y) -{ - Real ht = 0; - - Real z,waterZ; - if (TheTerrainLogic->isUnderwater(x, y, &waterZ, &z)) { - ht += waterZ; - } else { - ht += z; - } - - return ht; -} - -//------------------------------------------------------------------------------------------------- -Real Locomotor::calcLiftToUseAtPt(Object* obj, PhysicsBehavior *physics, Real curZ, Real surfaceAtPt, Real preferredHeight) -{ - /* - take the classic equation: - - x = x0 + v*t + 0.5*a*t^2 - - and solve for acceleration. - */ - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxGrossLift = getMaxLift(bdt); - Real maxNetLift = maxGrossLift + TheGlobalData->m_gravity; // note that gravity is always negative. - if (maxNetLift < 0) - maxNetLift = 0; - Real curVelZ = physics->getVelocity()->z; - // going down, braking is limited by net lift; going up, braking is limited by gravity - Real maxAccel; - if (getFlag(ULTRA_ACCURATE)) - maxAccel = (curVelZ < 0) ? 2*maxNetLift : -2*maxNetLift; - else - maxAccel = (curVelZ < 0) ? maxNetLift : TheGlobalData->m_gravity; - // see how far we need to slow to dead stop, given max braking - Real desiredAccel; - const Real TINY_ACCEL = 0.001f; - if (fabs(maxAccel) > TINY_ACCEL) - { - Real deltaZ = preferredHeight - curZ; - // calc how far it will take for us to go from cur speed to zero speed, at max accel. - // Real brakeDist = calcSlowDownDist(curVelZ, 0, maxAccel); - // in theory, the above is the correct calculation, but in practice, - // doesn't work in some situations (eg, opening of USA01 map). Why, I dunno. - // But for now I have gone back to the old, looks-incorrect-to-me-but-works calc. (srj) - Real brakeDist = (sqr(curVelZ) / fabs(maxAccel)); - if (fabs(brakeDist) > fabs(deltaZ)) - { - // if the dist-to-accel (or dist-to-brake) is further than the dist-to-go, - // use the max accel. - desiredAccel = maxAccel; - } - else if (fabs(curVelZ) > m_template->m_speedLimitZ) - { - // or, if we're going too fast, limit it here. - desiredAccel = m_template->m_speedLimitZ - curVelZ; - } - else - { - // ok, figure out the correct accel to use to get us there at zero. - // - // dz = v t + 0.5 a t^2 - // thus - // a = 2(dz - v t)/t^2 - // and - // t = (-v +- sqrt(v*v + 2*a*dz))/a - // - // but if we assume t=1, then - // a=2(dz-v) - // then, plug it back in and see if t is really 1... - desiredAccel = 2.0f * (deltaZ - curVelZ); - } - } - else - { - desiredAccel = 0.0f; - } - Real liftToUse = desiredAccel - TheGlobalData->m_gravity; - if (getFlag(ULTRA_ACCURATE)) - { - // in ultra-accurate mode, we allow cheating. - const Real UP_FACTOR = 3.0f; - if (liftToUse > UP_FACTOR*maxGrossLift) - liftToUse = UP_FACTOR*maxGrossLift; - // srj sez: we used to clip lift to zero here (not allowing neg lift). - // however, I now think that allowing neg lift in ultra-accurate mode is - // a good and desirable thing; in particular, it enables jets to complete - // "short" landings more accurately (previously they sometimes would "float" - // down, which sucked.) if you need to bump this back to zero, check it carefully... - else if (liftToUse < -maxGrossLift) - liftToUse = -maxGrossLift; - } - else - { - if (liftToUse > maxGrossLift) - liftToUse = maxGrossLift; - else if (liftToUse < 0.0f) - liftToUse = 0.0f; - } - - return liftToUse; -} - -//------------------------------------------------------------------------------------------------- -PhysicsTurningType Locomotor::rotateObjAroundLocoPivot(Object* obj, const Coord3D& goalPos, - Real maxTurnRate, Real *relAngle) -{ - Real angle = obj->getOrientation(); - Real offset = getTurnPivotOffset(); - - PhysicsTurningType turn = TURN_NONE; - - if (getFlag(IS_BRAKING)) offset = 0.0f; // When braking we do exact movement towards goal, instead of physics. - //Rotating about pivot moves the object, and can make us miss our goal, so it is disabled. jba. - if (offset != 0.0f) - { - Real radius = obj->getGeometryInfo().getBoundingCircleRadius(); - Real turnPointOffset = offset * radius; - - Coord3D turnPos = *obj->getPosition(); - const Coord3D* dir = obj->getUnitDirectionVector2D(); - turnPos.x += dir->x * turnPointOffset; - turnPos.y += dir->y * turnPointOffset; - Real dx =goalPos.x - turnPos.x; - Real dy = goalPos.y - turnPos.y; - // If we are very close to the goal, we twitch due to rounding error. So just return. jba. - if (fabs(dx)<0.1f && fabs(dy)<0.1f) return TURN_NONE; - Real desiredAngle = atan2(dy, dx); - Real amount = stdAngleDiff(desiredAngle, angle); - if (relAngle) *relAngle = amount; - if (amount>maxTurnRate) { - amount = maxTurnRate; - turn = TURN_POSITIVE; - } else if (amount < -maxTurnRate) { - amount = -maxTurnRate; - turn = TURN_NEGATIVE; - } else { - turn = TURN_NONE; - } - -#if 0 - Coord3D desiredPos = *obj->getPosition(); // well, desired Dir, anyway - desiredPos.x += Cos(angle + amount) * radius; - desiredPos.y += Sin(angle + amount) * radius; - - - // so, the thing is, we want to rotate ourselves so that our *center* is rotated - // by the given amount, but the rotation must be around turnPos. so do a little - // back-calculation. - Real angleDesiredForTurnPos = atan2(desiredPos.y - turnPos.y, desiredPos.x - turnPos.x); - amount = angleDesiredForTurnPos - angle; -#endif - /// @todo srj -- there's probably a more efficient & more direct way to do this. find it. - Matrix3D mtx; - Matrix3D tmp(1); - tmp.Translate(turnPos.x, turnPos.y, 0); - tmp.In_Place_Pre_Rotate_Z(amount); - tmp.Translate(-turnPos.x, -turnPos.y, 0); - - mtx.mul(tmp, *obj->getTransformMatrix()); - - obj->setTransformMatrix(&mtx); - } - else - { - Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); - Real amount = stdAngleDiff(desiredAngle, angle); - if (relAngle) *relAngle = amount; - if (amount>maxTurnRate) { - amount = maxTurnRate; - turn = TURN_POSITIVE; - } else if (amount < -maxTurnRate) { - amount = -maxTurnRate; - turn = TURN_NEGATIVE; - } else { - turn = TURN_NONE; - } - obj->setOrientation( normalizeAngle(angle + amount) ); - } - return turn; -} - -//------------------------------------------------------------------------------------------------- -/* - return true if we can maintain the position without being called every frame (eg, we are - resting on the ground), false if not (eg, we are hovering or circling) -*/ -Bool Locomotor::handleBehaviorZ(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos) -{ - Bool requiresConstantCalling = TRUE; - - // keep the agent aligned on the terrain - switch(m_template->m_behaviorZ) - { - case Z_NO_Z_MOTIVE_FORCE: - // nothing to do. - requiresConstantCalling = FALSE; - break; - - case Z_SEA_LEVEL: - requiresConstantCalling = TRUE; - if( !obj->isDisabledByType( DISABLED_HELD ) ) - { - Coord3D pos = *obj->getPosition(); - Real waterZ; - if (TheTerrainLogic->isUnderwater(pos.x, pos.y, &waterZ)) { - pos.z = waterZ; - } else { - pos.z = TheTerrainLogic->getLayerHeight(pos.x, pos.y, obj->getLayer()); - } - obj->setPosition(&pos); - } - break; - - case Z_FIXED_SURFACE_RELATIVE_HEIGHT: - case Z_FIXED_ABSOLUTE_HEIGHT: - requiresConstantCalling = TRUE; - { - Coord3D pos = *obj->getPosition(); - Bool surfaceRel = (m_template->m_behaviorZ == Z_FIXED_SURFACE_RELATIVE_HEIGHT); - Real surfaceHt = surfaceRel ? getSurfaceHtAtPt(pos.x, pos.y) : 0.0f; - pos.z = m_preferredHeight + (surfaceRel ? surfaceHt : 0); - obj->setPosition(&pos); - } - break; - - case Z_RELATIVE_TO_GROUND_AND_BUILDINGS: - requiresConstantCalling = TRUE; - { - // srj sez: use getGroundOrStructureHeight(), because someday it will cache building heights... - Coord3D pos = *obj->getPosition(); - Real surfaceHt = ThePartitionManager->getGroundOrStructureHeight(pos.x, pos.y); - - pos.z = m_preferredHeight + surfaceHt; - - obj->setPosition(&pos); - - } - break; - case Z_SMOOTH_RELATIVE_TO_HIGHEST_LAYER: - requiresConstantCalling = TRUE; - { - if (m_preferredHeight != 0.0f || getFlag(PRECISE_Z_POS)) - { - Coord3D pos = *obj->getPosition(); - - // srj sez: if we aren't on the ground, never find the ground layer - PathfindLayerEnum layerAtDest = obj->getLayer(); - if (layerAtDest == LAYER_GROUND) - layerAtDest = TheTerrainLogic->getHighestLayerForDestination( &pos ); - - Real surfaceHt; - Coord3D normal; - const Bool clip = false; // return the height, even if off the edge of the bridge proper. - surfaceHt = TheTerrainLogic->getLayerHeight( pos.x, pos.y, layerAtDest, &normal, clip ); - - Real preferredHeight = m_preferredHeight + surfaceHt; - if (getFlag(PRECISE_Z_POS)) - preferredHeight = goalPos.z; - - Real delta = preferredHeight - pos.z; - delta *= getPreferredHeightDamping(); - preferredHeight = pos.z + delta; - - Real liftToUse = calcLiftToUseAtPt(obj, physics, pos.z, surfaceHt, preferredHeight); - - //DEBUG_LOG(("HandleBZ %d LiftToUse %f\n",TheGameLogic->getFrame(),liftToUse)); - if (liftToUse != 0.0f) - { - Coord3D force; - force.x = 0.0f; - force.y = 0.0f; - force.z = liftToUse * physics->getMass(); - physics->applyMotiveForce(&force); - } - } - } - break; - - case Z_SURFACE_RELATIVE_HEIGHT: - case Z_ABSOLUTE_HEIGHT: - requiresConstantCalling = TRUE; - { - if (m_preferredHeight != 0.0f || getFlag(PRECISE_Z_POS)) - { - Coord3D pos = *obj->getPosition(); - - Bool surfaceRel = (m_template->m_behaviorZ == Z_SURFACE_RELATIVE_HEIGHT); - Real surfaceHt = surfaceRel ? getSurfaceHtAtPt(pos.x, pos.y) : 0.0f; - Real preferredHeight = m_preferredHeight + (surfaceRel ? surfaceHt : 0); - if (getFlag(PRECISE_Z_POS)) - preferredHeight = goalPos.z; - - Real delta = preferredHeight - pos.z; - delta *= getPreferredHeightDamping(); - preferredHeight = pos.z + delta; - - Real liftToUse = calcLiftToUseAtPt(obj, physics, pos.z, surfaceHt, preferredHeight); - - //DEBUG_LOG(("HandleBZ %d LiftToUse %f\n",TheGameLogic->getFrame(),liftToUse)); - if (liftToUse != 0.0f) - { - Coord3D force; - force.x = 0.0f; - force.y = 0.0f; - force.z = liftToUse * physics->getMass(); - physics->applyMotiveForce(&force); - } - } - } - break; - } - - return requiresConstantCalling; -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) -{ - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxAcceleration = getMaxAcceleration(bdt); - - // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at - Real maxSpeed = getMaxSpeedForCondition(bdt); - if( desiredSpeed > maxSpeed ) - desiredSpeed = maxSpeed; - - Real goalSpeed = desiredSpeed; - Real actualSpeed = physics->getForwardSpeed2D(); - - // Locomotion for other things, ie don't know what it is jba :) - // - // Orient toward goal position - // exception: if very close (ie, we could get there in 2 frames or less),\ - // and ULTRA_ACCURATE, just slide into place - // - const Coord3D* pos = obj->getPosition(); - Coord3D dirToApplyForce = *obj->getUnitDirectionVector2D(); - -//DEBUG_ASSERTLOG(!getFlag(ULTRA_ACCURATE),("thresh %f %f (%f %f)\n", -//fabs(goalPos.y - pos->y),fabs(goalPos.x - pos->x), -//fabs(goalPos.y - pos->y)/goalSpeed,fabs(goalPos.x - pos->x)/goalSpeed)); - if (getFlag(ULTRA_ACCURATE) && - fabs(goalPos.y - pos->y) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor && - fabs(goalPos.x - pos->x) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor) - { - // don't turn, just slide in the right direction - physics->setTurning(TURN_NONE); - dirToApplyForce.x = goalPos.x - pos->x; - dirToApplyForce.y = goalPos.y - pos->y; - dirToApplyForce.z = 0.0f; - dirToApplyForce.normalize(); - } - else - { - PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos); - physics->setTurning(rotating); - } - - if (!getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) - { - Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); - if (onPathDistToGoal < slowDownDist) - { - goalSpeed = m_template->m_minSpeed; - } - } - - // - // Maintain goal speed - // - Real speedDelta = goalSpeed - actualSpeed; - if (speedDelta != 0.0f) - { - Real mass = physics->getMass(); - Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - Coord3D force; - force.x = accelForce * dirToApplyForce.x; - force.y = accelForce * dirToApplyForce.y; - force.z = 0.0f; - - // apply forces to object - physics->applyMotiveForce( &force ); - } - -} - - -//------------------------------------------------------------------------------------------------- -/* - return true if we can maintain the position without being called every frame (eg, we are - resting on the ground), false if not (eg, we are hovering or circling) -*/ -Bool Locomotor::locoUpdate_maintainCurrentPosition(Object* obj) -{ - if (!getFlag(MAINTAIN_POS_IS_VALID)) - { - m_maintainPos = *obj->getPosition(); - setFlag(MAINTAIN_POS_IS_VALID, true); - } - - m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; - setFlag(IS_BRAKING, false); - PhysicsBehavior *physics = obj->getPhysics(); - if (physics == NULL) - { - DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); - return TRUE; - } - -#ifdef DEBUG_OBJECT_ID_EXISTS -// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_maintainCurrentPosition %f %f %f, speed %f (%f)\n",m_maintainPos.x,m_maintainPos.y,m_maintainPos.z,physics->getSpeed(),physics->getForwardSpeed2D())); -#endif - - Bool requiresConstantCalling = TRUE; // assume the worst. - switch (m_template->m_appearance) - { - case LOCO_THRUST: - maintainCurrentPositionThrust(obj, physics); - requiresConstantCalling = TRUE; - break; - case LOCO_LEGS_TWO: - maintainCurrentPositionLegs(obj, physics); - requiresConstantCalling = FALSE; - break; - case LOCO_CLIMBER: - maintainCurrentPositionLegs(obj, physics); - requiresConstantCalling = FALSE; - break; - case LOCO_WHEELS_FOUR: - case LOCO_MOTORCYCLE: - maintainCurrentPositionWheels(obj, physics); - requiresConstantCalling = FALSE; - break; - case LOCO_TREADS: - maintainCurrentPositionTreads(obj, physics); - requiresConstantCalling = FALSE; - break; - case LOCO_HOVER: - maintainCurrentPositionHover(obj, physics); - requiresConstantCalling = TRUE; - break; - case LOCO_WINGS: - maintainCurrentPositionWings(obj, physics); - requiresConstantCalling = TRUE; - break; - case LOCO_OTHER: - default: - maintainCurrentPositionOther(obj, physics); - requiresConstantCalling = TRUE; - break; - } - - // but we do need to do this even if not moving, for hovering/Thrusting things. - if (handleBehaviorZ(obj, physics, m_maintainPos)) - requiresConstantCalling = TRUE; - - return requiresConstantCalling; -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::maintainCurrentPositionThrust(Object* obj, PhysicsBehavior *physics) -{ - DEBUG_ASSERTCRASH(getFlag(MAINTAIN_POS_IS_VALID), ("invalid maintain pos")); - /// @todo srj -- should these also use the "circling radius" stuff, like wings? - moveTowardsPositionThrust(obj, physics, m_maintainPos, 0, getMinSpeed()); -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physics) -{ - DEBUG_ASSERTCRASH(getFlag(MAINTAIN_POS_IS_VALID), ("invalid maintain pos")); - physics->setTurning(TURN_NONE); - if (physics->isMotive() && obj->isAboveTerrain()) // no need to stop something that isn't moving (or is just sitting on the ground) - { - - // aim for the spot on the opposite side of the circle. - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real turnRadius = m_template->m_circlingRadius; - if (turnRadius == 0.0f) - turnRadius = calcMinTurnRadius(bdt, NULL); - - // find the direction towards our "maintain pos" - const Coord3D* pos = obj->getPosition(); - Real dx = m_maintainPos.x - pos->x; - Real dy = m_maintainPos.y - pos->y; - Real angleTowardMaintainPos = - (isNearlyZero(dx) && isNearlyZero(dy)) ? - obj->getOrientation() : - atan2(dy, dx); - - Real aimDir = (PI - PI/8); - if (turnRadius < 0) - { - turnRadius = -turnRadius; - aimDir = -aimDir; - } - angleTowardMaintainPos += aimDir; - - // project a spot "radius" dist away from it, in that dir - Coord3D desiredPos = m_maintainPos; - desiredPos.x += Cos(angleTowardMaintainPos) * turnRadius; - desiredPos.y += Sin(angleTowardMaintainPos) * turnRadius; - moveTowardsPositionWings(obj, physics, desiredPos, 0, m_template->m_minSpeed); - } -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physics) -{ - physics->setTurning(TURN_NONE); - if (physics->isMotive()) // no need to stop something that isn't moving. - { - DEBUG_ASSERTCRASH(m_template->m_minSpeed == 0.0f, ("HOVER should always have zero minSpeeds (otherwise, they WING)")); - - BodyDamageType bdt = obj->getBodyModule()->getDamageState(); - Real maxAcceleration = getMaxAcceleration(bdt); - Real actualSpeed = physics->getForwardSpeed2D(); - // - // Stop - // - Real minSpeed = max( 1.0E-10f, m_template->m_minSpeed ); - Real speedDelta = minSpeed - actualSpeed; - if (fabs(speedDelta) > minSpeed) - { - Real mass = physics->getMass(); - Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); - Real accelForce = mass * acceleration; - - /* - don't accelerate/brake more than necessary. do a quick calc to - see how much force we really need to achieve our goal speed... - */ - Real maxForceNeeded = mass * speedDelta; - if (fabs(accelForce) > fabs(maxForceNeeded)) - accelForce = maxForceNeeded; - - const Coord3D *dir = obj->getUnitDirectionVector2D(); - - Coord3D force; - force.x = accelForce * dir->x; - force.y = accelForce * dir->y; - force.z = 0.0f; - - - // Apply a random kick (if applicable) to dirty-up visually. - // The idea is that chopper pilots have to do course corrections all the time - // Because of changes in wind, pressure, etc. - // Those changes are added here, then the - - - - // apply forces to object - physics->applyMotiveForce( &force ); - } - } - -} - -//------------------------------------------------------------------------------------------------- -void Locomotor::maintainCurrentPositionOther(Object* obj, PhysicsBehavior *physics) -{ - - physics->setTurning(TURN_NONE); - if (physics->isMotive()) // no need to stop something that isn't moving. - { - physics->scrubVelocity2D(0); // stop. - } - -} - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- - -//------------------------------------------------------------------------------------------------- -LocomotorSet::LocomotorSet() -{ - m_locomotors.clear(); - m_validLocomotorSurfaces = 0; - m_downhillOnly = FALSE; - -} - -//------------------------------------------------------------------------------------------------- -LocomotorSet::LocomotorSet(const LocomotorSet& that) -{ - DEBUG_CRASH(("unimplemented")); -} - -//------------------------------------------------------------------------------------------------- -LocomotorSet& LocomotorSet::operator=(const LocomotorSet& that) -{ - if (this != &that) - { - DEBUG_CRASH(("unimplemented")); - } - return *this; -} - -//------------------------------------------------------------------------------------------------- -LocomotorSet::~LocomotorSet() -{ - clear(); -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void LocomotorSet::crc( Xfer *xfer ) -{ - -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void LocomotorSet::xfer( Xfer *xfer ) -{ - // version - const XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // count of vector - UnsignedShort count = m_locomotors.size(); - xfer->xferUnsignedShort( &count ); - - // data - if (xfer->getXferMode() == XFER_SAVE) - { - for (LocomotorVector::iterator it = m_locomotors.begin(); it != m_locomotors.end(); ++it) - { - Locomotor* loco = *it; - AsciiString name = loco->getTemplateName(); - xfer->xferAsciiString(&name); - xfer->xferSnapshot(loco); - } - } - else if (xfer->getXferMode() == XFER_LOAD) - { - // vector should be empty at this point - if (m_locomotors.empty() == FALSE) - { - DEBUG_CRASH(( "LocomotorSet::xfer - vector is not empty, but should be\n" )); - throw XFER_LIST_NOT_EMPTY; - } - - for (UnsignedShort i = 0; i < count; ++i) - { - AsciiString name; - xfer->xferAsciiString(&name); - - const LocomotorTemplate* lt = TheLocomotorStore->findLocomotorTemplate(NAMEKEY(name)); - if (lt == NULL) - { - DEBUG_CRASH(( "LocomotorSet::xfer - template %s not found\n", name.str() )); - throw XFER_UNKNOWN_STRING; - } - - Locomotor* loco = TheLocomotorStore->newLocomotor(lt); - xfer->xferSnapshot(loco); - m_locomotors.push_back(loco); - } - } - - xfer->xferInt(&m_validLocomotorSurfaces); - xfer->xferBool(&m_downhillOnly); - -} - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void LocomotorSet::loadPostProcess( void ) -{ - -} // end loadPostProcess - -//------------------------------------------------------------------------------------------------- -void LocomotorSet::xferSelfAndCurLocoPtr(Xfer *xfer, Locomotor** loco) -{ - xfer->xferSnapshot(this); - - if (xfer->getXferMode() == XFER_SAVE) - { - AsciiString name; - if (*loco) - name = (*loco)->getTemplateName(); - xfer->xferAsciiString(&name); - } - else if (xfer->getXferMode() == XFER_LOAD) - { - AsciiString name; - xfer->xferAsciiString(&name); - - if (name.isEmpty()) - { - *loco = NULL; - } - else - { - for (int i = 0; i < m_locomotors.size(); ++i) - { - if (m_locomotors[i]->getTemplateName() == name) - { - *loco = m_locomotors[i]; - return; - } - } - - DEBUG_CRASH(( "LocomotorSet::xfer - template %s not found\n", name.str() )); - throw XFER_UNKNOWN_STRING; - } - } -} - -//------------------------------------------------------------------------------------------------- -void LocomotorSet::clear() -{ - for (int i = 0; i < m_locomotors.size(); ++i) - { - if (m_locomotors[i]) - m_locomotors[i]->deleteInstance(); - } - m_locomotors.clear(); - m_validLocomotorSurfaces = 0; - m_downhillOnly = FALSE; -} - -//------------------------------------------------------------------------------------------------- -void LocomotorSet::addLocomotor(const LocomotorTemplate* lt) -{ - Locomotor* loco = TheLocomotorStore->newLocomotor(lt); - if (loco) - { - m_locomotors.push_back(loco); - m_validLocomotorSurfaces |= loco->getLegalSurfaces(); - if (loco->getIsDownhillOnly()) - { - m_downhillOnly = TRUE; - } - else // Previous locos were gravity only, but this one isn't! - { - DEBUG_ASSERTCRASH(!m_downhillOnly,("LocomotorSet, YOU CAN NOT MIX DOWNHILL-ONLY LOCOMOTORS WITH NON-DOWNHILL-ONLY ONES.")); - } - - } -} - -//------------------------------------------------------------------------------------------------- -Locomotor* LocomotorSet::findLocomotor(LocomotorSurfaceTypeMask t) -{ - for (LocomotorVector::iterator it = m_locomotors.begin(); it != m_locomotors.end(); ++it) - { - Locomotor* curLocomotor = *it; - if (curLocomotor && (curLocomotor->getLegalSurfaces() & t)) - return curLocomotor; - } - return NULL; -} - - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: Locomotor.cpp /////////////////////////////////////////////////////////////////////////////// +// Author: Steven Johnson, Feb 2002 +// Desc: Locomotor descriptions +/////////////////////////////////////////////////////////////////////////////////////////////////// + + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#define DEFINE_SURFACECATEGORY_NAMES +#define DEFINE_LOCO_Z_NAMES +#define DEFINE_LOCO_APPEARANCE_NAMES + +#include "Common/INI.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/PartitionManager.h" +#include "GameLogic/Locomotor.h" +#include "GameLogic/Object.h" +#include "GameLogic/AI.h" +#include "GameLogic/AIPathfind.h" +#include "GameLogic/Module/PhysicsUpdate.h" +#include "GameLogic/Module/BodyModule.h" +#include "GameLogic/Module/AIUpdate.h" + +#ifdef RTS_INTERNAL +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +static const Real DONUT_TIME_DELAY_SECONDS=2.5f; +static const Real DONUT_DISTANCE=4.0*PATHFIND_CELL_SIZE_F; + + +#define MAX_BRAKING_FACTOR 5.0f +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC DATA //////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +LocomotorStore *TheLocomotorStore = NULL; ///< the Locomotor store definition + +const Real BIGNUM = 99999.0f; + +static const char *TheLocomotorPriorityNames[] = +{ + "MOVES_BACK", + "MOVES_MIDDLE", + "MOVES_FRONT", + + NULL +}; + + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE DATA /////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PRIVATE FUNCTIONS ////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +static Real calcSlowDownDist(Real curSpeed, Real desiredSpeed, Real maxBraking) +{ + Real delta = curSpeed - desiredSpeed; + if (delta <= 0) + return 0.0f; + + Real dist = (sqr(delta) / fabs(maxBraking)) * 0.5f; + + // use a little fudge so that things can stop "on a dime" more easily... + const Real FUDGE = 1.05f; + return dist * FUDGE; +} + +//----------------------------------------------------------------------------- +inline Bool isNearlyZero(Real a) +{ + const Real TINY_EPSILON = 0.001f; + return fabs(a) < TINY_EPSILON; +} + +//----------------------------------------------------------------------------- +inline Bool isNearly(Real a, Real val) +{ + const Real TINY_EPSILON = 0.001f; + return fabs(a - val) < TINY_EPSILON; +} + +//----------------------------------------------------------------------------- +// return the angle delta (in 3-space) we turned. +static Real tryToRotateVector3D( + Real maxAngle, // if negative, it's a percent (0...1) of the dist to rotate 'em + const Vector3& inCurDir, + const Vector3& inGoalDir, + Vector3& actualDir +) +{ + if (isNearlyZero(maxAngle)) + { + actualDir = inCurDir; + return 0.0f; + } + + Vector3 curDir = inCurDir; + curDir.Normalize(); + + Vector3 goalDir = inGoalDir; + goalDir.Normalize(); + + // dot of two unit vectors is cos of angle between them. + Real cosine = Vector3::Dot_Product(curDir, goalDir); + // bound it in case of numerical error + Real angleBetween = (Real)ACos(clamp(-1.0f, cosine, 1.0f)); + + if (maxAngle < 0) + { + maxAngle = -maxAngle * angleBetween; + if (isNearlyZero(maxAngle)) + { + actualDir = inCurDir; + return 0.0f; + } + } + + if (fabs(angleBetween) <= maxAngle) + { + // close enough + actualDir = goalDir; + } + else + { + // nah, try as much as we can in the right dir. + // we need to rotate around the axis perpendicular to these two vecs. + // but: cross of two vectors is the perpendicular axis! +#ifdef ALLOW_TEMPORARIES + Vector3 objCrossGoal = Vector3::Cross_Product(curDir, goalDir); + objCrossGoal.Normalize(); +#else + Vector3 objCrossGoal; + Vector3::Normalized_Cross_Product(curDir, goalDir, &objCrossGoal); +#endif + + angleBetween = maxAngle; + Matrix3D rotMtx(objCrossGoal, angleBetween); + actualDir = rotMtx.Rotate_Vector(curDir); + } + + return angleBetween; +} + +//------------------------------------------------------------------------------------------------- +static Real tryToOrientInThisDirection3D(Object* obj, Real maxTurnRate, const Vector3& desiredDir) +{ + Vector3 actualDir; + Real relAngle = tryToRotateVector3D(maxTurnRate, obj->getTransformMatrix()->Get_X_Vector(), desiredDir, actualDir); + if (relAngle != 0.0f) + { + Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z); + + Matrix3D newXform; + newXform.buildTransformMatrix( objPos, actualDir ); + + obj->setTransformMatrix( &newXform ); + } + return relAngle; +} + +//------------------------------------------------------------------------------------------------- +inline Real tryToOrientInThisDirection3D(Object* obj, Real maxTurnRate, const Coord3D* dir) +{ + return tryToOrientInThisDirection3D(obj, maxTurnRate, Vector3(dir->x, dir->y, dir->z)); +} + +//----------------------------------------------------------------------------- +static void calcDirectionToApplyThrust( + const Object* obj, + const PhysicsBehavior* physics, + const Coord3D& ingoalPos, + Real maxAccel, + Vector3& goalDir +) +{ + /* + our meta-goal here is to calculate the direction we should apply our motive force + in order to minimize the angle between (our velocity) and (direction towards goalpos). + + this is complicated by the fact that we generally have an intrinsic velocity already, + that must be accounted for, and by the fact that we can only apply force in our + forward-x-direction (with a thrust-angle-range), and (due to limited range) might not + be able to apply the force in the optimal direction! + */ + + // convert to Vector3, to use all its handy stuff + Vector3 objPos(obj->getPosition()->x, obj->getPosition()->y, obj->getPosition()->z); + Vector3 goalPos(ingoalPos.x, ingoalPos.y, ingoalPos.z); + + Vector3 vecToGoal = goalPos - objPos; + if (isNearlyZero(vecToGoal.Length2())) + { + // goal pos is essentially same as current pos, so just stay the same & return + goalDir = obj->getTransformMatrix()->Get_X_Vector(); + return; + } + + /* + get our cur vel into a useful Vector3 form + */ + Vector3 curVel(physics->getVelocity()->x, physics->getVelocity()->y, physics->getVelocity()->z); + + // add gravity to our vel so that we account for it in our calcs + curVel.Z += TheGlobalData->m_gravity; + + Bool foundSolution = false; + Real distToGoalSqr = vecToGoal.Length2(); + Real distToGoal = sqrt(distToGoalSqr); + Real curVelMagSqr = curVel.Length2(); + Real curVelMag = sqrt(curVelMagSqr); + Real maxAccelSqr = sqr(maxAccel); + + Real denom = curVelMagSqr - maxAccelSqr; + if (!isNearlyZero(denom)) + { + // solve the (greatly simplified) quadratic... + Real t = (distToGoal * (curVelMag + maxAccel)) / denom; + Real t2 = (distToGoal * (curVelMag - maxAccel)) / denom; + if (t >= 0 || t2 >= 0) + { + // choose the smallest positive t. + if (t < 0 || (t2 >= 0 && t2 < t)) + t = t2; + + // plug it in. + if (!isNearlyZero(t)) + { + goalDir.X = (vecToGoal.X / t) - curVel.X; + goalDir.Y = (vecToGoal.Y / t) - curVel.Y; + goalDir.Z = (vecToGoal.Z / t) - curVel.Z; + goalDir.Normalize(); + foundSolution = true; + } + } + } + if (!foundSolution) + { + // Doh... no (useful) solution. revert to dumb. + goalDir = vecToGoal; + goalDir.Normalize(); + } + +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// PUBLIC FUNCTIONS /////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +LocomotorTemplate::LocomotorTemplate() +{ + // these values mean "make the same as undamaged if not explicitly specified" + m_maxSpeedDamaged = -1.0f; + m_maxTurnRateDamaged = -1.0f; + m_accelerationDamaged = -1.0f; + m_liftDamaged = -1.0f; + + m_surfaces = 0; + m_maxSpeed = 0.0f; + m_maxTurnRate = 0.0f; + m_acceleration = 0.0f; + m_lift = 0.0f; + m_braking = BIGNUM; + m_minSpeed = 0.0f; + m_minTurnSpeed = BIGNUM; + m_behaviorZ = Z_NO_Z_MOTIVE_FORCE; + m_appearance = LOCO_OTHER; + m_movePriority = LOCO_MOVES_MIDDLE; + m_preferredHeight = 0; + m_preferredHeightDamping = 1.0f; + m_circlingRadius = 0; + + m_maxThrustAngle = 0; + m_speedLimitZ = 999999.0f; + m_extra2DFriction = 0.0f; + + m_accelPitchLimit = 0; + m_decelPitchLimit = 0; + m_bounceKick = 0; + +// m_pitchStiffness = 0; +// m_rollStiffness = 0; +// m_pitchDamping = 0; +// m_rollDamping = 0; +// it's highly unlikely you want zero for the defaults for stiffness and damping... (srj) +// for stiffness: stiffness of the "springs" in the suspension 0 = no stiffness, 1 = totally stiff (huh huh, he said "stiff") +// for damping: 0=perfect spring, bounces forever. 1=glued to terrain. + m_pitchStiffness = 0.1f; + m_rollStiffness = 0.1f; + m_pitchDamping = 0.9f; + m_rollDamping = 0.9f; + m_forwardVelCoef = 0; + m_pitchByZVelCoef = 0; + m_thrustRoll = 0.0f; + m_wobbleRate = 0.0f; + m_minWobble = 0.0f; + m_maxWobble = 0.0f; + m_lateralVelCoef = 0; + m_forwardAccelCoef = 0; + m_lateralAccelCoef = 0; + m_uniformAxialDamping = 1.0f; + m_turnPivotOffset = 0; + m_apply2DFrictionWhenAirborne = false; + m_downhillOnly = false; + m_allowMotiveForceWhileAirborne = false; + m_locomotorWorksWhenDead = false; + m_airborneTargetingHeight = INT_MAX; + m_stickToGround = false; + m_canMoveBackward = false; + m_hasSuspension = false; + m_wheelTurnAngle = 0; + m_maximumWheelExtension = 0; + m_maximumWheelCompression = 0; + m_closeEnoughDist = 1.0f; + m_isCloseEnoughDist3D = FALSE; + m_ultraAccurateSlideIntoPlaceFactor = 0.0f; + + m_wanderWidthFactor = 0.0f; + m_wanderLengthFactor = 1.0f; + m_wanderAboutPointRadius = 0.0f; + + m_rudderCorrectionDegree = 0.0f; + m_rudderCorrectionRate = 0.0f; + m_elevatorCorrectionDegree = 0.0f; + m_elevatorCorrectionRate = 0.0f; + +} + +//------------------------------------------------------------------------------------------------- +LocomotorTemplate::~LocomotorTemplate() +{ + +} + +//------------------------------------------------------------------------------------------------- +void LocomotorTemplate::validate() +{ + // this is ok; parachutes need it! + //DEBUG_ASSERTCRASH(m_lift == 0.0f || m_lift > fabs(TheGlobalData->m_gravity), ("Lift is too low to counteract gravity!")); + //DEBUG_ASSERTCRASH(m_liftDamaged == 0.0f || m_liftDamaged > fabs(TheGlobalData->m_gravity), ("LiftDamaged is too low to counteract gravity!")); + //DEBUG_ASSERTCRASH(m_preferredHeight == 0.0f || (m_behaviorZ == Z_SURFACE_RELATIVE_HEIGHT || m_behaviorZ == Z_ABSOLUTE_HEIGHT || m_appearance == LOCO_THRUST), + // ("You must use Z_SURFACE_RELATIVE_HEIGHT or Z_ABSOLUTE_HEIGHT (or THRUST) to use preferredHeight")); + + // for 'damaged' stuff that was omitted, set 'em to be the same as 'undamaged'... + if (m_maxSpeedDamaged < 0.0f) + m_maxSpeedDamaged = m_maxSpeed; + + if (m_maxTurnRateDamaged < 0.0f) + m_maxTurnRateDamaged = m_maxTurnRate; + + if (m_accelerationDamaged < 0.0f) + m_accelerationDamaged = m_acceleration; + + if (m_liftDamaged < 0.0f) + m_liftDamaged = m_lift; + + if (m_appearance == LOCO_WINGS) + { + if (m_minSpeed <= 0.0f) + { + DEBUG_CRASH(("WINGS should always have positive minSpeeds (otherwise, they hover)")); + m_minSpeed = 0.01f; + } + if (m_minTurnSpeed <= 0.0f) + { + DEBUG_CRASH(("WINGS should always have positive minTurnSpeed")); + m_minTurnSpeed = 0.01f; + } + } + + if (m_appearance == LOCO_THRUST) + { + if (m_behaviorZ != Z_NO_Z_MOTIVE_FORCE || + m_lift != 0.0f || + m_liftDamaged != 0.0f) + { + DEBUG_CRASH(("THRUST locos may not use ZAxisBehavior or lift!\n")); + throw INI_INVALID_DATA; + } + if (m_maxSpeed <= 0.0f) + { + // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing + DEBUG_LOG(("THRUST locos may not have zero m_maxSpeed; healing...\n")); + m_maxSpeed = 0.01f; + } + if (m_maxSpeedDamaged <= 0.0f) + { + // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing + DEBUG_LOG(("THRUST locos may not have zero m_maxSpeedDamaged; healing...\n")); + m_maxSpeedDamaged = 0.01f; + } + if (m_minSpeed <= 0.0f) + { + // if one of these was omitted, it defaults to zero... just quietly heal it here, rather than crashing + DEBUG_LOG(("THRUST locos may not have zero m_minSpeed; healing...\n")); + m_minSpeed = 0.01f; + } + } +} + +//------------------------------------------------------------------------------------------------- +static void parseFrictionPerSec( INI* ini, void * /*instance*/, void *store, const void* /*userData*/ ) +{ + Real fricPerSec = INI::scanReal(ini->getNextToken()); + Real fricPerFrame = fricPerSec * SECONDS_PER_LOGICFRAME_REAL; + *(Real *)store = fricPerFrame; +} + +//------------------------------------------------------------------------------------------------- +const FieldParse* LocomotorTemplate::getFieldParse() const +{ + static const FieldParse TheFieldParse[] = + { + { "Surfaces", INI::parseBitString32, TheLocomotorSurfaceTypeNames, offsetof(LocomotorTemplate, m_surfaces) }, + { "Speed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_maxSpeed) }, + { "SpeedDamaged", INI::parseVelocityReal, NULL, offsetof( LocomotorTemplate, m_maxSpeedDamaged ) }, + { "TurnRate", INI::parseAngularVelocityReal, NULL, offsetof(LocomotorTemplate, m_maxTurnRate) }, + { "TurnRateDamaged", INI::parseAngularVelocityReal, NULL, offsetof( LocomotorTemplate, m_maxTurnRateDamaged ) }, + { "Acceleration", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_acceleration) }, + { "AccelerationDamaged", INI::parseAccelerationReal, NULL, offsetof( LocomotorTemplate, m_accelerationDamaged ) }, + { "Lift", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_lift) }, + { "LiftDamaged", INI::parseAccelerationReal, NULL, offsetof( LocomotorTemplate, m_liftDamaged ) }, + { "Braking", INI::parseAccelerationReal, NULL, offsetof(LocomotorTemplate, m_braking) }, + { "MinSpeed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_minSpeed) }, + { "MinTurnSpeed", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_minTurnSpeed) }, + { "PreferredHeight", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_preferredHeight) }, + { "PreferredHeightDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_preferredHeightDamping) }, + { "CirclingRadius", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_circlingRadius) }, + { "Extra2DFriction", parseFrictionPerSec, NULL, offsetof(LocomotorTemplate, m_extra2DFriction) }, + { "SpeedLimitZ", INI::parseVelocityReal, NULL, offsetof(LocomotorTemplate, m_speedLimitZ) }, + { "MaxThrustAngle", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_maxThrustAngle) }, // yes, angle, not angular-vel + { "ZAxisBehavior", INI::parseIndexList, TheLocomotorBehaviorZNames, offsetof(LocomotorTemplate, m_behaviorZ) }, + { "Appearance", INI::parseIndexList, TheLocomotorAppearanceNames, offsetof(LocomotorTemplate, m_appearance) }, \ + { "GroupMovementPriority", INI::parseIndexList, TheLocomotorPriorityNames, offsetof(LocomotorTemplate, m_movePriority) }, \ + + { "AccelerationPitchLimit", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_accelPitchLimit) }, + { "DecelerationPitchLimit", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_decelPitchLimit) }, + { "BounceAmount", INI::parseAngularVelocityReal, NULL, offsetof(LocomotorTemplate, m_bounceKick) }, + { "PitchStiffness", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchStiffness) }, + { "RollStiffness", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rollStiffness) }, + { "PitchDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchDamping) }, + { "RollDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rollDamping) }, + { "ThrustRoll", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_thrustRoll) }, + { "ThrustWobbleRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wobbleRate) }, + { "ThrustMinWobble", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_minWobble) }, + { "ThrustMaxWobble", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maxWobble) }, + { "PitchInDirectionOfZVelFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_pitchByZVelCoef) }, + { "ForwardVelocityPitchFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_forwardVelCoef) }, + { "LateralVelocityRollFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_lateralVelCoef) }, + { "ForwardAccelerationPitchFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_forwardAccelCoef) }, + { "LateralAccelerationRollFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_lateralAccelCoef) }, + { "UniformAxialDamping", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_uniformAxialDamping) }, + { "TurnPivotOffset", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_turnPivotOffset) }, + { "Apply2DFrictionWhenAirborne", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_apply2DFrictionWhenAirborne) }, + { "DownhillOnly", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_downhillOnly) }, + { "AllowAirborneMotiveForce", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_allowMotiveForceWhileAirborne) }, + { "LocomotorWorksWhenDead", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_locomotorWorksWhenDead) }, + { "AirborneTargetingHeight", INI::parseInt, NULL, offsetof( LocomotorTemplate, m_airborneTargetingHeight ) }, + { "StickToGround", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_stickToGround) }, + { "CanMoveBackwards", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_canMoveBackward) }, + { "HasSuspension", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_hasSuspension) }, + { "FrontWheelTurnAngle", INI::parseAngleReal, NULL, offsetof(LocomotorTemplate, m_wheelTurnAngle) }, + { "MaximumWheelExtension", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maximumWheelExtension) }, + { "MaximumWheelCompression", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_maximumWheelCompression) }, + { "CloseEnoughDist", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_closeEnoughDist) }, + { "CloseEnoughDist3D", INI::parseBool, NULL, offsetof(LocomotorTemplate, m_isCloseEnoughDist3D) }, + { "SlideIntoPlaceTime", INI::parseDurationReal, NULL, offsetof(LocomotorTemplate, m_ultraAccurateSlideIntoPlaceFactor) }, + + { "WanderWidthFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderWidthFactor) }, + { "WanderLengthFactor", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderLengthFactor) }, + { "WanderAboutPointRadius", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_wanderAboutPointRadius) }, + + { "RudderCorrectionDegree", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rudderCorrectionDegree) }, + { "RudderCorrectionRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_rudderCorrectionRate) }, + { "ElevatorCorrectionDegree", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_elevatorCorrectionDegree) }, + { "ElevatorCorrectionRate", INI::parseReal, NULL, offsetof(LocomotorTemplate, m_elevatorCorrectionRate) }, + { NULL, NULL, NULL, 0 } // keep this last + + }; + return TheFieldParse; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +LocomotorStore::LocomotorStore() +{ +} + +//------------------------------------------------------------------------------------------------- +LocomotorStore::~LocomotorStore() +{ + // delete all the templates, then clear out the table. + LocomotorTemplateMap::iterator it; + for (it = m_locomotorTemplates.begin(); it != m_locomotorTemplates.end(); ++it) { + it->second->deleteInstance(); + } + + m_locomotorTemplates.clear(); +} + +//------------------------------------------------------------------------------------------------- +LocomotorTemplate* LocomotorStore::findLocomotorTemplate(NameKeyType namekey) +{ + if (namekey == NAMEKEY_INVALID) + return NULL; + + LocomotorTemplateMap::iterator it = m_locomotorTemplates.find(namekey); + if (it == m_locomotorTemplates.end()) + return NULL; + else + return (*it).second; +} + +//------------------------------------------------------------------------------------------------- +const LocomotorTemplate* LocomotorStore::findLocomotorTemplate(NameKeyType namekey) const +{ + if (namekey == NAMEKEY_INVALID) + return NULL; + + LocomotorTemplateMap::const_iterator it = m_locomotorTemplates.find(namekey); + if (it == m_locomotorTemplates.end()) + { + return NULL; + } + else + { + return (*it).second; + } +} + +//------------------------------------------------------------------------------------------------- +void LocomotorStore::update() +{ +} + +//------------------------------------------------------------------------------------------------- +void LocomotorStore::reset() +{ + // cleanup overrides. + LocomotorTemplateMap::iterator it; + for (it = m_locomotorTemplates.begin(); it != m_locomotorTemplates.end(); ) { + Overridable *locoTemp = it->second->deleteOverrides(); + if (!locoTemp) + { + m_locomotorTemplates.erase(it); + } + else + { + ++it; + } + } +} + +//------------------------------------------------------------------------------------------------- +LocomotorTemplate *LocomotorStore::newOverride( LocomotorTemplate *locoTemplate ) +{ + if (locoTemplate == NULL) + return NULL; + + // allocate new template + LocomotorTemplate *newTemplate = newInstance(LocomotorTemplate); + + // copy data from final override to 'newTemplate' as a set of initial default values + *newTemplate = *locoTemplate; + locoTemplate->setNextOverride(newTemplate); + + newTemplate->markAsOverride(); + + // return the newly created override for us to set values with etc + return newTemplate; + +} // end newOverride + +//------------------------------------------------------------------------------------------------- +/*static*/ void LocomotorStore::parseLocomotorTemplateDefinition(INI* ini) +{ + if (!TheLocomotorStore) + throw INI_INVALID_DATA; + + Bool isOverride = false; + // read the Locomotor name + const char* token = ini->getNextToken(); + NameKeyType namekey = NAMEKEY(token); + + LocomotorTemplate *loco = TheLocomotorStore->findLocomotorTemplate(namekey); + if (loco) { + if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES) { + loco = TheLocomotorStore->newOverride((LocomotorTemplate*) loco->friend_getFinalOverride()); + } + isOverride = true; + } else { + loco = newInstance(LocomotorTemplate); + if (ini->getLoadType() == INI_LOAD_CREATE_OVERRIDES) { + loco->markAsOverride(); + } + } + + loco->friend_setName(token); + ini->initFromINI(loco, loco->getFieldParse()); + loco->validate(); + + // if this is an override, then we want the pointer on the existing named locomotor to point us + // to the override, so don't add it to the map. + if (!isOverride) + TheLocomotorStore->m_locomotorTemplates[namekey] = loco; +} + +//------------------------------------------------------------------------------------------------- +/*static*/ void INI::parseLocomotorTemplateDefinition( INI* ini ) +{ + LocomotorStore::parseLocomotorTemplateDefinition(ini); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- +Locomotor::Locomotor(const LocomotorTemplate* tmpl) +{ + m_template = tmpl; + m_brakingFactor = 1.0f; + m_maxLift = BIGNUM; + m_maxSpeed = BIGNUM; + m_maxAccel = BIGNUM; + m_maxBraking = BIGNUM; + m_maxTurnRate = BIGNUM; + m_flags = 0; + m_closeEnoughDist = m_template->m_closeEnoughDist; + setFlag(IS_CLOSE_ENOUGH_DIST_3D, m_template->m_isCloseEnoughDist3D); +#ifdef CIRCLE_FOR_LANDING + m_circleThresh = 0.0f; +#endif + m_preferredHeight = m_template->m_preferredHeight; + m_preferredHeightDamping = m_template->m_preferredHeightDamping; + + m_angleOffset = GameLogicRandomValueReal(-PI/6, PI/6); + m_offsetIncrement = (PI/40) * (GameLogicRandomValueReal(0.8f, 1.2f)/m_template->m_wanderLengthFactor); + setFlag(OFFSET_INCREASING, GameLogicRandomValue(0,1)); + m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; + + m_speedMultiplier = 1.0; +} + +//------------------------------------------------------------------------------------------------- +Locomotor::Locomotor(const Locomotor& that) +{ + //Added By Sadullah Nader + //Initializations + m_angleOffset = 0.0f; + m_maintainPos.zero(); + + // + + m_template = that.m_template; + m_brakingFactor = that.m_brakingFactor; + m_maxLift = that.m_maxLift; + m_maxSpeed = that.m_maxSpeed; + m_maxAccel = that.m_maxAccel; + m_maxBraking = that.m_maxBraking; + m_maxTurnRate = that.m_maxTurnRate; + m_flags = that.m_flags; + m_closeEnoughDist = that.m_closeEnoughDist; +#ifdef CIRCLE_FOR_LANDING + m_circleThresh = that.m_circleThresh; +#endif + m_preferredHeight = that.m_preferredHeight; + m_preferredHeightDamping = that.m_preferredHeightDamping; + m_angleOffset = that.m_angleOffset; + m_offsetIncrement = that.m_offsetIncrement; +} + +//------------------------------------------------------------------------------------------------- +Locomotor& Locomotor::operator=(const Locomotor& that) +{ + if (this != &that) + { + m_template = that.m_template; + m_brakingFactor = that.m_brakingFactor; + m_maxLift = that.m_maxLift; + m_maxSpeed = that.m_maxSpeed; + m_maxAccel = that.m_maxAccel; + m_maxBraking = that.m_maxBraking; + m_maxTurnRate = that.m_maxTurnRate; + m_flags = that.m_flags; + m_closeEnoughDist = that.m_closeEnoughDist; +#ifdef CIRCLE_FOR_LANDING + m_circleThresh = that.m_circleThresh; +#endif + m_preferredHeight = that.m_preferredHeight; + m_preferredHeightDamping = that.m_preferredHeightDamping; + } + return *this; +} + +//------------------------------------------------------------------------------------------------- +Locomotor::~Locomotor() +{ +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void Locomotor::crc( Xfer *xfer ) +{ + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void Locomotor::xfer( Xfer *xfer ) +{ + // version + const XferVersion currentVersion = 2; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + if (version>=2) { + xfer->xferUnsignedInt(&m_donutTimer); + } + + xfer->xferCoord3D(&m_maintainPos); + xfer->xferReal(&m_brakingFactor); + xfer->xferReal(&m_maxLift); + xfer->xferReal(&m_maxSpeed); + xfer->xferReal(&m_maxAccel); + xfer->xferReal(&m_maxBraking); + xfer->xferReal(&m_maxTurnRate); + xfer->xferReal(&m_closeEnoughDist); +#ifdef CIRCLE_FOR_LANDING + DEBUG_CRASH(("not supported, must fix me")); +#endif + xfer->xferUnsignedInt(&m_flags); + xfer->xferReal(&m_preferredHeight); + xfer->xferReal(&m_preferredHeightDamping); + xfer->xferReal(&m_angleOffset); + xfer->xferReal(&m_offsetIncrement); + + xfer->xferReal(&m_speedMultiplier); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void Locomotor::loadPostProcess( void ) +{ + +} // end loadPostProcess + +//------------------------------------------------------------------------------------------------- +void Locomotor::startMove(void) +{ + // Reset the donut timer. + m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getMaxSpeedForCondition(BodyDamageType condition) const +{ + Real speed; + + if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) + speed = m_template->m_maxSpeed; + else + speed = m_template->m_maxSpeedDamaged; + + speed *= m_speedMultiplier; + + if (speed > m_maxSpeed) + speed = m_maxSpeed; + + return speed; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getMaxTurnRate(BodyDamageType condition) const +{ + Real turn; + + if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) + turn = m_template->m_maxTurnRate; + else + turn = m_template->m_maxTurnRateDamaged; + + turn *= m_speedMultiplier; + + if (turn > m_maxTurnRate) + turn = m_maxTurnRate; + + const Real TURN_FACTOR = 2; + if (getFlag(ULTRA_ACCURATE)) + turn *= TURN_FACTOR; // monster turning ability + + return turn; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getMaxAcceleration(BodyDamageType condition) const +{ + Real accel; + + if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) + accel = m_template->m_acceleration; + else + accel = m_template->m_accelerationDamaged; + + accel *= m_speedMultiplier; + + if (accel > m_maxAccel) + accel = m_maxAccel; + + return accel; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getBraking() const +{ + Real braking = m_template->m_braking; + + braking *= m_speedMultiplier; + + if (braking > m_maxBraking) + braking = m_maxBraking; + + return braking; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::getMaxLift(BodyDamageType condition) const +{ + Real lift; + + if( IS_CONDITION_BETTER( condition, TheGlobalData->m_movementPenaltyDamageState ) ) + lift = m_template->m_lift; + else + lift = m_template->m_liftDamaged; + + lift *= m_speedMultiplier; + + if (lift > m_maxLift) + lift = m_maxLift; + + return lift; +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::locoUpdate_moveTowardsAngle(Object* obj, Real goalAngle) +{ + setFlag(MAINTAIN_POS_IS_VALID, false); + + if (obj == NULL || m_template == NULL) + return; + + PhysicsBehavior *physics = obj->getPhysics(); + if (physics == NULL) + { + DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); + return; + } + + // Skip moveTowardsAngle if physics say you're stunned + if(physics->getIsStunned()) + { + return; + } + +#ifdef DEBUG_OBJECT_ID_EXISTS +// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_moveTowardsAngle %f (%f deg), spd %f (%f)\n",goalAngle,goalAngle*180/PI,physics->getSpeed(),physics->getForwardSpeed2D())); +#endif + + Real minSpeed = getMinSpeed(); + if (minSpeed > 0) + { + // can't stay in one place; move in the desired direction at min speed. + Coord3D desiredPos = *obj->getPosition(); + desiredPos.x += Cos(goalAngle) * minSpeed * 2; + desiredPos.y += Sin(goalAngle) * minSpeed * 2; + // pass a huge num for "dist to goal", so that we don't think we're nearing + // our destination and thus slow down... + const Real onPathDistToGoal = 99999.0f; + Bool blocked = false; + locoUpdate_moveTowardsPosition(obj, desiredPos, onPathDistToGoal, minSpeed, &blocked); + + // don't need to call handleBehaviorZ() here, since locoUpdate_moveTowardsPosition() will do so + return; + } + else + { + DEBUG_ASSERTCRASH(m_template->m_appearance != LOCO_THRUST, ("THRUST should always have minspeeds!\n")); + Coord3D desiredPos = *obj->getPosition(); + desiredPos.x += Cos(goalAngle) * 1000.0f; + desiredPos.y += Sin(goalAngle) * 1000.0f; + PhysicsTurningType rotating = rotateTowardsPosition(obj, desiredPos); + physics->setTurning(rotating); + handleBehaviorZ(obj, physics, *obj->getPosition()); + } + +} + +//------------------------------------------------------------------------------------------------- +PhysicsTurningType Locomotor::rotateTowardsPosition(Object* obj, const Coord3D& goalPos, Real *relAngle) +{ + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real turnRate = getMaxTurnRate(bdt); + + PhysicsTurningType rotating = rotateObjAroundLocoPivot(obj, goalPos, turnRate, relAngle); + return rotating; +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::setPhysicsOptions(Object* obj) +{ + PhysicsBehavior *physics = obj->getPhysics(); + if (physics == NULL) + { + DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); + return; + } + + // crank up the friction in ultra-accurate mode to increase movement precision. + const Real EXTRA_FRIC = 0.5f; + Real extraExtraFriction = getFlag(ULTRA_ACCURATE) ? EXTRA_FRIC : 0.0f; + physics->setExtraFriction(m_template->m_extra2DFriction + extraExtraFriction); + physics->setAllowAirborneFriction(getApply2DFrictionWhenAirborne()); // you'd think we wouldn't want friction in the air, but it's needed for realistic behavior. + physics->setStickToGround(getStickToGround()); // walking guys aren't allowed to catch huge (or even small) air. +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::locoUpdate_moveTowardsPosition(Object* obj, const Coord3D& goalPos, + Real onPathDistToGoal, Real desiredSpeed, Bool *blocked) +{ + setFlag(MAINTAIN_POS_IS_VALID, false); + + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxSpeed = getMaxSpeedForCondition(bdt); + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + Real distToStopAtMaxSpeed = (maxSpeed/getBraking()) * (maxSpeed)/2.0f; + if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > distToStopAtMaxSpeed) + { + setFlag(IS_BRAKING, false); + m_brakingFactor = 1.0f; + } + PhysicsBehavior *physics = obj->getPhysics(); + if (physics == NULL) + { + DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); + return; + } + + // Skip moveTowardsPosition if physics say you're stunned + if(physics->getIsStunned()) + { + return; + } + +#ifdef DEBUG_OBJECT_ID_EXISTS +// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_moveTowardsPosition %f %f %f (dtg %f, spd %f), speed %f (%f)\n",goalPos.x,goalPos.y,goalPos.z,onPathDistToGoal,desiredSpeed,physics->getSpeed(),physics->getForwardSpeed2D())); +#endif + + // + // do not allow for invalid positions that the pathfinder cannot handle ... for airborne + // objects we don't need the pathfinder so we'll ignore this + // + if( BitIsSet( m_template->m_surfaces, LOCOMOTORSURFACE_AIR ) == false && + !TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, obj->getPosition()) && + !getFlag(ALLOW_INVALID_POSITION)) + { + // Somehow, we have gotten to an invalid location. + if (fixInvalidPosition(obj, physics)) + { + // the we adjusted us toward a legal position, so just return. + return; + } + } + + // If the actual distance is farther, then use the actual distance so we get there. + Real dx = goalPos.x - obj->getPosition()->x; + Real dy = goalPos.y - obj->getPosition()->y; + Real dz = goalPos.z - obj->getPosition()->z; + Real dist = sqrt(dx*dx+dy*dy); + if (dist>onPathDistToGoal) + { + if (!obj->isKindOf(KINDOF_PROJECTILE) && dist>2*onPathDistToGoal) + { + setFlag(IS_BRAKING, true); + } + onPathDistToGoal = dist; + } + + Coord3D nullAccel; + + Bool treatAsAirborne = false; + Coord3D pos = *obj->getPosition(); + Real heightAboveSurface = pos.z - TheTerrainLogic->getLayerHeight(pos.x, pos.y, obj->getLayer()); + + if( obj->getStatusBits().test( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) ) + { + heightAboveSurface -= obj->getCarrierDeckHeight(); + } + + if (heightAboveSurface > -(3*3)*TheGlobalData->m_gravity) + { + // If we get high enough to stay up for 3 frames, then we left the ground. + treatAsAirborne = true; + } + // We apply a zero acceleration to all units, as the call to + // applyMotiveForce flags an object as being "driven" by a locomotor, rather + // than being pushed around by objects bumping it. + nullAccel.x = nullAccel.y = nullAccel.z = 0; + physics->applyMotiveForce(&nullAccel); + + if (*blocked) + { + if (desiredSpeed > physics->getVelocityMagnitude()) + { + *blocked = false; + } + if (treatAsAirborne && BitIsSet( m_template->m_surfaces, LOCOMOTORSURFACE_AIR ) ) + { + // Airborne flying objects don't collide for now. jba. + *blocked = false; + } + } + + if (*blocked) + { + physics->scrubVelocity2D(desiredSpeed); // stop if we are about to run into the blocking object. + Real turnRate = getMaxTurnRate(obj->getBodyModule()->getDamageState()); + if (m_template->m_wanderWidthFactor == 0.0f) + { + *blocked = (TURN_NONE != rotateObjAroundLocoPivot(obj, goalPos, turnRate)); + } + + // it is very important to be sure to call this in all situations, even if not moving in 2d space. + handleBehaviorZ(obj, physics, goalPos); + return; + } + + if ( +// srj sez: I don't know why we didn't want HOVERs to allow to "brake". +// we actually really want them to, because it allows much more precise destination positioning. +// m_template->m_appearance == LOCO_HOVER || + m_template->m_appearance == LOCO_WINGS) + { + setFlag(IS_BRAKING, false); + } + + Bool wasBraking = obj->getStatusBits().test( OBJECT_STATUS_BRAKING ); + + physics->setTurning(TURN_NONE); + if (getAllowMotiveForceWhileAirborne() || !treatAsAirborne) + { + switch (m_template->m_appearance) + { + case LOCO_LEGS_TWO: + moveTowardsPositionLegs(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_CLIMBER: + moveTowardsPositionClimb(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_WHEELS_FOUR: + case LOCO_MOTORCYCLE: + moveTowardsPositionWheels( obj, physics, goalPos, onPathDistToGoal, desiredSpeed ); + break; + case LOCO_TREADS: + moveTowardsPositionTreads(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_HOVER: + moveTowardsPositionHover(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_WINGS: + moveTowardsPositionWings(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_THRUST: + moveTowardsPositionThrust(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + case LOCO_OTHER: + default: + moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + break; + } + } + + handleBehaviorZ(obj, physics, goalPos); + // Objects that are braking don't follow the normal physics, so they end up at their destination exactly. + obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ), getFlag(IS_BRAKING) ); + + if (wasBraking) + { + #define MIN_VEL (PATHFIND_CELL_SIZE_F/(LOGICFRAMES_PER_SECOND)) + + Coord3D pos = *obj->getPosition(); + if (obj->isKindOf(KINDOF_PROJECTILE)) + { + // Projectiles never stop braking once they start. jba. + obj->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_BRAKING ) ); + // Projectiles cheat in 3 dimensions. + dist = sqrt(dx*dx+dy*dy+dz*dz); + Real vel = physics->getVelocityMagnitude(); + if (vel < MIN_VEL) + vel = MIN_VEL; + if (vel > dist) + vel = dist; // do not overcompensate! + // Normalize. + if (dist > 0.001f) + { + dist = 1.0f / dist; + dx *= dist; + dy *= dist; + dz *= dist; + + // DEBUG_LOG((">>> Locomotor Braking - d(xyz) = %f / %f / %f\n", dx * vel, dy * vel, dz * vel)); + + pos.x += dx * vel; + pos.y += dy * vel; + pos.z += dz * vel; + } + } + else + { + // not projectiles only cheat in x & y. + // Normalize. + if (dist > 0.001f) + { + Real vel = fabs(physics->getForwardSpeed2D()); + if (vel < MIN_VEL) + vel = MIN_VEL; + if (vel > dist) + vel = dist; // do not overcompensate! + dist = 1.0f / dist; + dx *= dist; + dy *= dist; + pos.x += dx * vel; + pos.y += dy * vel; + } + } + obj->setPosition(&pos); + } + +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionTreads(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxSpeed = getMaxSpeedForCondition(bdt); + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + Real maxAcceleration = getMaxAcceleration(bdt); + + // Locomotion for treaded vehicles, ie tanks. + + // + // Orient toward goal position + // +// Real angle = obj->getOrientation(); +// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); +// Real desiredAngle = angle + relAngle; + Real relAngle ; + PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos, &relAngle); + physics->setTurning(rotating); + + // + // Modulate speed according to turning. The more we have to turn, the slower we go + // + const Real QUAETERPI = PI / 4.0f; + Real angleCoeff = (Real)fabs( relAngle ) / QUAETERPI; + if (angleCoeff > 1.0f) + angleCoeff = 1.0; + + + Real dx = obj->getPosition()->x - goalPos.x; + Real dy = obj->getPosition()->y - goalPos.y; + + + Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; + + +// if (speed < m_minTurnSpeed) +// speed = m_minTurnSpeed; + + Real actualSpeed = physics->getForwardSpeed2D(); + Real slowDownTime = actualSpeed / getBraking(); + Real slowDownDist = (actualSpeed/1.50f) * slowDownTime; + + if (sqr(dx)+sqr(dy) 0.05) { + goalSpeed = actualSpeed*0.6f; + } + + if (onPathDistToGoal < slowDownDist && !getFlag(IS_BRAKING) && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + { + setFlag(IS_BRAKING, true); + m_brakingFactor = 1.1f; + } + + if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > 2.0*slowDownDist) + { + setFlag(IS_BRAKING, false); + } + + if (getFlag(IS_BRAKING)) + { + m_brakingFactor = slowDownDist/onPathDistToGoal; + m_brakingFactor *= m_brakingFactor; + if (m_brakingFactor>MAX_BRAKING_FACTOR) { + m_brakingFactor = MAX_BRAKING_FACTOR; + } + if (slowDownDist>onPathDistToGoal) { + goalSpeed = actualSpeed-getBraking(); + if (goalSpeed<0.0f) goalSpeed= 0.0f; + } else if (slowDownDist>onPathDistToGoal*0.75f) { + goalSpeed = actualSpeed-getBraking()/2.0f; + if (goalSpeed<0.0f) goalSpeed = 0.0f; + } else { + goalSpeed = actualSpeed; + } + } + + + //DEBUG_LOG(("Actual speed %f, Braking factor %f, slowDownDist %f, Pathdist %f, goalSpeed %f\n", + // actualSpeed, m_brakingFactor, slowDownDist, onPathDistToGoal, goalSpeed)); + + // + // Maintain goal speed + // + Real speedDelta = goalSpeed - actualSpeed; + if (speedDelta != 0.0f) + { + Real mass = physics->getMass(); + Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -m_brakingFactor*getBraking(); + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + const Coord3D *dir = obj->getUnitDirectionVector2D(); + + Coord3D force; + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; + force.z = 0.0f; + + // apply forces to object + physics->applyMotiveForce( &force ); + } +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionWheels(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxSpeed = getMaxSpeedForCondition(bdt); + Real maxTurnRate = getMaxTurnRate(bdt); + Real maxAcceleration = getMaxAcceleration(bdt); + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + // Locomotion for wheeled vehicles, ie trucks. + // + // See if we are turning. If so, use the min turn speed. + // + Real turnSpeed = m_template->m_minTurnSpeed; + Real angle = obj->getOrientation(); +// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); +// Real desiredAngle = angle + relAngle; + Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + Real relAngle = stdAngleDiff(desiredAngle, angle); + + Bool moveBackwards = false; + + // Wheeled vehicles can only turn while moving, so make sure the turn speed is reasonable. + if (turnSpeed < maxSpeed/4.0f) + { + turnSpeed = maxSpeed/4.0f; + } + + + Real actualSpeed = physics->getForwardSpeed2D(); + Bool do3pointTurn = false; +#if 1 + if (actualSpeed==0.0f) { + setFlag(MOVING_BACKWARDS, false); + if (m_template->m_canMoveBackward && fabs(relAngle) > PI/2) { + setFlag(MOVING_BACKWARDS, true ); + setFlag(DOING_THREE_POINT_TURN, onPathDistToGoal>5*obj->getGeometryInfo().getMajorRadius()); + } + + } + if (getFlag(MOVING_BACKWARDS)) { + if (fabs(relAngle) < PI/2) { + moveBackwards = false; + setFlag(MOVING_BACKWARDS, false); + } else { + moveBackwards = true; + setFlag(DOING_THREE_POINT_TURN, onPathDistToGoal>5*obj->getGeometryInfo().getMajorRadius()); + do3pointTurn = getFlag(DOING_THREE_POINT_TURN); + if (!do3pointTurn) { + desiredAngle = stdAngleDiff(desiredAngle, PI); + relAngle = stdAngleDiff(desiredAngle, angle); + } + } + } +#endif + + const Real SMALL_TURN = PI / 20.0f; + if ((Real)fabs( relAngle ) > SMALL_TURN) + { + if (desiredSpeed>turnSpeed) + { + desiredSpeed = turnSpeed; + } + } + + Real goalSpeed = desiredSpeed; + if (moveBackwards) { + actualSpeed = -actualSpeed; + } + + + + Real slowDownTime = actualSpeed / getBraking() + 1.0f; + Real slowDownDist = (actualSpeed/1.5f) * slowDownTime + actualSpeed; + Real effectiveSlowDownDist = slowDownDist; + if (effectiveSlowDownDist < 1*PATHFIND_CELL_SIZE) { + effectiveSlowDownDist = 1*PATHFIND_CELL_SIZE; + } + + + const Real FIFTEEN_DEGREES = PI / 12.0f; + const Real PROJECT_FRAMES = LOGICFRAMES_PER_SECOND/2; // Project out 1/2 second. + if (fabs( relAngle ) > FIFTEEN_DEGREES) + { + // If we're turning more than 10 degrees, check & see if we're moving into "impassable territory" + Real distance = PROJECT_FRAMES * (goalSpeed+actualSpeed)/2.0f; + Real targetAngle = obj->getOrientation(); + Real turnFactor = ((goalSpeed+actualSpeed)/2.0f)/turnSpeed; + if (turnFactor > 1.0f) + turnFactor = 1.0f; + Real turnAmount = PROJECT_FRAMES*turnFactor*maxTurnRate/4.0f; + if (relAngle < 0) + { + targetAngle -= turnAmount; + } + else + { + targetAngle += turnAmount; + } + Coord3D offset; + offset.x = Cos(targetAngle)*distance; + offset.y = Sin(targetAngle)*distance; + offset.z = 0; + + const Coord3D* pos = obj->getPosition(); + + Coord3D nextPos; + nextPos.x = pos->x+offset.x; + nextPos.y = pos->y+offset.y; + nextPos.z = pos->z; + + pos = obj->getPosition(); + + Coord3D halfPos; + halfPos.x = pos->x+offset.x/2; + halfPos.y = pos->y+offset.y/2; + halfPos.z = pos->z; + + if (!TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &halfPos) || + !TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &nextPos)) + { + PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos); + physics->setTurning(rotating); + + // apply a zero force to object so that it acts "driven" + Coord3D force; + force.zero(); + physics->applyMotiveForce( &force ); + return; + } + + } + + if (onPathDistToGoal < effectiveSlowDownDist && !getFlag(IS_BRAKING) && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + { + setFlag(IS_BRAKING, true); + m_brakingFactor = 1.1f; + } + + + if (onPathDistToGoal>PATHFIND_CELL_SIZE_F && onPathDistToGoal > 2.0*slowDownDist) + { + setFlag(IS_BRAKING, false); + } + + if (onPathDistToGoal > DONUT_DISTANCE) { + m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; + } else { + if (m_donutTimer < TheGameLogic->getFrame()) { + setFlag(IS_BRAKING, true); + } + } + + if (getFlag(IS_BRAKING)) + { + m_brakingFactor = slowDownDist/onPathDistToGoal; + m_brakingFactor *= m_brakingFactor; + if (m_brakingFactor>MAX_BRAKING_FACTOR) { + m_brakingFactor = MAX_BRAKING_FACTOR; + } + m_brakingFactor = 1.0f; + if (slowDownDist>onPathDistToGoal) { + goalSpeed = actualSpeed-getBraking(); + if (goalSpeed<0.0f) goalSpeed= 0.0f; + } else if (slowDownDist>onPathDistToGoal*0.75f) { + goalSpeed = actualSpeed-getBraking()/2.0f; + if (goalSpeed<0.0f) goalSpeed = 0.0f; + } else { + goalSpeed = actualSpeed; + } + } + + + //DEBUG_LOG(("Actual speed %f, Braking factor %f, slowDownDist %f, Pathdist %f, goalSpeed %f\n", + // actualSpeed, m_brakingFactor, slowDownDist, onPathDistToGoal, goalSpeed)); + + + // Wheeled can only turn while moving. + Real turnFactor = actualSpeed/turnSpeed; + if (turnFactor<0) { + turnFactor = -turnFactor; // in case we're sliding backwards in a 3 pt turn. + } + if (turnFactor > 1.0f) + turnFactor = 1.0f; + Real turnAmount = turnFactor*maxTurnRate; + + PhysicsTurningType rotating; + if (moveBackwards && !do3pointTurn) { + Coord3D backwardPos = *obj->getPosition(); + backwardPos.x += -(goalPos.x - obj->getPosition()->x); + backwardPos.y += -(goalPos.y - obj->getPosition()->y); + rotating = rotateObjAroundLocoPivot(obj, backwardPos, turnAmount); + } else { + rotating = rotateObjAroundLocoPivot(obj, goalPos, turnAmount); + } + + physics->setTurning(rotating); + + // + // Maintain goal speed + // + Real speedDelta = goalSpeed - actualSpeed; + if (moveBackwards) { + speedDelta = -goalSpeed+actualSpeed; + } + if (speedDelta != 0.0f) + { + Real mass = physics->getMass(); + Real acceleration; + if (moveBackwards) { + acceleration = (speedDelta < 0.0f) ? -maxAcceleration : m_brakingFactor*getBraking(); + } else { + acceleration = (speedDelta > 0.0f) ? maxAcceleration : -m_brakingFactor*getBraking(); + } + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + //DEBUG_LOG(("Braking %d, actualSpeed %f, goalSpeed %f, delta %f, accel %f\n", getFlag(IS_BRAKING), + //actualSpeed, goalSpeed, speedDelta, accelForce)); + + const Coord3D *dir = obj->getUnitDirectionVector2D(); + + Coord3D force; + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; + force.z = 0.0f; + + // apply forces to object + physics->applyMotiveForce( &force ); + } + +} +//------------------------------------------------------------------------------------------------- +Bool Locomotor::fixInvalidPosition(Object* obj, PhysicsBehavior *physics) +{ + if (obj->isKindOf(KINDOF_DOZER)) { + // don't fix him. + return false; + } +#define no_IGNORE_INVALID +#ifdef IGNORE_INVALID + // Right now we ignore invalid positions, so when units clip the edge of a building or cliff + // they don't get stuck. jba. 12SEPT02 + return false; +#else + Int dx = 0; + Int dy = 0; + Int i, j; + for (j=-1; j<2; j++) { + for (i=-1; i<2; i++) { + Coord3D thePos = *obj->getPosition(); + thePos.x += i*PATHFIND_CELL_SIZE_F; + thePos.y += j*PATHFIND_CELL_SIZE_F; + if (!TheAI->pathfinder()->validMovementTerrain(obj->getLayer(), this, &thePos)) { + if (i<0) dx += 1; + if (i>0) dx -= 1; + if (j<0) dy += 1; + if (j>0) dy -= 1; + } + } + } + if (dx || dy) { + + Coord3D correction; + correction.x = dx*physics->getMass()/5; + correction.y = dy*physics->getMass()/5; + correction.z = 0; + + Coord3D correctionNormalized = correction; + correctionNormalized.normalize(); + + Coord3D velocity; + // Kill current velocity in the direction of the correction. + velocity = *physics->getVelocity(); + Real dot = (velocity.x*correctionNormalized.x) + (velocity.y*correctionNormalized.y); + if (dot>.25f) { + // It was already leaving. + return false; + } + + + // Kill current accel + //physics->clearAcceleration(); + + if (dot<0) { + dot = sqrt(-dot); + correctionNormalized.x *= dot*physics->getMass(); + correctionNormalized.y *= dot*physics->getMass(); + physics->applyMotiveForce(&correctionNormalized); + } + + // apply correction. + physics->applyMotiveForce(&correction); + return true; + } + return false; +#endif +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::calcMinTurnRadius(BodyDamageType condition, Real* timeToTravelThatDist) const +{ + Real minSpeed = getMinSpeed(); // in dist/frame + Real maxTurnRate = getMaxTurnRate(condition); // in rads/frame + + /* + our minimum circumference will be like so: + + Real minTurnCircum = maxSpeed * (2*PI / maxTurnRate); + + so therefore our minimum turn radius is: + + Real minTurnRadius = minTurnCircum / 2*PI; + + so we just eliminate the middleman: + */ + // if we can't turn, return a huge-but-finite radius rather than NAN... + Real minTurnRadius = (maxTurnRate > 0.0f) ? minSpeed / maxTurnRate : BIGNUM; + + if (timeToTravelThatDist) + *timeToTravelThatDist = (minSpeed > 0.0f) ? (minTurnRadius / minSpeed) : 0.0f; + + return minTurnRadius; +} + + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionLegs(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + if (getIsDownhillOnly() && obj->getPosition()->z < goalPos.z) + { + return; + } + + Real maxAcceleration = getMaxAcceleration( obj->getBodyModule()->getDamageState() ); + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState() ); + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + // Locomotion for infantry. + // + // Orient toward goal position + // + Real actualSpeed = physics->getForwardSpeed2D(); + Real angle = obj->getOrientation(); +// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); +// Real desiredAngle = angle + relAngle; + Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + + if (m_template->m_wanderWidthFactor != 0.0f) { + Real angleLimit = PI/8 * m_template->m_wanderWidthFactor; + // This is the wander offline code - it forces the desired angle away from the goal, so we wander back & forth. jba. + if (getFlag(OFFSET_INCREASING)) { + m_angleOffset += m_offsetIncrement*actualSpeed; + if (m_angleOffset > angleLimit) { + setFlag(OFFSET_INCREASING, false); + } + } else { + m_angleOffset -= m_offsetIncrement*actualSpeed; + if (m_angleOffset<-angleLimit) { + setFlag(OFFSET_INCREASING, true); + } + } + desiredAngle = normalizeAngle(desiredAngle+m_angleOffset); + } + + Real relAngle = stdAngleDiff(desiredAngle, angle); + locoUpdate_moveTowardsAngle(obj, desiredAngle); + + // + // Modulate speed according to turning. The more we have to turn, the slower we go + // + const Real QUARTERPI = PI / 4.0f; + Real angleCoeff = (Real)fabs( relAngle ) / (QUARTERPI); + if (angleCoeff > 1.0f) + angleCoeff = 1.0; + + Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; + + //Real slowDownDist = (actualSpeed - m_template->m_minSpeed) / getBraking(); + Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); + if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + { + goalSpeed = m_template->m_minSpeed; + } + + + + // + // Maintain goal speed + // + Real speedDelta = goalSpeed - actualSpeed; + if (speedDelta != 0.0f) + { + Real mass = physics->getMass(); + Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + const Coord3D *dir = obj->getUnitDirectionVector2D(); + + Coord3D force; + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; + force.z = 0.0f; + + + + // apply forces to object + physics->applyMotiveForce( &force ); + } +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionClimb(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + Real maxAcceleration = getMaxAcceleration( obj->getBodyModule()->getDamageState() ); + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + Real maxSpeed = getMaxSpeedForCondition( obj->getBodyModule()->getDamageState() ); + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + // Locomotion for climbing infantry. + + + Bool moveBackwards = false; + + Real dx, dy, dz; + + Coord3D pos = *obj->getPosition(); + + dx = pos.x - goalPos.x; + dy = pos.y - goalPos.y; + dz = pos.z - goalPos.z; + if (dz*dz > sqr(PATHFIND_CELL_SIZE_F)) { + setFlag(CLIMBING, true); + } + if (fabs(dz)<1) { + setFlag(CLIMBING, false); + } + + + //setFlag(CLIMBING, true); + + if (getFlag(CLIMBING)) { + Coord3D delta = goalPos; + delta.x -= pos.x; + delta.y -= pos.y; + delta.z = 0; + delta.normalize(); + delta.x += pos.x; + delta.y += pos.y; + delta.z = TheTerrainLogic->getGroundHeight(delta.x, delta.y); + if (delta.z < pos.z-0.1) { + moveBackwards = true; + } + + Real groundSlope = fabs(delta.z - pos.z); + if (groundSlope<1.0f) groundSlope = 1.0f; + + if (groundSlope>1.0f) { + desiredSpeed /= groundSlope*4; + } + } + setFlag(MOVING_BACKWARDS, moveBackwards); + + // + // Orient toward goal position + // + Real angle = obj->getOrientation(); +// Real relAngle = ThePartitionManager->getRelativeAngle2D( obj, &goalPos ); +// Real desiredAngle = angle + relAngle; + Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + Real relAngle = stdAngleDiff(desiredAngle, angle); + + if (moveBackwards) { + desiredAngle = stdAngleDiff(desiredAngle, PI); + relAngle = stdAngleDiff(desiredAngle, angle); + } + + locoUpdate_moveTowardsAngle(obj, desiredAngle); + + // + // Modulate speed according to turning. The more we have to turn, the slower we go + // + const Real QUARTERPI = PI / 4.0f; + Real angleCoeff = (Real)fabs( relAngle ) / (QUARTERPI); + if (angleCoeff > 1.0f) + angleCoeff = 1.0; + + Real goalSpeed = (1.0f - angleCoeff) * desiredSpeed; + + Real actualSpeed = physics->getForwardSpeed2D(); + + if (moveBackwards) { + actualSpeed = -actualSpeed; + } + + //Real slowDownDist = (actualSpeed - m_template->m_minSpeed) / getBraking(); + Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); + if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + { + goalSpeed = m_template->m_minSpeed; + } + + // + // Maintain goal speed + // + Real speedDelta = goalSpeed - actualSpeed; + if (moveBackwards) { + speedDelta = -goalSpeed+actualSpeed; + } + if (speedDelta != 0.0f) + { + Real mass = physics->getMass(); + Real acceleration; + if (moveBackwards) { + acceleration = (speedDelta < 0.0f) ? -maxAcceleration : getBraking(); + } else { + acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); + } + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + const Coord3D *dir = obj->getUnitDirectionVector2D(); + + Coord3D force; + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; + force.z = 0.0f; + + // apply forces to object + physics->applyMotiveForce( &force ); + } +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionWings(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ +#ifdef CIRCLE_FOR_LANDING + if (m_circleThresh > 0.0f) + { + // if we are going a mostly-vertical maneuver, circle in order to + // gain/lose altitude, then resume course... + const Coord3D* pos = obj->getPosition(); + Real dx = goalPos.x - pos->x; + Real dy = goalPos.y - pos->y; + Real dz = goalPos.z - pos->z; + if (fabs(dz) > m_circleThresh) + { + // aim for the spot on the opposite side of the circle. + + // find the direction towards our goal pos + Real angleTowardPos = + (isNearlyZero(dx) && isNearlyZero(dy)) ? + obj->getOrientation() : + atan2(dy, dx); + + Real aimDir = (PI - PI/8); + angleTowardPos += aimDir; + + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real turnRadius = calcMinTurnRadius(bdt, NULL) * 4; + + // project a spot "radius" dist away from it, in that dir + Coord3D desiredPos = goalPos; + desiredPos.x += Cos(angleTowardPos) * turnRadius; + desiredPos.y += Sin(angleTowardPos) * turnRadius; + moveTowardsPositionOther(obj, physics, desiredPos, 0, desiredSpeed); + return; + } + } +#endif + + // handle the 2D component. + moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionHover(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + // handle the 2D component. + moveTowardsPositionOther(obj, physics, goalPos, onPathDistToGoal, desiredSpeed); + + // Only hover locomotors care about their OverWater special effects. (OverWater also affects speed, so this is not a client thing) + Coord3D newPosition = *obj->getPosition(); + if( TheTerrainLogic->isUnderwater( newPosition.x, newPosition.y ) ) + { + if( ! getFlag( OVER_WATER ) ) + { + // Change my model condition because I used to not be over water, but now I am + setFlag( OVER_WATER, TRUE ); + obj->setModelConditionState( MODELCONDITION_OVER_WATER ); + } + } + else + { + if( getFlag( OVER_WATER ) ) + { + // Here, I was, but now I'm not + setFlag( OVER_WATER, FALSE ); + obj->clearModelConditionState( MODELCONDITION_OVER_WATER ); + } + } +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionThrust(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + + Real maxForwardSpeed = getMaxSpeedForCondition(bdt); + desiredSpeed = clamp(m_template->m_minSpeed, desiredSpeed, maxForwardSpeed); + Real actualForwardSpeed = physics->getForwardSpeed3D(); + + if (getBraking() > 0) + { + //Real slowDownDist = (actualForwardSpeed - m_template->m_minSpeed) / getBraking(); + Real slowDownDist = calcSlowDownDist(actualForwardSpeed, m_template->m_minSpeed, getBraking()); + if (onPathDistToGoal < slowDownDist && !getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + desiredSpeed = m_template->m_minSpeed; + } + + Coord3D localGoalPos = goalPos; +#ifdef USE_ZDIR_DAMPING + Real zDirDamping = 0.0f; +#endif + + //out of the handleBehaviorZ() function + Coord3D pos = *obj->getPosition(); + if( m_preferredHeight != 0.0f && !getFlag(PRECISE_Z_POS) ) + { + // If we have a preferred flight height, and we haven't been told explicitly to ignore it... + Real surfaceHt = getSurfaceHtAtPt(pos.x, pos.y); + localGoalPos.z = m_preferredHeight + surfaceHt; +// localGoalPos.z = goalPos.z; + Real delta = localGoalPos.z - pos.z; + delta *= getPreferredHeightDamping(); + localGoalPos.z = pos.z + delta; + +#ifdef USE_ZDIR_DAMPING + // closer we get to the preferred height, less we adjust z-thrust, + // so we tend to "level out" at that height. we don't use this till + // below, but go ahead and calc it now... + Real MAX_VERTICAL_DAMP_RANGE = m_preferredHeight * 0.5; + delta = fabs(delta); + if (delta > MAX_VERTICAL_DAMP_RANGE) + delta = MAX_VERTICAL_DAMP_RANGE; + zDirDamping = 1.0f - (delta / MAX_VERTICAL_DAMP_RANGE); +#endif + } + + Vector3 forwardDir = obj->getTransformMatrix()->Get_X_Vector(); + + // Maintain goal speed + Real forwardSpeedDelta = desiredSpeed - actualForwardSpeed; + Real maxAccel = (forwardSpeedDelta > 0.0f || getBraking() == 0) ? getMaxAcceleration(bdt) : -getBraking(); + Real maxTurnRate = getMaxTurnRate(bdt); + + // what direction do we need to thrust in, in order to reach the goalpos? + Vector3 desiredThrustDir; + calcDirectionToApplyThrust(obj, physics, localGoalPos, maxAccel, desiredThrustDir); + + // we might not be able to thrust in that dir, so thrust as closely as we can + Real maxThrustAngle = (maxTurnRate > 0) ? (m_template->m_maxThrustAngle) : 0; + Vector3 thrustDir; + Real thrustAngle = tryToRotateVector3D(maxThrustAngle, forwardDir, desiredThrustDir, thrustDir); + + // note that we are trying to orient in the direction of our vel, not the dir of our thrust. + if (!isNearlyZero(physics->getVelocityMagnitude())) + { + const Coord3D* veltmp = physics->getVelocity(); + Vector3 vel(veltmp->x, veltmp->y, veltmp->z); + Bool adjust = true; + if( obj->getStatusBits().test( OBJECT_STATUS_BRAKING ) ) + { + //Real closeInDist = 150.0f; // TODO: get/set this from missileAI? + //Real af = 1.0f - __min((onPathDistToGoal / closeInDist), 1.0); + + //if (af > 0.0f) { + + // vel.Set( + // vel.X * (1.0f - af) + (goalPos.x - pos.x) * af, + // vel.Y * (1.0f - af) + (goalPos.y - pos.y) * af, + // vel.Z * (1.0f - af) + (goalPos.z - pos.z) * af + // ); + // if (isNearlyZero(sqr(vel.X) + sqr(vel.Y) + sqr(vel.Z))) { + // // we are at target. + // adjust = false; + // } + // maxTurnRate = (1.0f + (af * 2.0f) ) * maxTurnRate; + //} + + // DEBUG_LOG((">>> moveTowardsPositionThrust - Braking - maxTurnRate = %f\n", maxTurnRate)); + + // align to target, cause that's where we're going anyway. + + vel.Set(goalPos.x - pos.x, goalPos.y-pos.y, goalPos.z-pos.z); + if (isNearlyZero(sqr(vel.X)+sqr(vel.Y)+sqr(vel.Z))) { + // we are at target. + adjust = false; + } + maxTurnRate = 3*maxTurnRate; + } +#ifdef USE_ZDIR_DAMPING + if (zDirDamping != 0.0f) + { + Vector3 vel2D(veltmp->x, veltmp->y, 0); + // no need to normalize -- this call does that internally + tryToRotateVector3D(-zDirDamping, vel, vel2D, vel); + } +#endif + if (adjust) { + /*Real orient =*/ tryToOrientInThisDirection3D(obj, maxTurnRate, vel); + } + } + + if (forwardSpeedDelta != 0.0f || thrustAngle != 0.0f) + { + if (maxForwardSpeed <= 0.0f) + { + maxForwardSpeed = 0.01f; // In some cases, this is 0, hack for now. jba. + } + Real damping = clamp(0.0f, maxAccel / maxForwardSpeed, 1.0f); + Vector3 curVel(physics->getVelocity()->x, physics->getVelocity()->y, physics->getVelocity()->z); + + Vector3 accelVec = thrustDir * maxAccel - curVel * damping; + //DEBUG_LOG(("accel %f (max %f) vel %f (max %f) damping %f\n",accelVec.Length(),maxAccel,curVel.Length(),maxForwardSpeed,damping)); + + Real mass = physics->getMass(); + + Coord3D force; + force.x = mass * accelVec.X; + force.y = mass * accelVec.Y; + force.z = mass * accelVec.Z; + + // apply forces to object + physics->applyMotiveForce( &force ); + } +} + +//------------------------------------------------------------------------------------------------- +/*static*/ Real Locomotor::getSurfaceHtAtPt(Real x, Real y) +{ + Real ht = 0; + + Real z,waterZ; + if (TheTerrainLogic->isUnderwater(x, y, &waterZ, &z)) { + ht += waterZ; + } else { + ht += z; + } + + return ht; +} + +//------------------------------------------------------------------------------------------------- +Real Locomotor::calcLiftToUseAtPt(Object* obj, PhysicsBehavior *physics, Real curZ, Real surfaceAtPt, Real preferredHeight) +{ + /* + take the classic equation: + + x = x0 + v*t + 0.5*a*t^2 + + and solve for acceleration. + */ + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxGrossLift = getMaxLift(bdt); + Real maxNetLift = maxGrossLift + TheGlobalData->m_gravity; // note that gravity is always negative. + if (maxNetLift < 0) + maxNetLift = 0; + Real curVelZ = physics->getVelocity()->z; + // going down, braking is limited by net lift; going up, braking is limited by gravity + Real maxAccel; + if (getFlag(ULTRA_ACCURATE)) + maxAccel = (curVelZ < 0) ? 2*maxNetLift : -2*maxNetLift; + else + maxAccel = (curVelZ < 0) ? maxNetLift : TheGlobalData->m_gravity; + // see how far we need to slow to dead stop, given max braking + Real desiredAccel; + const Real TINY_ACCEL = 0.001f; + if (fabs(maxAccel) > TINY_ACCEL) + { + Real deltaZ = preferredHeight - curZ; + // calc how far it will take for us to go from cur speed to zero speed, at max accel. + // Real brakeDist = calcSlowDownDist(curVelZ, 0, maxAccel); + // in theory, the above is the correct calculation, but in practice, + // doesn't work in some situations (eg, opening of USA01 map). Why, I dunno. + // But for now I have gone back to the old, looks-incorrect-to-me-but-works calc. (srj) + Real brakeDist = (sqr(curVelZ) / fabs(maxAccel)); + if (fabs(brakeDist) > fabs(deltaZ)) + { + // if the dist-to-accel (or dist-to-brake) is further than the dist-to-go, + // use the max accel. + desiredAccel = maxAccel; + } + else if (fabs(curVelZ) > m_template->m_speedLimitZ) + { + // or, if we're going too fast, limit it here. + desiredAccel = m_template->m_speedLimitZ - curVelZ; + } + else + { + // ok, figure out the correct accel to use to get us there at zero. + // + // dz = v t + 0.5 a t^2 + // thus + // a = 2(dz - v t)/t^2 + // and + // t = (-v +- sqrt(v*v + 2*a*dz))/a + // + // but if we assume t=1, then + // a=2(dz-v) + // then, plug it back in and see if t is really 1... + desiredAccel = 2.0f * (deltaZ - curVelZ); + } + } + else + { + desiredAccel = 0.0f; + } + Real liftToUse = desiredAccel - TheGlobalData->m_gravity; + if (getFlag(ULTRA_ACCURATE)) + { + // in ultra-accurate mode, we allow cheating. + const Real UP_FACTOR = 3.0f; + if (liftToUse > UP_FACTOR*maxGrossLift) + liftToUse = UP_FACTOR*maxGrossLift; + // srj sez: we used to clip lift to zero here (not allowing neg lift). + // however, I now think that allowing neg lift in ultra-accurate mode is + // a good and desirable thing; in particular, it enables jets to complete + // "short" landings more accurately (previously they sometimes would "float" + // down, which sucked.) if you need to bump this back to zero, check it carefully... + else if (liftToUse < -maxGrossLift) + liftToUse = -maxGrossLift; + } + else + { + if (liftToUse > maxGrossLift) + liftToUse = maxGrossLift; + else if (liftToUse < 0.0f) + liftToUse = 0.0f; + } + + return liftToUse; +} + +//------------------------------------------------------------------------------------------------- +PhysicsTurningType Locomotor::rotateObjAroundLocoPivot(Object* obj, const Coord3D& goalPos, + Real maxTurnRate, Real *relAngle) +{ + Real angle = obj->getOrientation(); + Real offset = getTurnPivotOffset(); + + PhysicsTurningType turn = TURN_NONE; + + if (getFlag(IS_BRAKING)) offset = 0.0f; // When braking we do exact movement towards goal, instead of physics. + //Rotating about pivot moves the object, and can make us miss our goal, so it is disabled. jba. + if (offset != 0.0f) + { + Real radius = obj->getGeometryInfo().getBoundingCircleRadius(); + Real turnPointOffset = offset * radius; + + Coord3D turnPos = *obj->getPosition(); + const Coord3D* dir = obj->getUnitDirectionVector2D(); + turnPos.x += dir->x * turnPointOffset; + turnPos.y += dir->y * turnPointOffset; + Real dx =goalPos.x - turnPos.x; + Real dy = goalPos.y - turnPos.y; + // If we are very close to the goal, we twitch due to rounding error. So just return. jba. + if (fabs(dx)<0.1f && fabs(dy)<0.1f) return TURN_NONE; + Real desiredAngle = atan2(dy, dx); + Real amount = stdAngleDiff(desiredAngle, angle); + if (relAngle) *relAngle = amount; + if (amount>maxTurnRate) { + amount = maxTurnRate; + turn = TURN_POSITIVE; + } else if (amount < -maxTurnRate) { + amount = -maxTurnRate; + turn = TURN_NEGATIVE; + } else { + turn = TURN_NONE; + } + +#if 0 + Coord3D desiredPos = *obj->getPosition(); // well, desired Dir, anyway + desiredPos.x += Cos(angle + amount) * radius; + desiredPos.y += Sin(angle + amount) * radius; + + + // so, the thing is, we want to rotate ourselves so that our *center* is rotated + // by the given amount, but the rotation must be around turnPos. so do a little + // back-calculation. + Real angleDesiredForTurnPos = atan2(desiredPos.y - turnPos.y, desiredPos.x - turnPos.x); + amount = angleDesiredForTurnPos - angle; +#endif + /// @todo srj -- there's probably a more efficient & more direct way to do this. find it. + Matrix3D mtx; + Matrix3D tmp(1); + tmp.Translate(turnPos.x, turnPos.y, 0); + tmp.In_Place_Pre_Rotate_Z(amount); + tmp.Translate(-turnPos.x, -turnPos.y, 0); + + mtx.mul(tmp, *obj->getTransformMatrix()); + + obj->setTransformMatrix(&mtx); + } + else + { + Real desiredAngle = atan2(goalPos.y - obj->getPosition()->y, goalPos.x - obj->getPosition()->x); + Real amount = stdAngleDiff(desiredAngle, angle); + if (relAngle) *relAngle = amount; + if (amount>maxTurnRate) { + amount = maxTurnRate; + turn = TURN_POSITIVE; + } else if (amount < -maxTurnRate) { + amount = -maxTurnRate; + turn = TURN_NEGATIVE; + } else { + turn = TURN_NONE; + } + obj->setOrientation( normalizeAngle(angle + amount) ); + } + return turn; +} + +//------------------------------------------------------------------------------------------------- +/* + return true if we can maintain the position without being called every frame (eg, we are + resting on the ground), false if not (eg, we are hovering or circling) +*/ +Bool Locomotor::handleBehaviorZ(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos) +{ + Bool requiresConstantCalling = TRUE; + + // keep the agent aligned on the terrain + switch(m_template->m_behaviorZ) + { + case Z_NO_Z_MOTIVE_FORCE: + // nothing to do. + requiresConstantCalling = FALSE; + break; + + case Z_SEA_LEVEL: + requiresConstantCalling = TRUE; + if( !obj->isDisabledByType( DISABLED_HELD ) ) + { + Coord3D pos = *obj->getPosition(); + Real waterZ; + if (TheTerrainLogic->isUnderwater(pos.x, pos.y, &waterZ)) { + pos.z = waterZ; + } else { + pos.z = TheTerrainLogic->getLayerHeight(pos.x, pos.y, obj->getLayer()); + } + obj->setPosition(&pos); + } + break; + + case Z_FIXED_SURFACE_RELATIVE_HEIGHT: + case Z_FIXED_ABSOLUTE_HEIGHT: + requiresConstantCalling = TRUE; + { + Coord3D pos = *obj->getPosition(); + Bool surfaceRel = (m_template->m_behaviorZ == Z_FIXED_SURFACE_RELATIVE_HEIGHT); + Real surfaceHt = surfaceRel ? getSurfaceHtAtPt(pos.x, pos.y) : 0.0f; + pos.z = m_preferredHeight + (surfaceRel ? surfaceHt : 0); + obj->setPosition(&pos); + } + break; + + case Z_RELATIVE_TO_GROUND_AND_BUILDINGS: + requiresConstantCalling = TRUE; + { + // srj sez: use getGroundOrStructureHeight(), because someday it will cache building heights... + Coord3D pos = *obj->getPosition(); + Real surfaceHt = ThePartitionManager->getGroundOrStructureHeight(pos.x, pos.y); + + pos.z = m_preferredHeight + surfaceHt; + + obj->setPosition(&pos); + + } + break; + case Z_SMOOTH_RELATIVE_TO_HIGHEST_LAYER: + requiresConstantCalling = TRUE; + { + if (m_preferredHeight != 0.0f || getFlag(PRECISE_Z_POS)) + { + Coord3D pos = *obj->getPosition(); + + // srj sez: if we aren't on the ground, never find the ground layer + PathfindLayerEnum layerAtDest = obj->getLayer(); + if (layerAtDest == LAYER_GROUND) + layerAtDest = TheTerrainLogic->getHighestLayerForDestination( &pos ); + + Real surfaceHt; + Coord3D normal; + const Bool clip = false; // return the height, even if off the edge of the bridge proper. + surfaceHt = TheTerrainLogic->getLayerHeight( pos.x, pos.y, layerAtDest, &normal, clip ); + + Real preferredHeight = m_preferredHeight + surfaceHt; + if (getFlag(PRECISE_Z_POS)) + preferredHeight = goalPos.z; + + Real delta = preferredHeight - pos.z; + delta *= getPreferredHeightDamping(); + preferredHeight = pos.z + delta; + + Real liftToUse = calcLiftToUseAtPt(obj, physics, pos.z, surfaceHt, preferredHeight); + + //DEBUG_LOG(("HandleBZ %d LiftToUse %f\n",TheGameLogic->getFrame(),liftToUse)); + if (liftToUse != 0.0f) + { + Coord3D force; + force.x = 0.0f; + force.y = 0.0f; + force.z = liftToUse * physics->getMass(); + physics->applyMotiveForce(&force); + } + } + } + break; + + case Z_SURFACE_RELATIVE_HEIGHT: + case Z_ABSOLUTE_HEIGHT: + requiresConstantCalling = TRUE; + { + if (m_preferredHeight != 0.0f || getFlag(PRECISE_Z_POS)) + { + Coord3D pos = *obj->getPosition(); + + Bool surfaceRel = (m_template->m_behaviorZ == Z_SURFACE_RELATIVE_HEIGHT); + Real surfaceHt = surfaceRel ? getSurfaceHtAtPt(pos.x, pos.y) : 0.0f; + Real preferredHeight = m_preferredHeight + (surfaceRel ? surfaceHt : 0); + if (getFlag(PRECISE_Z_POS)) + preferredHeight = goalPos.z; + + Real delta = preferredHeight - pos.z; + delta *= getPreferredHeightDamping(); + preferredHeight = pos.z + delta; + + Real liftToUse = calcLiftToUseAtPt(obj, physics, pos.z, surfaceHt, preferredHeight); + + //DEBUG_LOG(("HandleBZ %d LiftToUse %f\n",TheGameLogic->getFrame(),liftToUse)); + if (liftToUse != 0.0f) + { + Coord3D force; + force.x = 0.0f; + force.y = 0.0f; + force.z = liftToUse * physics->getMass(); + physics->applyMotiveForce(&force); + } + } + } + break; + } + + return requiresConstantCalling; +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::moveTowardsPositionOther(Object* obj, PhysicsBehavior *physics, const Coord3D& goalPos, Real onPathDistToGoal, Real desiredSpeed) +{ + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxAcceleration = getMaxAcceleration(bdt); + + // sanity, we cannot use desired speed that is greater than our max speed we are capable of moving at + Real maxSpeed = getMaxSpeedForCondition(bdt); + if( desiredSpeed > maxSpeed ) + desiredSpeed = maxSpeed; + + Real goalSpeed = desiredSpeed; + Real actualSpeed = physics->getForwardSpeed2D(); + + // Locomotion for other things, ie don't know what it is jba :) + // + // Orient toward goal position + // exception: if very close (ie, we could get there in 2 frames or less),\ + // and ULTRA_ACCURATE, just slide into place + // + const Coord3D* pos = obj->getPosition(); + Coord3D dirToApplyForce = *obj->getUnitDirectionVector2D(); + +//DEBUG_ASSERTLOG(!getFlag(ULTRA_ACCURATE),("thresh %f %f (%f %f)\n", +//fabs(goalPos.y - pos->y),fabs(goalPos.x - pos->x), +//fabs(goalPos.y - pos->y)/goalSpeed,fabs(goalPos.x - pos->x)/goalSpeed)); + if (getFlag(ULTRA_ACCURATE) && + fabs(goalPos.y - pos->y) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor && + fabs(goalPos.x - pos->x) <= goalSpeed * m_template->m_ultraAccurateSlideIntoPlaceFactor) + { + // don't turn, just slide in the right direction + physics->setTurning(TURN_NONE); + dirToApplyForce.x = goalPos.x - pos->x; + dirToApplyForce.y = goalPos.y - pos->y; + dirToApplyForce.z = 0.0f; + dirToApplyForce.normalize(); + } + else + { + PhysicsTurningType rotating = rotateTowardsPosition(obj, goalPos); + physics->setTurning(rotating); + } + + if (!getFlag(NO_SLOW_DOWN_AS_APPROACHING_DEST)) + { + Real slowDownDist = calcSlowDownDist(actualSpeed, m_template->m_minSpeed, getBraking()); + if (onPathDistToGoal < slowDownDist) + { + goalSpeed = m_template->m_minSpeed; + } + } + + // + // Maintain goal speed + // + Real speedDelta = goalSpeed - actualSpeed; + if (speedDelta != 0.0f) + { + Real mass = physics->getMass(); + Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + Coord3D force; + force.x = accelForce * dirToApplyForce.x; + force.y = accelForce * dirToApplyForce.y; + force.z = 0.0f; + + // apply forces to object + physics->applyMotiveForce( &force ); + } + +} + + +//------------------------------------------------------------------------------------------------- +/* + return true if we can maintain the position without being called every frame (eg, we are + resting on the ground), false if not (eg, we are hovering or circling) +*/ +Bool Locomotor::locoUpdate_maintainCurrentPosition(Object* obj) +{ + if (!getFlag(MAINTAIN_POS_IS_VALID)) + { + m_maintainPos = *obj->getPosition(); + setFlag(MAINTAIN_POS_IS_VALID, true); + } + + m_donutTimer = TheGameLogic->getFrame()+DONUT_TIME_DELAY_SECONDS*LOGICFRAMES_PER_SECOND; + setFlag(IS_BRAKING, false); + PhysicsBehavior *physics = obj->getPhysics(); + if (physics == NULL) + { + DEBUG_CRASH(("you can only apply Locomotors to objects with Physics")); + return TRUE; + } + +#ifdef DEBUG_OBJECT_ID_EXISTS +// DEBUG_ASSERTLOG(obj->getID() != TheObjectIDToDebug, ("locoUpdate_maintainCurrentPosition %f %f %f, speed %f (%f)\n",m_maintainPos.x,m_maintainPos.y,m_maintainPos.z,physics->getSpeed(),physics->getForwardSpeed2D())); +#endif + + Bool requiresConstantCalling = TRUE; // assume the worst. + switch (m_template->m_appearance) + { + case LOCO_THRUST: + maintainCurrentPositionThrust(obj, physics); + requiresConstantCalling = TRUE; + break; + case LOCO_LEGS_TWO: + maintainCurrentPositionLegs(obj, physics); + requiresConstantCalling = FALSE; + break; + case LOCO_CLIMBER: + maintainCurrentPositionLegs(obj, physics); + requiresConstantCalling = FALSE; + break; + case LOCO_WHEELS_FOUR: + case LOCO_MOTORCYCLE: + maintainCurrentPositionWheels(obj, physics); + requiresConstantCalling = FALSE; + break; + case LOCO_TREADS: + maintainCurrentPositionTreads(obj, physics); + requiresConstantCalling = FALSE; + break; + case LOCO_HOVER: + maintainCurrentPositionHover(obj, physics); + requiresConstantCalling = TRUE; + break; + case LOCO_WINGS: + maintainCurrentPositionWings(obj, physics); + requiresConstantCalling = TRUE; + break; + case LOCO_OTHER: + default: + maintainCurrentPositionOther(obj, physics); + requiresConstantCalling = TRUE; + break; + } + + // but we do need to do this even if not moving, for hovering/Thrusting things. + if (handleBehaviorZ(obj, physics, m_maintainPos)) + requiresConstantCalling = TRUE; + + return requiresConstantCalling; +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::maintainCurrentPositionThrust(Object* obj, PhysicsBehavior *physics) +{ + DEBUG_ASSERTCRASH(getFlag(MAINTAIN_POS_IS_VALID), ("invalid maintain pos")); + /// @todo srj -- should these also use the "circling radius" stuff, like wings? + moveTowardsPositionThrust(obj, physics, m_maintainPos, 0, getMinSpeed()); +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::maintainCurrentPositionWings(Object* obj, PhysicsBehavior *physics) +{ + DEBUG_ASSERTCRASH(getFlag(MAINTAIN_POS_IS_VALID), ("invalid maintain pos")); + physics->setTurning(TURN_NONE); + if (physics->isMotive() && obj->isAboveTerrain()) // no need to stop something that isn't moving (or is just sitting on the ground) + { + + // aim for the spot on the opposite side of the circle. + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real turnRadius = m_template->m_circlingRadius; + if (turnRadius == 0.0f) + turnRadius = calcMinTurnRadius(bdt, NULL); + + // find the direction towards our "maintain pos" + const Coord3D* pos = obj->getPosition(); + Real dx = m_maintainPos.x - pos->x; + Real dy = m_maintainPos.y - pos->y; + Real angleTowardMaintainPos = + (isNearlyZero(dx) && isNearlyZero(dy)) ? + obj->getOrientation() : + atan2(dy, dx); + + Real aimDir = (PI - PI/8); + if (turnRadius < 0) + { + turnRadius = -turnRadius; + aimDir = -aimDir; + } + angleTowardMaintainPos += aimDir; + + // project a spot "radius" dist away from it, in that dir + Coord3D desiredPos = m_maintainPos; + desiredPos.x += Cos(angleTowardMaintainPos) * turnRadius; + desiredPos.y += Sin(angleTowardMaintainPos) * turnRadius; + moveTowardsPositionWings(obj, physics, desiredPos, 0, m_template->m_minSpeed); + } +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::maintainCurrentPositionHover(Object* obj, PhysicsBehavior *physics) +{ + physics->setTurning(TURN_NONE); + if (physics->isMotive()) // no need to stop something that isn't moving. + { + DEBUG_ASSERTCRASH(m_template->m_minSpeed == 0.0f, ("HOVER should always have zero minSpeeds (otherwise, they WING)")); + + BodyDamageType bdt = obj->getBodyModule()->getDamageState(); + Real maxAcceleration = getMaxAcceleration(bdt); + Real actualSpeed = physics->getForwardSpeed2D(); + // + // Stop + // + Real minSpeed = max( 1.0E-10f, m_template->m_minSpeed ); + Real speedDelta = minSpeed - actualSpeed; + if (fabs(speedDelta) > minSpeed) + { + Real mass = physics->getMass(); + Real acceleration = (speedDelta > 0.0f) ? maxAcceleration : -getBraking(); + Real accelForce = mass * acceleration; + + /* + don't accelerate/brake more than necessary. do a quick calc to + see how much force we really need to achieve our goal speed... + */ + Real maxForceNeeded = mass * speedDelta; + if (fabs(accelForce) > fabs(maxForceNeeded)) + accelForce = maxForceNeeded; + + const Coord3D *dir = obj->getUnitDirectionVector2D(); + + Coord3D force; + force.x = accelForce * dir->x; + force.y = accelForce * dir->y; + force.z = 0.0f; + + + // Apply a random kick (if applicable) to dirty-up visually. + // The idea is that chopper pilots have to do course corrections all the time + // Because of changes in wind, pressure, etc. + // Those changes are added here, then the + + + + // apply forces to object + physics->applyMotiveForce( &force ); + } + } + +} + +//------------------------------------------------------------------------------------------------- +void Locomotor::maintainCurrentPositionOther(Object* obj, PhysicsBehavior *physics) +{ + + physics->setTurning(TURN_NONE); + if (physics->isMotive()) // no need to stop something that isn't moving. + { + physics->scrubVelocity2D(0); // stop. + } + +} + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- +LocomotorSet::LocomotorSet() +{ + m_locomotors.clear(); + m_validLocomotorSurfaces = 0; + m_downhillOnly = FALSE; + +} + +//------------------------------------------------------------------------------------------------- +LocomotorSet::LocomotorSet(const LocomotorSet& that) +{ + DEBUG_CRASH(("unimplemented")); +} + +//------------------------------------------------------------------------------------------------- +LocomotorSet& LocomotorSet::operator=(const LocomotorSet& that) +{ + if (this != &that) + { + DEBUG_CRASH(("unimplemented")); + } + return *this; +} + +//------------------------------------------------------------------------------------------------- +LocomotorSet::~LocomotorSet() +{ + clear(); +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void LocomotorSet::crc( Xfer *xfer ) +{ + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void LocomotorSet::xfer( Xfer *xfer ) +{ + // version + const XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // count of vector + UnsignedShort count = m_locomotors.size(); + xfer->xferUnsignedShort( &count ); + + // data + if (xfer->getXferMode() == XFER_SAVE) + { + for (LocomotorVector::iterator it = m_locomotors.begin(); it != m_locomotors.end(); ++it) + { + Locomotor* loco = *it; + AsciiString name = loco->getTemplateName(); + xfer->xferAsciiString(&name); + xfer->xferSnapshot(loco); + } + } + else if (xfer->getXferMode() == XFER_LOAD) + { + // vector should be empty at this point + if (m_locomotors.empty() == FALSE) + { + DEBUG_CRASH(( "LocomotorSet::xfer - vector is not empty, but should be\n" )); + throw XFER_LIST_NOT_EMPTY; + } + + for (UnsignedShort i = 0; i < count; ++i) + { + AsciiString name; + xfer->xferAsciiString(&name); + + const LocomotorTemplate* lt = TheLocomotorStore->findLocomotorTemplate(NAMEKEY(name)); + if (lt == NULL) + { + DEBUG_CRASH(( "LocomotorSet::xfer - template %s not found\n", name.str() )); + throw XFER_UNKNOWN_STRING; + } + + Locomotor* loco = TheLocomotorStore->newLocomotor(lt); + xfer->xferSnapshot(loco); + m_locomotors.push_back(loco); + } + } + + xfer->xferInt(&m_validLocomotorSurfaces); + xfer->xferBool(&m_downhillOnly); + +} + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void LocomotorSet::loadPostProcess( void ) +{ + +} // end loadPostProcess + +//------------------------------------------------------------------------------------------------- +void LocomotorSet::xferSelfAndCurLocoPtr(Xfer *xfer, Locomotor** loco) +{ + xfer->xferSnapshot(this); + + if (xfer->getXferMode() == XFER_SAVE) + { + AsciiString name; + if (*loco) + name = (*loco)->getTemplateName(); + xfer->xferAsciiString(&name); + } + else if (xfer->getXferMode() == XFER_LOAD) + { + AsciiString name; + xfer->xferAsciiString(&name); + + if (name.isEmpty()) + { + *loco = NULL; + } + else + { + for (int i = 0; i < m_locomotors.size(); ++i) + { + if (m_locomotors[i]->getTemplateName() == name) + { + *loco = m_locomotors[i]; + return; + } + } + + DEBUG_CRASH(( "LocomotorSet::xfer - template %s not found\n", name.str() )); + throw XFER_UNKNOWN_STRING; + } + } +} + +//------------------------------------------------------------------------------------------------- +void LocomotorSet::clear() +{ + for (int i = 0; i < m_locomotors.size(); ++i) + { + if (m_locomotors[i]) + m_locomotors[i]->deleteInstance(); + } + m_locomotors.clear(); + m_validLocomotorSurfaces = 0; + m_downhillOnly = FALSE; +} + +//------------------------------------------------------------------------------------------------- +void LocomotorSet::addLocomotor(const LocomotorTemplate* lt) +{ + Locomotor* loco = TheLocomotorStore->newLocomotor(lt); + if (loco) + { + m_locomotors.push_back(loco); + m_validLocomotorSurfaces |= loco->getLegalSurfaces(); + if (loco->getIsDownhillOnly()) + { + m_downhillOnly = TRUE; + } + else // Previous locos were gravity only, but this one isn't! + { + DEBUG_ASSERTCRASH(!m_downhillOnly,("LocomotorSet, YOU CAN NOT MIX DOWNHILL-ONLY LOCOMOTORS WITH NON-DOWNHILL-ONLY ONES.")); + } + + } +} + +//------------------------------------------------------------------------------------------------- +Locomotor* LocomotorSet::findLocomotor(LocomotorSurfaceTypeMask t) +{ + for (LocomotorVector::iterator it = m_locomotors.begin(); it != m_locomotors.end(); ++it) + { + Locomotor* curLocomotor = *it; + if (curLocomotor && (curLocomotor->getLegalSurfaces() & t)) + return curLocomotor; + } + return NULL; +} + + diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 1bf736457ad..2632e6b6e67 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -1,6534 +1,6534 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE Object.cpp //////////////////////////////////////////////////////////////////////////////// -// Simple base object -// Author: Michael S. Booth, October 2000 -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine -#define DEFINE_WEAPONCONDITIONMAP -#include "Common/BitFlagsIO.h" -#include "Common/BuildAssistant.h" -#include "Common/Dict.h" -#include "Common/GameCommon.h" -#include "Common/GameEngine.h" -#include "Common/GameState.h" -#include "Common/ModuleFactory.h" -#include "Common/Player.h" -#include "Common/PlayerList.h" -#include "Common/Radar.h" -#include "Common/SpecialPower.h" -#include "Common/Team.h" -#include "Common/ThingFactory.h" -#include "Common/ThingTemplate.h" -#include "Common/Upgrade.h" -#include "Common/WellKnownKeys.h" -#include "Common/Xfer.h" -#include "Common/XferCRC.h" -#include "Common/PerfTimer.h" - -#include "GameClient/Anim2D.h" -#include "GameClient/ControlBar.h" -#include "GameClient/Drawable.h" -#include "GameClient/Eva.h" -#include "GameClient/GameClient.h" -#include "GameClient/InGameUI.h" - -#include "GameLogic/AI.h" -#include "GameLogic/AIPathfind.h" -#include "GameLogic/ExperienceTracker.h" -#include "GameLogic/FiringTracker.h" -#include "GameLogic/GameLogic.h" -#include "GameLogic/Locomotor.h" - -#include "GameLogic/Module/AIUpdate.h" -#include "GameLogic/Module/AutoHealBehavior.h" -#include "GameLogic/Module/BehaviorModule.h" -#include "GameLogic/Module/BodyModule.h" -#include "GameLogic/Module/CollideModule.h" -#include "GameLogic/Module/ContainModule.h" -#include "GameLogic/Module/CountermeasuresBehavior.h" -#include "GameLogic/Module/CreateModule.h" -#include "GameLogic/Module/DamageModule.h" -#include "GameLogic/Module/DeletionUpdate.h" -#include "GameLogic/Module/DestroyModule.h" -#include "GameLogic/Module/DieModule.h" -#include "GameLogic/Module/DozerAIUpdate.h" -#include "GameLogic/Module/ObjectDefectionHelper.h" -#include "GameLogic/Module/ObjectRepulsorHelper.h" -#include "GameLogic/Module/ObjectSMCHelper.h" -#include "GameLogic/Module/ObjectWeaponStatusHelper.h" -#include "GameLogic/Module/OverchargeBehavior.h" -#include "GameLogic/Module/PhysicsUpdate.h" -#include "GameLogic/Module/PowerPlantUpgrade.h" -#include "GameLogic/Module/ProductionUpdate.h" -#include "GameLogic/Module/RadarUpgrade.h" -#include "GameLogic/Module/RebuildHoleBehavior.h" -#include "GameLogic/Module/SpawnBehavior.h" -#include "GameLogic/Module/SpecialPowerModule.h" -#include "GameLogic/Module/SpecialAbilityUpdate.h" -#include "GameLogic/Module/StatusDamageHelper.h" -#include "GameLogic/Module/StickyBombUpdate.h" -#include "GameLogic/Module/SubdualDamageHelper.h" -#include "GameLogic/Module/ChronoDamageHelper.h" -#include "GameLogic/Module/TempWeaponBonusHelper.h" -#include "GameLogic/Module/ToppleUpdate.h" -#include "GameLogic/Module/UpdateModule.h" -#include "GameLogic/Module/UpgradeModule.h" - -#include "GameLogic/Object.h" -#include "GameLogic/PartitionManager.h" -#include "GameLogic/PolygonTrigger.h" -#include "GameLogic/ScriptEngine.h" -#include "GameLogic/Weapon.h" -#include "GameLogic/WeaponSet.h" -#include "GameLogic/Module/RadarUpdate.h" -#include "GameLogic/Module/PowerPlantUpdate.h" - -#include "Common/CRCDebug.h" -#include "Common/MiscAudio.h" -#include "Common/AudioEventInfo.h" -#include "Common/DynamicAudioEventInfo.h" - -#ifdef RTS_INTERNAL -// for occasional debugging... -//#pragma optimize("", off) -//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") -#endif - -#ifdef DEBUG_OBJECT_ID_EXISTS -ObjectID TheObjectIDToDebug = INVALID_ID; -#endif - -// ------------------------------------------------------------------------------------------------ -static const ModelConditionFlags s_allWeaponFireFlags[WEAPONSLOT_COUNT] = -{ - MAKE_MODELCONDITION_MASK5( - MODELCONDITION_FIRING_A, - MODELCONDITION_BETWEEN_FIRING_SHOTS_A, - MODELCONDITION_RELOADING_A, - MODELCONDITION_PREATTACK_A, - MODELCONDITION_USING_WEAPON_A - ), - MAKE_MODELCONDITION_MASK5( - MODELCONDITION_FIRING_B, - MODELCONDITION_BETWEEN_FIRING_SHOTS_B, - MODELCONDITION_RELOADING_B, - MODELCONDITION_PREATTACK_B, - MODELCONDITION_USING_WEAPON_B - ), - MAKE_MODELCONDITION_MASK5( - MODELCONDITION_FIRING_C, - MODELCONDITION_BETWEEN_FIRING_SHOTS_C, - MODELCONDITION_RELOADING_C, - MODELCONDITION_PREATTACK_C, - MODELCONDITION_USING_WEAPON_C - ), - MAKE_MODELCONDITION_MASK5( - MODELCONDITION_FIRING_D, - MODELCONDITION_BETWEEN_FIRING_SHOTS_D, - MODELCONDITION_RELOADING_D, - MODELCONDITION_PREATTACK_D, - MODELCONDITION_USING_WEAPON_D - ), - MAKE_MODELCONDITION_MASK5( - MODELCONDITION_FIRING_E, - MODELCONDITION_BETWEEN_FIRING_SHOTS_E, - MODELCONDITION_RELOADING_E, - MODELCONDITION_PREATTACK_E, - MODELCONDITION_USING_WEAPON_E - ), - MAKE_MODELCONDITION_MASK5( - MODELCONDITION_FIRING_F, - MODELCONDITION_BETWEEN_FIRING_SHOTS_F, - MODELCONDITION_RELOADING_F, - MODELCONDITION_PREATTACK_F, - MODELCONDITION_USING_WEAPON_F - ), - MAKE_MODELCONDITION_MASK5( - MODELCONDITION_FIRING_G, - MODELCONDITION_BETWEEN_FIRING_SHOTS_G, - MODELCONDITION_RELOADING_G, - MODELCONDITION_PREATTACK_G, - MODELCONDITION_USING_WEAPON_G - ), - MAKE_MODELCONDITION_MASK5( - MODELCONDITION_FIRING_H, - MODELCONDITION_BETWEEN_FIRING_SHOTS_H, - MODELCONDITION_RELOADING_H, - MODELCONDITION_PREATTACK_H, - MODELCONDITION_USING_WEAPON_H - ) -}; - -//------------------------------------------------------------------------------------------------- -extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color); - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -AsciiString DebugDescribeObject(const Object *obj) -{ - if (!obj) - return ""; - - AsciiString ret; - - if (obj->getName().isNotEmpty()) - { - ret.format("Object %d (%s) [%s, owned by player %d (%ls)]", - obj->getID(), obj->getName().str(), obj->getTemplate()->getName().str(), - obj->getControllingPlayer()->getPlayerIndex(), - obj->getControllingPlayer()->getPlayerDisplayName().str()); - } - else - { - ret.format("Object %d [%s, owned by player %d (%ls)]", - obj->getID(), obj->getTemplate()->getName().str(), - obj->getControllingPlayer()->getPlayerIndex(), - obj->getControllingPlayer()->getPlayerDisplayName().str()); - } - - return ret; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Object::Object( const ThingTemplate *tt, const ObjectStatusMaskType &objectStatusMask, Team *team ) : - Thing(tt), - m_indicatorColor(0), - m_ai(NULL), - m_physics(NULL), - m_geometryInfo(tt->getTemplateGeometryInfo()), - m_containedBy(NULL), - m_xferContainedByID(INVALID_ID), - m_containedByFrame(0), - m_behaviors(NULL), - m_body(NULL), - m_contain(NULL), - m_stealth(NULL), - m_partitionData(NULL), - m_radarData(NULL), - m_drawable(NULL), - m_next(NULL), - m_prev(NULL), - m_team(NULL), - m_experienceTracker(NULL), - m_firingTracker(NULL), - m_repulsorHelper(NULL), - m_statusDamageHelper(NULL), - m_tempWeaponBonusHelper(NULL), - m_subdualDamageHelper(NULL), - m_chronoDamageHelper(NULL), - m_smcHelper(NULL), - m_wsHelper(NULL), - m_defectionHelper(NULL), - m_partitionLastLook(NULL), - m_partitionRevealAllLastLook(NULL), - m_partitionLastShroud(NULL), - m_partitionLastThreat(NULL), - m_partitionLastValue(NULL), - m_smcUntil(NEVER), - m_privateStatus(0), - m_formationID(NO_FORMATION_ID), - m_isReceivingDifficultyBonus(FALSE), - m_singleUseCommandUsed(FALSE), - m_scriptStatus(0), - m_enteredOrExitedFrame(0), - m_visionSpiedMask (PLAYERMASK_NONE), - m_numTriggerAreasActive(0) -{ -#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) - m_hasDiedAlready = false; -#endif - //Modules have not been created yet! - m_modulesReady = false; - - // Force the thing template to use the most overridden version of itself - jkmcd - // Note that after this, the object will be using m_template, which forces the usage of the - // most overridden version of tt, so this is okay. - tt = (const ThingTemplate *) tt->getFinalOverride(); - - Int i, modIdx; - AsciiString modName; - - //Added By Sadullah Nader - //Initializations inserted - m_formationOffset.x = m_formationOffset.y = 0.0f; - m_iPos.zero(); - // - for (i = 0; i < MAX_PLAYER_COUNT; ++i) - { - m_visionSpiedBy[i] = 0; - } - - for( i = 0; i < DISABLED_COUNT; i++ ) - { - m_disabledTillFrame[ i ] = NEVER; - } - - m_weaponBonusCondition = 0; - m_curWeaponSetFlags.clear(); - - // sanity - if( TheGameLogic == NULL || tt == NULL ) - { - - assert( 0 ); - return; - - } // end if - - // Object's set of these persist for the life of the object. - m_partitionLastLook = newInstance(SightingInfo); - m_partitionLastLook->reset(); - m_partitionRevealAllLastLook = newInstance(SightingInfo); - m_partitionRevealAllLastLook->reset(); - m_partitionLastShroud = newInstance(SightingInfo); - m_partitionLastShroud->reset(); - m_partitionLastThreat = newInstance(SightingInfo); - m_partitionLastThreat->reset(); - m_partitionLastValue = newInstance(SightingInfo); - m_partitionLastValue->reset(); - - // must set ID to zero, since some of these set methods - // will cause network messages to be sent - // which use this ID. - m_id = INVALID_ID; - m_producerID = INVALID_ID; - m_builderID = INVALID_ID; - - m_status = objectStatusMask; - m_layer = LAYER_GROUND; - - m_group = NULL; - - m_constructionPercent = CONSTRUCTION_COMPLETE; // complete by default - - m_visionRange = tt->friend_calcVisionRange(); - m_shroudClearingRange = tt->friend_calcShroudClearingRange(); - if( m_shroudClearingRange == -1.0f ) - m_shroudClearingRange = m_visionRange;// Backwards compatible, and perfectly logical default to assign - m_shroudRange = 0.0f; - - m_singleUseCommandUsed = false; - - // assign unique object id - setID( TheGameLogic->allocateObjectID() ); - - // - // allocate any modules we need to, we should keep - // this at or near the end of the drawable construction so that we have - // all the valid data about the thing when we create the module - // - Int totalModules = tt->getBehaviorModuleInfo().getCount() + NUM_SLEEP_HELPERS; // need to take into account all the helper modules - - // allocate the publicModule arrays -// pool[]ify - m_behaviors = MSGNEW("ModulePtrs") BehaviorModule*[totalModules + 1]; - BehaviorModule** curB = m_behaviors; - const ModuleInfo& mi = tt->getBehaviorModuleInfo(); - - // set m_team to null before the first call, to avoid naughtiness... - // If no team is specified in the constructor, then assign the object - // to the neutral team. - setTeam(team ? team : ThePlayerList->getNeutralPlayer()->getDefaultTeam()); - - // the helpers are done first -- even before Behaviors! -- in case a module needs - // to call something that uses them. - static const NameKeyType smcHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_SMCHelper" ); - static ObjectSMCHelperModuleData smcModuleData; - smcModuleData.setModuleTagNameKey( smcHelperModuleDataTagNameKey ); - m_smcHelper = newInstance(ObjectSMCHelper)(this, &smcModuleData); - *curB++ = m_smcHelper; - - //Inactive bodies can't take special damage since they can't take damage - Bool isInactiveBody = FALSE; - for( Int infoIndex = 0; infoIndex < mi.getCount(); ++infoIndex ) - { - modName = mi.getNthName(infoIndex); - if (modName.isEmpty()) - continue; - - if( modName.compare("InactiveBody") == 0 ) - { - isInactiveBody = TRUE; - break; - } - } - - if( !isInactiveBody ) - { - static const NameKeyType statusHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_StatusDamageHelper" ); - static StatusDamageHelperModuleData statusModuleData; - statusModuleData.setModuleTagNameKey( statusHelperModuleDataTagNameKey ); - m_statusDamageHelper = newInstance(StatusDamageHelper)(this, &statusModuleData); - *curB++ = m_statusDamageHelper; - - static const NameKeyType subdualHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_SubdualDamageHelper" ); - static SubdualDamageHelperModuleData subdualModuleData; - subdualModuleData.setModuleTagNameKey( subdualHelperModuleDataTagNameKey ); - m_subdualDamageHelper = newInstance(SubdualDamageHelper)(this, &subdualModuleData); - *curB++ = m_subdualDamageHelper; - - static const NameKeyType chronoHelperModuleDataTagNameKey = NAMEKEY("ModuleTag_ChronoDamageHelper"); - static ChronoDamageHelperModuleData chronoModuleData; - chronoModuleData.setModuleTagNameKey(chronoHelperModuleDataTagNameKey); - m_chronoDamageHelper = newInstance(ChronoDamageHelper)(this, &chronoModuleData); - *curB++ = m_chronoDamageHelper; - } - - if (TheAI != NULL - && TheAI->getAiData()->m_enableRepulsors - && isKindOf(KINDOF_CAN_BE_REPULSED)) - { - // if we can ever be a temporary-repulsor, make a repulsor helper. (srj) - static const NameKeyType repulsorHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_RepulsorHelper" ); - static ObjectRepulsorHelperModuleData repulsorModuleData; - repulsorModuleData.setModuleTagNameKey( repulsorHelperModuleDataTagNameKey ); - m_repulsorHelper = newInstance(ObjectRepulsorHelper)(this, &repulsorModuleData); - *curB++ = m_repulsorHelper; - } - - /** @todo srj -- figure out how to create this only on demand. - currently we don't have a good way to add/remove update modules from - an object on-the-fly, so we fake it here, and just skip the creation - if it is impossible for this object to ever defect... */ - - // shrubbery cannot defect. no, really. - if (!tt->isKindOf(KINDOF_SHRUBBERY)) - { - static const NameKeyType defectionModuleDataTagNameKey = NAMEKEY( "ModuleTag_DefectionHelper" ); - static ObjectDefectionHelperModuleData defectionModuleData; - defectionModuleData.setModuleTagNameKey( defectionModuleDataTagNameKey ); - m_defectionHelper = newInstance(ObjectDefectionHelper)(this, &defectionModuleData); - *curB++ = m_defectionHelper; - } - - if (tt->canPossiblyHaveAnyWeapon()) - { - // we only need a firingtracker and wshelper if we can possibly have a weapon. - static const NameKeyType weaponStatusModuleDataTagNameKey = NAMEKEY( "ModuleTag_WeaponStatusHelper" ); - static ObjectWeaponStatusHelperModuleData weaponStatusModuleData; - weaponStatusModuleData.setModuleTagNameKey( weaponStatusModuleDataTagNameKey ); - m_wsHelper = newInstance(ObjectWeaponStatusHelper)(this, &weaponStatusModuleData); - *curB++ = m_wsHelper; - - static const NameKeyType firingTrackerModuleDataTagNameKey = NAMEKEY( "ModuleTag_FiringTrackerHelper" ); - static FiringTrackerModuleData firingTrackerModuleData; - firingTrackerModuleData.setModuleTagNameKey( firingTrackerModuleDataTagNameKey ); - m_firingTracker = newInstance(FiringTracker)(this, &firingTrackerModuleData); - *curB++ = m_firingTracker; - - static const NameKeyType tempWeaponBonusHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_TempWeaponBonusHelper" ); - static TempWeaponBonusHelperModuleData tempWeaponBonusModuleData; - tempWeaponBonusModuleData.setModuleTagNameKey( tempWeaponBonusHelperModuleDataTagNameKey ); - m_tempWeaponBonusHelper = newInstance(TempWeaponBonusHelper)(this, &tempWeaponBonusModuleData); - *curB++ = m_tempWeaponBonusHelper; - } - - // behaviors are always done first, so they get into the publicModule arrays - // before anything else. - for (modIdx = 0; modIdx < mi.getCount(); ++modIdx) - { - modName = mi.getNthName(modIdx); - if (modName.isEmpty()) - continue; - - BehaviorModule* newMod = (BehaviorModule*)TheModuleFactory->newModule(this, modName, mi.getNthData(modIdx), MODULETYPE_BEHAVIOR); - *curB++ = newMod; - - BodyModuleInterface* body = newMod->getBody(); - if (body) - { - DEBUG_ASSERTCRASH(m_body == NULL, ("Duplicate bodies")); - m_body = body; - } - - ContainModuleInterface* contain = newMod->getContain(); - if (contain) - { - DEBUG_ASSERTCRASH(m_contain == NULL, ("Duplicate containers")); - m_contain = contain; - } - - StealthUpdate* stealth = (StealthUpdate*)newMod->getStealth(); - if ( stealth ) - { - DEBUG_ASSERTCRASH( m_stealth == NULL, ("DuplicateStealthUpdates!") ); - m_stealth = stealth; - } - - - AIUpdateInterface* ai = newMod->getAIUpdateInterface(); - if (ai) - { - if( m_ai ) - { - DEBUG_ASSERTCRASH( m_ai == NULL, ("%s has more than one AI module. This is illegal!\n", getTemplate()->getName().str()) ); - } - m_ai = ai; - } - - static NameKeyType key_PhysicsUpdate = NAMEKEY("PhysicsBehavior"); - if (newMod->getModuleNameKey() == key_PhysicsUpdate) - { - DEBUG_ASSERTCRASH(m_physics == NULL, ("You should never have more than one Physics module (%s)\n",getTemplate()->getName().str())); - m_physics = (PhysicsBehavior*)newMod; - } - } - - *curB = NULL; - - AIUpdateInterface *ai = getAIUpdateInterface(); - if (ai) { - ai->setAttitude(getTeam()->getPrototype()->getTemplateInfo()->m_initialTeamAttitude); - if (m_team && m_team->getPrototype() && m_team->getPrototype()->getAttackPriorityName().isNotEmpty()) { - AsciiString name = m_team->getPrototype()->getAttackPriorityName(); - const AttackPriorityInfo *info = TheScriptEngine->getAttackInfo(name); - if (info && info->getName().isNotEmpty()) { - ai->setAttackInfo(info); - } - } - } - - // allocate experience tracker - m_experienceTracker = newInstance(ExperienceTracker)(this); - - // If a valid team has been assigned me, then I have a Player I can ask about my starting level - const Player* controller = getControllingPlayer(); - m_experienceTracker->setVeterancyLevel( controller->getProductionVeterancyLevel( getTemplate()->getName() ) ); - - /// allow for inter-Module resolution - for (BehaviorModule** b = m_behaviors; *b; ++b) - { - (*b)->onObjectCreated(); - } - - m_numTriggerAreasActive = 0; - m_enteredOrExitedFrame = 0; - m_isSelectable = tt->isKindOf(KINDOF_SELECTABLE); - - m_healthBoxOffset.zero();// this is used for units that are amorphous, like angry mob - - //Modules have now been completely created! - m_modulesReady = true; - - TheRadar->addObject( this ); - - // register the object with the GameLogic - TheGameLogic->registerObject( this ); - - //disable occlusion for some time after object is created to allow them to exit the factory/building. - m_safeOcclusionFrame = TheGameLogic->getFrame()+tt->getOcclusionDelay(); - - - m_soleHealingBenefactorID = INVALID_ID; ///< who is the only other object that can give me this non-stacking heal benefit? - m_soleHealingBenefactorExpirationFrame = 0; ///< on what frame can I accept healing (thus to switch) from a new benefactor - - - -} // end Object - -//------------------------------------------------------------------------------------------------- -/** Emit message announcing object's creation - * Note: Have to do this in virtual init() method because virtual methods - * don't become virtual until AFTER the constructor has completed, and we - * need to send our type in this message via virtual getType(). */ -//------------------------------------------------------------------------------------------------- -void Object::initObject() -{ - // Weapons & Damage ------------------------------------------------------------------------------------------------- - // Force the initial weapon set to be instantiated & reloaded. - - //GS No Bad Wrong - // The flags are constructed to empty, and between then and now they may be set in valid ways by onCreate modules. - // We don't want to blow that away. updateWeaponSet is safe to call on its own, so I will move that to the end. -// m_curWeaponSetFlags.clear(); -// m_weaponSet.updateWeaponSet(this); -// m_weaponBonusCondition = 0; - - for (int i = 0; i < WEAPONSLOT_COUNT; ++i) - m_lastWeaponCondition[i] = WSF_INVALID; - - // emit message announcing object's creation - TheGameLogic->sendObjectCreated( this ); - - // If I have a valid team assigned, I can run through my Upgrade modules with his flags - updateUpgradeModules(); - - //If the player has battle plans (America Strategy Center), then apply those bonuses - //to this object if applicable. Internally it validates certain kinds of objects. - const Player* controller = getControllingPlayer(); - if (controller) - { - if (!getReceivingDifficultyBonus() && TheScriptEngine->getObjectsShouldReceiveDifficultyBonus()) - { - setReceivingDifficultyBonus(TRUE); - } - - if (controller->getNumBattlePlansActive() > 0) - { - controller->applyBattlePlanBonusesForObject( this ); - } - } - - - //For each special power module that we have, add it's type to the specialpower bits. This is - //for optimal access later. - for (BehaviorModule** m = m_behaviors; *m; ++m) - { - SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); - if (!sp) - continue; - - const SpecialPowerTemplate *spTemplate = sp->getSpecialPowerTemplate(); - if( spTemplate ) - { - SET_SPECIALPOWERMASK( m_specialPowerBits, spTemplate->getSpecialPowerType() ); - } - } - - // Kris -- All missiles must be projectiles! This is the perfect place to assert them! - // srj: yes, but only in debug... -#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) - if( !isKindOf( KINDOF_PROJECTILE ) ) - { - if( isKindOf( KINDOF_SMALL_MISSILE ) || isKindOf( KINDOF_BALLISTIC_MISSILE ) ) - { - //Warning only... - DEBUG_CRASH( ("Missile %s must also be a KindOf = PROJECTILE in addition to being either a SMALL_MISSILE or PROJECTILE_MISSILE -- call Kris (36844) for questions!", getTemplate()->getName().str() ) ); - } - } -#endif - if (!isKindOf(KINDOF_PROJECTILE) && !isKindOf(KINDOF_INERT)) { - // Notify script conditions to update conditions that consider unit counts. - // We ignore projectiles cause they are frequently created & destroyed, and are not - // of general interest. Normal unit count tests consider tanks or infantry or planes, etc. jba. - TheScriptEngine->notifyOfObjectCreationOrDestruction(); - TheGameLogic->updateObjectsChangedTriggerAreas(); - } - - // Everything (like weaponSet flags) is inited, so check if the WeaponSet needs to change. - m_weaponSet.updateWeaponSet(this); - - if( isKindOf( KINDOF_MINE ) || isKindOf( KINDOF_BOOBY_TRAP ) || isKindOf( KINDOF_DEMOTRAP ) ) - { - ThePlayerList->getNeutralPlayer()->getAcademyStats()->recordMine(); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Object::~Object() -{ - - // tell the AI the building is gone - /// @todo Generalize the notion of objects entering and leaving the world, so we don't have to special case this - TheAI->pathfinder()->removeObjectFromPathfindMap( this ); - - if (!isKindOf(KINDOF_PROJECTILE) && !isKindOf(KINDOF_INERT)) { - // Notify script conditions to update conditions that consider unit counts. - // We ignore projectiles cause they are frequently created & destroyed, and are not - // of general interest. Normal unit count tests consider tanks or infantry or planes, etc. jba. - TheGameLogic->updateObjectsChangedTriggerAreas(); - TheScriptEngine->notifyOfObjectCreationOrDestruction(); - } - - // - // remove from radar before we NULL out the team ... the order of ops are critical here - // because the radar code will sometimes look at the team info and it is assumed through - // the team and player code that the team is valid - // - if( m_radarData ) - TheRadar->removeObject( this ); - - // emit message announcing object's destruction. Again, order is important; we must do this - // before wiping out the team. - TheGameLogic->sendObjectDestroyed( this ); - - // empty the team - setTeam( NULL ); - - // Object's set of these persist for the life of the object. - m_partitionLastLook->deleteInstance(); - m_partitionLastLook = NULL; - m_partitionRevealAllLastLook->deleteInstance(); - m_partitionRevealAllLastLook = NULL; - m_partitionLastShroud->deleteInstance(); - m_partitionLastShroud = NULL; - m_partitionLastThreat->deleteInstance(); - m_partitionLastThreat = NULL; - m_partitionLastValue->deleteInstance(); - m_partitionLastValue = NULL; - - // remove the object from the partition system if present - if( m_partitionData ) - ThePartitionManager->unRegisterObject( this ); - - // if we are in a group, remove us - if (m_group) - m_group->remove( this ); - - // note, do NOT free these, there are just a shadow copy! - m_ai = NULL; - m_physics = NULL; - - // delete any modules present - for (BehaviorModule** b = m_behaviors; *b; ++b) - { - (*b)->deleteInstance(); - *b = NULL; // in case other modules call findModule from their dtor! - } - - delete [] m_behaviors; - m_behaviors = NULL; - - if( m_experienceTracker ) - m_experienceTracker->deleteInstance(); - - m_experienceTracker = NULL; - - // we don't need to delete these, there were deleted on the m_behaviors list - m_firingTracker = NULL; - m_repulsorHelper = NULL; - - m_statusDamageHelper = NULL; - m_tempWeaponBonusHelper = NULL; - m_subdualDamageHelper = NULL; - m_chronoDamageHelper = NULL; - m_smcHelper = NULL; - m_wsHelper = NULL; - m_defectionHelper = NULL; - - // reset id to zero so we never mistaken grab "dead" objects - m_id = INVALID_ID; - - // Instead of removing it from the named cache, notify the script engine that it has died. - // The script engine will remove it from the cache if necessary. The script engine needs to take - // a crack at this in case it is the current "This Object" pointer. - TheScriptEngine->notifyOfObjectDestruction(this); -} - -//------------------------------------------------------------------------------------------------- -/// this object now contained in "containedBy" -//------------------------------------------------------------------------------------------------- -void Object::onContainedBy( Object *containedBy ) -{ - setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_UNSELECTABLE ) ); - if (containedBy && containedBy->getContain()->isEnclosingContainerFor(this)) - setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) ); - else - clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) ); - m_containedBy = containedBy; - m_containedByFrame = TheGameLogic->getFrame(); - - handlePartitionCellMaintenance(); // which should unlook me now that I am contained - -} - -//------------------------------------------------------------------------------------------------- -/// this object no longer contained in "containedBy" -//------------------------------------------------------------------------------------------------- -void Object::onRemovedFrom( Object *removedFrom ) -{ - clearStatus( MAKE_OBJECT_STATUS_MASK2( OBJECT_STATUS_MASKED, OBJECT_STATUS_UNSELECTABLE ) ); - m_containedBy = NULL; - m_containedByFrame = 0; - - handlePartitionCellMaintenance(); // get a clean look, now that I am outdoors, again - -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Int Object::getTransportSlotCount() const -{ - Int count = getTemplate()->getRawTransportSlotCount(); - ContainModuleInterface* contain = getContain(); - if ( contain && contain->isSpecialZeroSlotContainer() ) - { - count = 0; - const ContainedItemsList* items = contain->getContainedItemsList(); - if (items) - { - for (ContainedItemsList::const_iterator it = items->begin(); it != items->end(); ++it) - { - count += (*it)->getTransportSlotCount(); - } - } - } - return count; -} - -//------------------------------------------------------------------------------------------------- -/** Run from GameLogic::destroyObject */ -//------------------------------------------------------------------------------------------------- -void Object::onDestroy() -{ - - // This is the old cleanUpContain safeguard. Say goodbye so they don't try to look us up. - if( m_containedBy && m_containedBy->getContain() ) - { - m_containedBy->getContain()->removeFromContain( this ); - } - - // - // run the onDelete on all modules present so they each have an opportunity to cleanup - // anything they need to ... including talking to any other modules - // - for (BehaviorModule** b = m_behaviors; *b; ++b) - { - (*b)->onDelete(); - } - - //Have to remove ourself from looking as well. RebuildHoleWorkers definately hit here. - handlePartitionCellMaintenance(); -} // end onDestroy - -//============================================================================= -//============================================================================= -void Object::setGeometryInfo(const GeometryInfo& geom) -{ - m_geometryInfo = geom; - if( m_partitionData ) - { - // if our geometry changes, we unregister and re-register with the partitionmgr - // so that our size gets updated appropriately. this shouldn't be a problem - // unless setGeometryInfo gets called frequently. (srj) - ThePartitionManager->unRegisterObject( this ); - ThePartitionManager->registerObject( this ); - } - - if (m_drawable) - m_drawable->reactToGeometryChange(); -} - -//============================================================================= -//============================================================================= -void Object::setGeometryInfoZ( Real newZ ) -{ - // A Z change only does not need to un/register with the PartitionManager - m_geometryInfo.setMaxHeightAbovePosition( newZ ); - - if (m_drawable) - m_drawable->reactToGeometryChange(); -} - -//============================================================================= -void Object::friend_setUndetectedDefector( Bool status ) -{ - if (status) - m_privateStatus |= UNDETECTED_DEFECTOR; - else - m_privateStatus &= ~UNDETECTED_DEFECTOR; -} - -//============================================================================= -void Object::restoreOriginalTeam() -{ - if( m_team == NULL || m_originalTeamName.isEmpty() ) - return; - - Team* origTeam = TheTeamFactory->findTeam(m_originalTeamName); - if (origTeam == NULL) - { - DEBUG_CRASH(("Object original team (%s) could not be found or created! (srj)\n",m_originalTeamName.str())); - return; - } - - if (m_team == origTeam) - { - DEBUG_CRASH(("Object appears to still be on its original team, so why are we attempting to restore it? (srj)\n")); - return; - } - - setTeam(origTeam); -} - -//============================================================================= -//============================================================================= -void Object::setTeam( Team *team ) -{ - // In order to prevent spawning useful units for a player after he dies, we - // just assign objects to the neutral player if we try to misbehave. - if (team && !team->getControllingPlayer()->isPlayerActive()) - team = ThePlayerList->getNeutralPlayer()->getDefaultTeam(); - - setTemporaryTeam(team); - m_originalTeamName = m_team ? m_team->getName() : AsciiString::TheEmptyString; -} - -//============================================================================= -//============================================================================= -void Object::setTemporaryTeam( Team *team ) -{ - const Bool restoring = false; - setOrRestoreTeam(team, restoring); -} - -//============================================================================= -//============================================================================= -void Object::setOrRestoreTeam( Team* team, Bool restoring ) -{ - // don't do anything if the team hasn't changed - if( m_team == team ) - return; - - Team* oldTeam = m_team; - - // Before Switch ////////////////////////// - if (m_team) - { - if (m_team->isInList_TeamMemberList(this)) - { - m_team->removeFrom_TeamMemberList(this); - m_team->getControllingPlayer()->becomingTeamMember(this, false); - } - } - - // Switch ////////////////////////// - m_team = team; - - // After Switch ////////////////////////// - if (m_team) - { - if (!m_team->isInList_TeamMemberList(this)) - { - m_team->prependTo_TeamMemberList(this); - m_team->getControllingPlayer()->becomingTeamMember(this, true); - } - - // now, adjust the attitude of the unit to its new team. - const TeamPrototype* proto = m_team->getPrototype(); - if (proto && proto->getTemplateInfo()) - { - AIUpdateInterface *ai = getAIUpdateInterface(); - if (ai) - { - ai->setAttitude(proto->getTemplateInfo()->m_initialTeamAttitude); - if (proto->getAttackPriorityName().isNotEmpty()) { - AsciiString name = proto->getAttackPriorityName(); - const AttackPriorityInfo *info = TheScriptEngine->getAttackInfo(name); - if (info && info->getName().isNotEmpty()) { - ai->setAttackInfo(info); - } - } - } - } - // emit message announcing object's new alliance - Drawable *draw = getDrawable(); - if (draw) - draw->changedTeam(); - } - - // This can't just go in ::defect, because some things just do setTeam. The act of - // setting a new team needs to tell the modules and do other important stuff. - // And it needs to happen after the switch. - if( oldTeam && team && !restoring ) - onCapture( oldTeam->getControllingPlayer(), team->getControllingPlayer() ); - - // - // the team changed we have a change in priorities on the radar if we are - // a candidate for the radar as it is - // - if( m_radarData ) - { - - // removing it and adding it will cause a resort to happen - TheRadar->removeObject( this ); - TheRadar->addObject( this ); - } - - // Tell TheInGameUI that the object has changed hands - Int oldPlayerIndex = (oldTeam)?(oldTeam->getControllingPlayer()->getPlayerIndex()):-1; - Int newPlayerIndex = (m_team)?(m_team->getControllingPlayer()->getPlayerIndex()):-1; - if (oldPlayerIndex != newPlayerIndex) - TheInGameUI->objectChangedTeam(this, oldPlayerIndex, newPlayerIndex); -} - -//============================================================================= -enum -{ - BOOBY_TRAP_SCAN_RANGE = 25 -}; -Bool Object::checkAndDetonateBoobyTrap(const Object *victim) -{ - if( !testStatus(OBJECT_STATUS_BOOBY_TRAPPED) ) - return FALSE; - - PartitionFilterAcceptByKindOf kindFilter(MAKE_KINDOF_MASK(KINDOF_BOOBY_TRAP), KINDOFMASK_NONE); - PartitionFilterSameMapStatus filterMapStatus(this); - PartitionFilter *filters[3]; - filters[0] = &kindFilter; - filters[1] = &filterMapStatus; - filters[2] = NULL; - - ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( getPosition(), BOOBY_TRAP_SCAN_RANGE + getGeometryInfo().getBoundingCircleRadius(), - FROM_CENTER_2D, filters, ITER_SORTED_NEAR_TO_FAR ); - MemoryPoolObjectHolder hold(iter);// This is the magic thing that frees the dynamically made iter in its destructor - - Object *ourBoobyTrap = NULL; - for( Object *other = iter->first(); other; other = iter->next() ) - { - if( other->getProducerID() == getID() )// Sticky bombs call the thing they are on their producer for just such an occasion - { - ourBoobyTrap = other; - break; - } - } - - if( ourBoobyTrap ) - { - static NameKeyType key_StickyBombUpdate = NAMEKEY( "StickyBombUpdate" ); - StickyBombUpdate *update = (StickyBombUpdate*)ourBoobyTrap->findUpdateModule( key_StickyBombUpdate ); - if( update ) - { - if( victim && ourBoobyTrap->getControllingPlayer()->getRelationship(victim->getTeam()) == ALLIES ) - return FALSE;// Friends don't touch friends boobies. - - update->detonate(); - return TRUE;// Booby Trapped status will be cleared by stickybomb, as they set it - } - } - - return FALSE; -} - -//============================================================================= -void Object::setStatus( ObjectStatusMaskType objectStatus, Bool set ) -{ - ObjectStatusMaskType oldStatus = m_status; - - if (set) - m_status.set( objectStatus ); - else - m_status.clear( objectStatus ); - - if (m_status != oldStatus) - { - if( set && objectStatus.test( OBJECT_STATUS_REPULSOR ) && m_repulsorHelper != NULL ) - { - // Damaged repulsable civilians scare (repulse) other civs, but only - // for a short amount of time... use the repulsor helper to turn off repulsion shortly. - m_repulsorHelper->sleepUntil(TheGameLogic->getFrame() + 2*LOGICFRAMES_PER_SECOND); - } - - if( objectStatus.test( OBJECT_STATUS_STEALTHED ) || objectStatus.test( OBJECT_STATUS_DETECTED ) || objectStatus.test( OBJECT_STATUS_DISGUISED ) ) - { - //Kris: Aug 20, 2003 - //When any of the three key status bits for stealth go on or off, then handle partition updates for vision. - if( getTemplate()->getShroudRevealToAllRange() > 0.0f ) - { - handlePartitionCellMaintenance(); - } - } - - - // when an object's construction status changes, it needs to have its partition data updated, - // in order to maintain the shroud correctly. - if( m_status.test( OBJECT_STATUS_UNDER_CONSTRUCTION ) != oldStatus.test( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) - { - - // CHECK FOR MINES, AND DETONATE THEM NOW - ObjectIterator *iter = - ThePartitionManager->iteratePotentialCollisions( getPosition(), getGeometryInfo(), getOrientation() ); - MemoryPoolObjectHolder hold( iter ); - Object *them; - for( them = iter->first(); them; them = iter->next() ) - { - if (them->isKindOf( KINDOF_MINE )) - { - //DETONATE ANY ENEMY MINES, OR DELETE FRIENDLY ONES - Relationship r = getRelationship(them); - if (r == ENEMIES) - { - them->kill(); // detonate mine - } - else - { - TheGameLogic->destroyObject(them); - } - } - }// next object - - if (m_partitionData) - m_partitionData->makeDirty(true); - } - - } - -} - -//============================================================================= -void Object::setScriptStatus( ObjectScriptStatusBit bit, Bool set ) -{ - UnsignedInt oldScriptStatus = m_scriptStatus; - - if( set ) - { - m_scriptStatus |= bit; - } - else - { - m_scriptStatus &= ~bit; - } - - if( m_scriptStatus != oldScriptStatus ) - { - if( (m_scriptStatus & OBJECT_STATUS_SCRIPT_DISABLED) != (oldScriptStatus & OBJECT_STATUS_SCRIPT_DISABLED) ) - { - if( m_partitionData ) - { - // if an object becomes disabled or unpowered, then you have to update its partition data because it will - // change how far it can see. - m_partitionData->makeDirty(true); - } - if( m_scriptStatus & OBJECT_STATUS_SCRIPT_DISABLED ) - { - //I am now disabled, so tell the main game engine! - setDisabled( DISABLED_SCRIPT_DISABLED ); - } - else - { - //I am no longer disabled, so tell the main game engine! - clearDisabled( DISABLED_SCRIPT_DISABLED ); - } - } - if( (m_scriptStatus & OBJECT_STATUS_SCRIPT_UNPOWERED) != (oldScriptStatus & OBJECT_STATUS_SCRIPT_UNPOWERED) ) - { - if( m_partitionData ) - { - // if an object becomes disabled or unpowered, then you have to update its partition data because it will - // change how far it can see. - m_partitionData->makeDirty(true); - } - if( m_scriptStatus & OBJECT_STATUS_SCRIPT_UNPOWERED ) - { - //I am now underpowered, so tell the main game engine! - setDisabled( DISABLED_SCRIPT_UNDERPOWERED ); - } - else - { - //I am no longer undperpowered, so tell the main game engine! - clearDisabled( DISABLED_SCRIPT_UNDERPOWERED ); - } - } - } -} - -//============================================================================= -Bool Object::canCrushOrSquish(Object *otherObj, CrushSquishTestType testType ) const -{ - DEBUG_ASSERTCRASH(this, ("null this in canCrushOrSquish")); - - if( !otherObj ) - { - //Can't crush anything. - return false; - } - - if( isDisabledByType( DISABLED_UNMANNED ) ) - { - //Unmanned vehicles cannot crush troops. This was happening when Jarmen Kell sniped - //the vehicle and booted the guys out while still moving, as the vehicle is now - //on a different team. - return false; - } - - UnsignedByte crusherLevel = getCrusherLevel(); - - // order matters: we want to know if I consider it to be an ally, not vice versa - if( getRelationship( otherObj ) == ALLIES ) - { - //Friends don't let friends crush friends. - return false; - } - - if( !crusherLevel ) - { - //Can't crush anything! - return false; - } - - //Test this case for generic infantry getting squished by vehicles! - if( testType == TEST_SQUISH_ONLY || testType == TEST_CRUSH_OR_SQUISH ) - { - - //**************************************************************************************** - //NOTE: This section of code is used by the pathfinder to determine if the object should - // move to the target. I don't think it's the right place to check for this because - // the semantics check to see if we can squish something -- not approach it. However - // I'm not moving it for fear of some major breakage! -- KM - //Bool squisher = crusherLevel > 0; - //if( !squisher ) - //{ - // Weapon *weapon = getCurrentWeapon(); - // if( weapon && weapon->isContactWeapon() ) - // { - // squisher = true; - // } - //} - //if( squisher ) - //NOTE2: *** IF YOU REENABLE THIS CODE -- Move the "if( !crusherLevel ) return false" below - // this squish section. - //**************************************************************************************** - { - // See if other is squishable - static NameKeyType key_squish = NAMEKEY( "SquishCollide" ); - if( otherObj->findModule( key_squish ) ) - { - return true; // squishable. - } - } - } - - - UnsignedByte crushableLevel = otherObj->getCrushableLevel(); - - if( testType == TEST_CRUSH_ONLY || testType == TEST_CRUSH_OR_SQUISH ) - { - if( crusherLevel > crushableLevel ) - { - return true; - } - } - - return false; -} - -//------------------------------------------------------------------------------------------------- -UnsignedByte Object::getCrusherLevel() const -{ - return getTemplate()->getCrusherLevel(); -} - -//------------------------------------------------------------------------------------------------- -UnsignedByte Object::getCrushableLevel() const -{ - return getTemplate()->getCrushableLevel(); -} - - -// ------------------------------------------------------------------------------------------------ -/** Topple an object, if possible */ -// ------------------------------------------------------------------------------------------------ -void Object::topple( const Coord3D *toppleDirection, Real toppleSpeed, UnsignedInt options ) -{ - static NameKeyType key_ToppleUpdate = NAMEKEY("ToppleUpdate"); - - ToppleUpdate* toppleUpdate = (ToppleUpdate*)findModule(key_ToppleUpdate); - if( toppleUpdate && toppleUpdate->isAbleToBeToppled() ) - { - - // apply the topple force - toppleUpdate->applyTopplingForce( toppleDirection, toppleSpeed, options ); - - } // end if - -} // end topple - -//============================================================================= -void Object::setArmorSetFlag(ArmorSetType ast) -{ - m_body->setArmorSetFlag(ast); -} - -//============================================================================= -void Object::clearArmorSetFlag(ArmorSetType ast) -{ - m_body->clearArmorSetFlag(ast); -} - -//============================================================================= -Bool Object::testArmorSetFlag(ArmorSetType ast) const -{ - return m_body->testArmorSetFlag(ast); -} - -//============================================================================= -void Object::reloadAllAmmo(Bool now) -{ - m_weaponSet.reloadAllAmmo(this, now); -} - -//============================================================================= -Bool Object::isOutOfAmmo() const -{ - return m_weaponSet.isOutOfAmmo(); -} - -//============================================================================= -Bool Object::hasAnyWeapon() const -{ - return m_weaponSet.hasAnyWeapon(); -} - -//============================================================================= -Bool Object::hasAnyDamageWeapon() const -{ - //First check to see if we have any weapons -- if not return false. - if( !m_weaponSet.hasAnyDamageWeapon() ) - { - return FALSE; - } - return TRUE; -} - -//============================================================================= -UnsignedInt Object::getMostPercentReadyToFireAnyWeapon() const -{ - return m_weaponSet.getMostPercentReadyToFireAnyWeapon(); -} - -//============================================================================= -Bool Object::getWeaponInWeaponSlotSyncedToSlot(WeaponSlotType thisSlot, WeaponSlotType otherSlot) const -{ - CommandSourceMask mask = getWeaponInWeaponSlotCommandSourceMask(thisSlot); - - //Bool value0a = mask & (1 << CMD_SYNC_TO_PRIMARY); - //Bool value0b = (otherSlot == PRIMARY_WEAPON); - //Bool value1a = mask & (1 << CMD_SYNC_TO_SECONDARY); - //Bool value1b = (otherSlot == SECONDARY_WEAPON); - //Bool value2a = mask & (1 << CMD_SYNC_TO_TERTIARY); - //Bool value2b = (otherSlot == TERTIARY_WEAPON); - - //DEBUG_LOG(("- getWeaponInWeaponSlotSyncedToSlot (thisSlot=%d, otherSlot=%d): mask = %d --> value0 = %d/%d, value1 = %d/%d, value2 = %d/%d.\n", - // thisSlot, otherSlot, static_cast(mask), value0a, value0b, value1a, value1b, value2a, value2b)); - - return ((Int)mask >= 0) && - ((mask & (1 << CMD_SYNC_TO_PRIMARY) && otherSlot == PRIMARY_WEAPON) || - (mask & (1 << CMD_SYNC_TO_SECONDARY) && otherSlot == SECONDARY_WEAPON) || - (mask & (1 << CMD_SYNC_TO_TERTIARY) && otherSlot == TERTIARY_WEAPON) || - (mask & (1 << CMD_SYNC_TO_FOUR) && otherSlot == WEAPON_FOUR) || - (mask & (1 << CMD_SYNC_TO_FIVE) && otherSlot == WEAPON_FIVE) || - (mask & (1 << CMD_SYNC_TO_SIX) && otherSlot == WEAPON_SIX) || - (mask & (1 << CMD_SYNC_TO_SEVEN) && otherSlot == WEAPON_SEVEN) || - (mask & (1 << CMD_SYNC_TO_EIGHT) && otherSlot == WEAPON_EIGHT)); - -} - -//============================================================================= -Bool Object::hasWeaponToDealDamageType(DamageType typeToDeal) const -{ - return m_weaponSet.hasWeaponToDealDamageType(typeToDeal); -} - -//============================================================================= -Real Object::getLargestWeaponRange() const -{ - Real retVal = -1; - for (Int i = PRIMARY_WEAPON; i < WEAPONSLOT_COUNT; ++i) { - Weapon* weapon = m_weaponSet.getWeaponInWeaponSlot((WeaponSlotType)i); - if (!weapon) { - continue; - } - - Real tmpVal = weapon->getAttackRange(this); - if (tmpVal > retVal) { - retVal = tmpVal; - } - } - return retVal; -} - -//============================================================================= -void Object::setFiringConditionForCurrentWeapon() const -{ - if (m_drawable) - { - WeaponSlotType wslot = m_weaponSet.getCurWeaponSlot(); - ModelConditionFlags c = m_weaponSet.getModelConditionForWeaponSlot(wslot, WSF_FIRING); - m_drawable->clearAndSetModelConditionFlags(s_allWeaponFireFlags[wslot], c); - } -} - -//============================================================================= -void Object::setModelConditionState( ModelConditionFlagType a ) -{ - if (m_drawable) - { - m_drawable->setModelConditionState(a); - } -} - -//============================================================================= -void Object::clearModelConditionState( ModelConditionFlagType a ) -{ - if (m_drawable) - { - m_drawable->clearModelConditionState(a); - } -} - -//============================================================================= -void Object::clearAndSetModelConditionState( ModelConditionFlagType clr, ModelConditionFlagType set ) -{ - if (m_drawable) - { - m_drawable->clearAndSetModelConditionState(clr, set); - } -} - -//============================================================================= -void Object::clearModelConditionFlags( const ModelConditionFlags& clr ) -{ - if (m_drawable) - { - m_drawable->clearModelConditionFlags(clr); - } -} - -//============================================================================= -void Object::setModelConditionFlags( const ModelConditionFlags& set ) -{ - if (m_drawable) - { - m_drawable->setModelConditionFlags(set); - } -} - -//============================================================================= -void Object::clearAndSetModelConditionFlags( const ModelConditionFlags& clr, const ModelConditionFlags& set ) -{ - if (m_drawable) - { - m_drawable->clearAndSetModelConditionFlags(clr, set); - } -} - -//============================================================================= -// Special model states are states that are turned on for a period of time, and -// turned off automatically -- used for cheer, and scripted special moment -// animations. Setting a special state will automatically clear any other -// special states that may be turned on so you can only have one at a time. -//============================================================================= -void Object::setSpecialModelConditionState( ModelConditionFlagType set, UnsignedInt frames ) -{ - clearSpecialModelConditionStates(); - - setModelConditionState( set ); - - if( frames == 0 ) - { - frames = 1; - } - - m_smcUntil = TheGameLogic->getFrame() + frames; - m_smcHelper->sleepUntil(m_smcUntil); -} - -//============================================================================= -void Object::clearSpecialModelConditionStates() -{ - clearModelConditionFlags( MAKE_MODELCONDITION_MASK( MODELCONDITION_SPECIAL_CHEERING ) ); - m_smcUntil = NEVER; -} - -// Lorenzen has some interest in this, ask before deleting -//============================================================================= -//const ModelConditionFlags& Object::getModelConditionFlags() const -//{ -// if (m_drawable) -// { -// return m_drawable->getModelConditionFlags(); -// } -// else -// { -// DEBUG_CRASH(("NULL Drawable at this point, you can't get modelconditionflags now.")); -// static ModelConditionFlags noFlags; -// return noFlags; -// } -//} - -//============================================================================= -Weapon* Object::getCurrentWeapon(WeaponSlotType* wslot) -{ - if (!m_weaponSet.hasAnyWeapon()) - return NULL; - - if (wslot) - *wslot = m_weaponSet.getCurWeaponSlot(); - return m_weaponSet.getCurWeapon(); -} - -//============================================================================= -const Weapon* Object::getCurrentWeapon(WeaponSlotType* wslot) const -{ - if (!m_weaponSet.hasAnyWeapon()) - return NULL; - - if (wslot) - *wslot = m_weaponSet.getCurWeaponSlot(); - return m_weaponSet.getCurWeapon(); -} - -//============================================================================= -Weapon* Object::findWaypointFollowingCapableWeapon() -{ - return m_weaponSet.findWaypointFollowingCapableWeapon(); -} - -//============================================================================= -Bool Object::getAmmoPipShowingInfo(Int& numTotal, Int& numFull) const -{ -/// @todo srj -- may need to cache this inside weaponset. - const Weapon* w = m_weaponSet.findAmmoPipShowingWeapon(); - if (w) - { - numTotal = w->getClipSize(); - numFull = w->getRemainingAmmo(); - return true; - } - else - { - return false; - } -} - -//============================================================================= -/* - NOTE: getAbleToAttackSpecificObject NO LONGER internally calls isAbleToAttack(), - since that isn't an incredibly fast call, and this is called repeatedly in some inner loops - where we already know that isAbleToAttack() == true. so you should always - call isAbleToAttack prior to calling this! (srj) -*/ -CanAttackResult Object::getAbleToAttackSpecificObject( AbleToAttackType t, const Object* target, CommandSourceType commandSource, WeaponSlotType specificSlot ) const -{ - // NO! BAD! WRONG! - // If we can't attack at all, then we cannot attack this - //if (!isAbleToAttack()) - // return FALSE; - - // Otherwise leave it up to our weapons. - return m_weaponSet.getAbleToAttackSpecificObject( t, this, target, commandSource, specificSlot ); -} - -//============================================================================= -//Used for base defenses and otherwise stationary units to see if you can attack a position potentially out of range. -CanAttackResult Object::getAbleToUseWeaponAgainstTarget( AbleToAttackType attackType, const Object *victim, const Coord3D *pos, CommandSourceType commandSource, WeaponSlotType specificSlot ) const -{ - return m_weaponSet.getAbleToUseWeaponAgainstTarget( attackType, this, victim, pos, commandSource, specificSlot ); -} - - -//============================================================================= -Bool Object::chooseBestWeaponForTarget(const Object* target, WeaponChoiceCriteria criteria, CommandSourceType cmdSource ) -{ - return m_weaponSet.chooseBestWeaponForTarget(this, target, criteria, cmdSource ); -} - -//DECLARE_PERF_TIMER(fireCurrentWeapon) -//============================================================================= -void Object::fireCurrentWeapon(Object *target) -{ - //USE_PERF_TIMER(fireCurrentWeapon) - - // victim may have already been destroyed - if (target == NULL) - return; - - Weapon* weapon = m_weaponSet.getCurWeapon(); - if (weapon && (weapon->getStatus() == READY_TO_FIRE)) - { - Bool reloaded = weapon->fireWeapon(this, target); - DEBUG_ASSERTCRASH(m_firingTracker, ("hey, we are firing but have no firing tracker. this is wrong.")); - if (m_firingTracker) - m_firingTracker->shotFired(weapon, target->getID()); - if (reloaded) - releaseWeaponLock(LOCKED_TEMPORARILY); // release any temporary locks. - - friend_setUndetectedDefector( FALSE );// My secret is out - } -} - -//============================================================================= -void Object::fireCurrentWeapon(const Coord3D* pos) -{ - //USE_PERF_TIMER(fireCurrentWeapon) - - if (pos == NULL) - return; - - Weapon* weapon = m_weaponSet.getCurWeapon(); - if (weapon && (weapon->getStatus() == READY_TO_FIRE)) - { - Bool reloaded = weapon->fireWeapon(this, pos); - DEBUG_ASSERTCRASH(m_firingTracker, ("hey, we are firing but have no firing tracker. this is wrong.")); - if (m_firingTracker) - m_firingTracker->shotFired(weapon, INVALID_ID); - if (reloaded) - releaseWeaponLock(LOCKED_TEMPORARILY); // release any temporary locks. - - friend_setUndetectedDefector( FALSE );// My secret is out - } -} - -//============================================================================== -void Object::notifyFiringTrackerShotFired( const Weapon* weaponFired, ObjectID victimID ) -{ - if ( m_firingTracker ) - m_firingTracker->shotFired( weaponFired, victimID ); -} - - -//============================================================================= -void Object::preFireCurrentWeapon( const Object *victim ) -{ - Weapon* weapon = m_weaponSet.getCurWeapon(); - - //If we are going to be capable of firing our weapon NEXT frame, set the pre-attack - //up now. This gets called by AIAttackFireWeaponState::onEnter().. but the update happens - //next frame. - if (weapon && TheGameLogic->getFrame() + 1 >= weapon->getPossibleNextShotFrame() ) - { - weapon->preFireWeapon( this, victim ); - friend_setUndetectedDefector( FALSE );// My secret is out - } -} - -//============================================================================= -void Object::preFireCurrentWeapon(const Coord3D* pos) -{ - Weapon* weapon = m_weaponSet.getCurWeapon(); - - //If we are going to be capable of firing our weapon NEXT frame, set the pre-attack - //up now. This gets called by AIAttackFireWeaponState::onEnter().. but the update happens - //next frame. - if (weapon && TheGameLogic->getFrame() + 1 >= weapon->getPossibleNextShotFrame()) - { - weapon->preFireWeapon(this, pos); - friend_setUndetectedDefector(FALSE);// My secret is out - } -} - -// ============================================================================ -/** Using the firing tracker, return the frame a shot was last fired on */ -// ============================================================================ -UnsignedInt Object::getLastShotFiredFrame() const -{ - UnsignedInt recent = 0; - for (int i = 0; i < WEAPONSLOT_COUNT; ++i) - { - const Weapon* w = m_weaponSet.getWeaponInWeaponSlot((WeaponSlotType)i); - if (w) - { - UnsignedInt when = w->getLastShotFrame(); - if (when > recent) - recent = when; - } - } - return recent; -} - -// ============================================================================ -/** Get the victim ID we last shot at */ -// ============================================================================ -ObjectID Object::getLastVictimID() const -{ - return m_firingTracker ? m_firingTracker->getLastShotVictim() : INVALID_ID; -} - -//============================================================================= -// Object::getRelationship -//============================================================================= -Relationship Object::getRelationship(const Object *that) const -{ - const Team *myTeam = getTeam(); - - if (myTeam && that) - { - if (getIsUndetectedDefector()) - { - return NEUTRAL; // so my AI does not give away my position by auto acquire - } - else if (that->getIsUndetectedDefector()) - { - return ALLIES; // so I treat undetecteddefectors like they were my very own - } - else - { - return myTeam->getRelationship( that->getTeam() ); - } - } - - return NEUTRAL; - -} - -//============================================================================= -// Object::getControllingPlayer -//============================================================================= -Player * Object::getControllingPlayer() const -{ - const Team* myTeam = this->getTeam(); - if (myTeam) - return myTeam->getControllingPlayer(); - - return NULL; -} - -//============================================================================= -void Object::setProducer(const Object* obj) -{ - m_producerID = obj ? obj->getID() : INVALID_ID; -// seems like a good idea, but is not. (srj) -// if (obj) -// m_indicatorColor = obj->m_indicatorColor; -} - -//============================================================================= -void Object::setBuilder( const Object *obj ) -{ - - m_builderID = obj ? obj->getID() : INVALID_ID; - -} - -//============================================================================= -void Object::setCustomIndicatorColor(Color c) -{ - if (m_indicatorColor != c) - { - m_indicatorColor = c; - if (m_drawable) - m_drawable->changedTeam(); - } -} - -//============================================================================= -void Object::removeCustomIndicatorColor() -{ - setCustomIndicatorColor(0); -} - -//============================================================================= -// Object::getIndicatorColor -//============================================================================= -Color Object::getIndicatorColor() const -{ - if (m_indicatorColor == 0) - { - const Team *myTeam = getTeam(); - if (myTeam) - { - const Player* p = myTeam->getControllingPlayer(); - if (p) - { - return p->getPlayerColor(); - } - } - return GameMakeColor(0, 0, 0, 255); - } - else - { - return m_indicatorColor; - } -} - -//============================================================================= -// Object::getNightIndicatorColor - used to make blue/purple easier to see on night models. -//============================================================================= -Color Object::getNightIndicatorColor() const -{ - if (m_indicatorColor == 0) - { - const Team *myTeam = getTeam(); - if (myTeam) - { - const Player* p = myTeam->getControllingPlayer(); - if (p) - { - return p->getPlayerNightColor(); - } - } - return GameMakeColor(0, 0, 0, 255); - } - else - { - return m_indicatorColor; - } -} - -//============================================================================= -// Object::isLocallyControlled -//============================================================================= -Bool Object::isLocallyControlled() const -{ - return getControllingPlayer() == ThePlayerList->getLocalPlayer(); -} - -//============================================================================= -// Object::isLocallyControlled -//============================================================================= -Bool Object::isNeutralControlled() const -{ - return getControllingPlayer() == ThePlayerList->getNeutralPlayer(); -} - -//------------------------------------------------------------------------------------------------- -inline Bool isPosDifferent(const Coord3D* a, const Coord3D* b) -{ - // this is necessary because PhysicsBehavior may generate tiny changes even when - // "standing still", due to roundoff errors. It's important that we only invalidate - // the PartitionManager stuff when the pos/orientation really changes (for efficiency purposes) - // so we must put in some cleverness... - const Real THRESH = 0.01f; - - if (fabs(a->x - b->x) > THRESH) - return true; - - if (fabs(a->y - b->y) > THRESH) - return true; - - if (fabs(a->z - b->z) > THRESH) - return true; - - return false; -} - -//------------------------------------------------------------------------------------------------- -inline Bool isAngleDifferent(Real a, Real b) -{ - // this is necessary because PhysicsBehavior may generate tiny changes even when - // "standing still", due to roundoff errors. It's important that we only invalidate - // the PartitionManager stuff when the pos/orientation really changes (for efficiency purposes) - // so we must put in some cleverness... - - const Real THRESH = 0.01f; // in radians, this is approx 1/2 degree. - - if (fabs(a - b) > THRESH) - return true; - - return false; -} - -//------------------------------------------------------------------------------------------------- -void Object::reactToTurretChange( WhichTurretType turret, Real oldRotation, Real oldPitch ) -{ - Real currentRotation = 0.0f; - Real currentPitch = 0.0f; - if( getAI() ) - { - getAI()->getTurretRotAndPitch( turret, ¤tRotation, ¤tPitch ); - } - Bool rotationChange = (currentRotation != oldRotation); -// Bool pitchChange = (currentPitch != oldPitch); - - if( rotationChange ) - { - if (getContain()) - getContain()->containReactToTransformChange(); - } -} - -//------------------------------------------------------------------------------------------------- -//DECLARE_PERF_TIMER(Object_reactToTransformChange) -void Object::reactToTransformChange(const Matrix3D* oldMtx, const Coord3D* oldPos, Real oldAngle) -{ - //USE_PERF_TIMER(Object_reactToTransformChange) - if(_isnan(getPosition()->x) || _isnan(getPosition()->y) || _isnan(getPosition()->z)) { - DEBUG_CRASH(("Object pos is nan.")); - TheGameLogic->destroyObject(this); - } - if (m_drawable) - { - m_drawable->setTransformMatrix( this->getTransformMatrix() ); - } - - Bool posDiff = isPosDifferent(oldPos, getPosition()); - Bool angDiff = isAngleDifferent(oldAngle, getOrientation()); - - if (posDiff || angDiff) - { - if (m_partitionData) - m_partitionData->makeDirty(true); - - if (getContain()) - getContain()->containReactToTransformChange(); - } - - if (posDiff) - { - setTriggerAreaFlagsForChangeInPosition(); // Update for entered/exited - - Region3D mapExtent; - TheTerrainLogic->getExtent(&mapExtent); - if (mapExtent.isInRegionNoZ(getPosition())) - m_privateStatus &= ~OFF_MAP; - else - m_privateStatus |= OFF_MAP; - } -} - -//------------------------------------------------------------------------------------------------- -ObjectShroudStatus Object::getShroudedStatus(Int playerIndex) const -{ - if (getTemplate()->isKindOf( KINDOF_ALWAYS_VISIBLE )) - return OBJECTSHROUD_CLEAR; - - if (m_partitionData) - return m_partitionData->getShroudedStatus(playerIndex); - - // This can happen for objects removed from the partition system (e.g., - // for soldiers that are garrisoned inside a building). - return OBJECTSHROUD_CLEAR; -} - -//------------------------------------------------------------------------------------------------- -/** Something is attempting to damage this object */ -//------------------------------------------------------------------------------------------------- -void Object::attemptDamage( DamageInfo *damageInfo ) -{ - BodyModuleInterface* body = getBodyModule(); - if (body) - body->attemptDamage( damageInfo ); - - // Process any shockwave forces that might affect this object due to the incurred damage - if (damageInfo->in.m_shockWaveAmount > 0.0f && damageInfo->in.m_shockWaveRadius > 0.0f) - { - //KindOfMaskType immuneToShockwaveKindofs; //NEW RESTRICTIONS ADDED - //immuneToShockwaveKindofs.set(KINDOF_PROJECTILE);// projectiles go idle in midair when they get sw'd //NEW RESTRICTIONS ADDED - //immuneToShockwaveKindofs.set(KINDOF_PRODUCED_AT_HELIPAD);//helicopters go all wonky when they get shockwaved //NEW RESTRICTIONS ADDED - - PhysicsBehavior *behavior = getPhysics(); - if ( behavior && (isAirborneTarget() == FALSE) && (! isKindOf(KINDOF_PROJECTILE) ) ) -// if (behavior && isAnyKindOf( immuneToShockwaveKindofs ) == FALSE )//NEW RESTRICTIONS ADDED - { - // Calculate the shockwave taperoff amount due to distance from ground zero - Real shockWaveScalar = damageInfo->in.m_shockWaveVector.length(); - Real distanceFromCenter = min(1.0f, shockWaveScalar / damageInfo->in.m_shockWaveRadius); - Real distanceTaper = (distanceFromCenter) * (1.0f - damageInfo->in.m_shockWaveTaperOff); - Real shockTaperMult = 1.0f - distanceTaper; - - // Set up the shockwave force to use apply on object - Coord3D shockWaveForce; - shockWaveForce.set( &damageInfo->in.m_shockWaveVector ); - shockWaveForce.normalize(); - shockWaveForce.scale( damageInfo->in.m_shockWaveAmount * shockTaperMult ); - shockWaveForce.z = shockWaveForce.length(); // Apply up force equal to the lateral force for dramatic effect - - // Apply the shock to the object - behavior->applyShock(&shockWaveForce); - - // Add random rotation to the object for drama - - behavior->applyRandomRotation(); - - // Set stunned state due to the shock for the object - behavior->setStunned(true); - - setModelConditionState(MODELCONDITION_STUNNED_FLAILING); - } - } - - - /// @todo track damage dealt/attempted - - // - // if actual damage occurred, and this is an object owned by the local player we - // might do a radar event for under attack. Note that we do not even try - // to do radar events for DAMAGE_PENALTY as that damage type is a type of damage - // that occurs with explicit player knowledge - // - if( damageInfo->out.m_actualDamageDealt > 0.0f && - damageInfo->in.m_damageType != DAMAGE_PENALTY && - damageInfo->in.m_damageType != DAMAGE_HEALING && - getControllingPlayer() && - !BitIsSet(damageInfo->in.m_sourcePlayerMask, getControllingPlayer()->getPlayerMask()) && - m_radarData != NULL && - getControllingPlayer() == ThePlayerList->getLocalPlayer() ) - TheRadar->tryUnderAttackEvent( this ); - -} - -//------------------------------------------------------------------------------------------------- -void Object::attemptHealing(Real amount, const Object* source) -{ - BodyModuleInterface* body = getBodyModule(); - if (body) - { - DamageInfo damageInfo; - damageInfo.in.m_damageType = DAMAGE_HEALING; - damageInfo.in.m_deathType = DEATH_NONE; - damageInfo.in.m_sourceID = source ? source->getID() : INVALID_ID; - damageInfo.in.m_amount = amount; - body->attemptHealing( &damageInfo ); - } -} - -ObjectID Object::getSoleHealingBenefactor( void ) const -{ - UnsignedInt now = TheGameLogic->getFrame(); - if( now > m_soleHealingBenefactorExpirationFrame ) - return INVALID_ID; - - return m_soleHealingBenefactorID; - -} - -Bool Object::attemptHealingFromSoleBenefactor ( Real amount, const Object* source, UnsignedInt duration ) -{///< for the non-stacking healers like ambulance and propaganda - - if( ! source ) // sanity - return FALSE; - - UnsignedInt now = TheGameLogic->getFrame(); - ObjectID id = source->getID(); - -// Either it is ok to accept healing from any who offer or this is my guy, calling again - if( now > m_soleHealingBenefactorExpirationFrame || m_soleHealingBenefactorID == id ) - { - m_soleHealingBenefactorID = id; - m_soleHealingBenefactorExpirationFrame = now + duration; - - BodyModuleInterface* body = getBodyModule(); - if (body) - { - DamageInfo damageInfo; - damageInfo.in.m_damageType = DAMAGE_HEALING; - damageInfo.in.m_deathType = DEATH_NONE; - damageInfo.in.m_sourceID = source ? source->getID() : INVALID_ID; - damageInfo.in.m_amount = amount; - body->attemptHealing( &damageInfo ); - } - - return TRUE; - } - - return FALSE; - -} - - -//------------------------------------------------------------------------------------------------- -Real Object::estimateDamage( DamageInfoInput& damageInfo ) const -{ - BodyModuleInterface* body = getBodyModule(); - if (body) - return body->estimateDamage( damageInfo ); - - return 0.0f; -} - -//------------------------------------------------------------------------------------------------- -/** Do so much damage to an object that it will certainly die */ -//------------------------------------------------------------------------------------------------- -void Object::kill( DamageType damageType, DeathType deathType ) -{ - DamageInfo damageInfo; - - // Do unmodifiable damage equal to their max health to kill. - damageInfo.in.m_damageType = damageType; - damageInfo.in.m_deathType = deathType; - damageInfo.in.m_sourceID = INVALID_ID; - damageInfo.in.m_amount = getBodyModule()->getMaxHealth(); - damageInfo.in.m_kill = TRUE; // Triggers object to die no matter what. - attemptDamage( &damageInfo ); - - DEBUG_ASSERTCRASH(!damageInfo.out.m_noEffect, ("Attempting to kill an unKillable object (InactiveBody?)\n")); - -} // end kill - -//------------------------------------------------------------------------------------------------- -/** Restore max health to this Object */ -//------------------------------------------------------------------------------------------------- -void Object::healCompletely() -{ - attemptHealing(HUGE_DAMAGE_AMOUNT, NULL); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void Object::setEffectivelyDead(Bool dead) -{ - if (dead) - BitSet(m_privateStatus, EFFECTIVELY_DEAD); - else - BitClear(m_privateStatus, EFFECTIVELY_DEAD); - - if (dead) - { - if( m_radarData ) - TheRadar->removeObject( this ); - } -} - -//------------------------------------------------------------------------------------------------- -void Object::setCaptured(Bool isCaptured) -{ - if (isCaptured) - BitSet(m_privateStatus, CAPTURED); - else - { - DEBUG_LOG(("Clearing Captured Status. This should never happen. jkmcd")); - BitClear(m_privateStatus, CAPTURED); - } - - // No need to see if we should skip updates, this flag has no effect on skipping updates. -} - - - -//------------------------------------------------------------------------------------------------- -Bool Object::isStructure(void) const -{ - return isKindOf(KINDOF_STRUCTURE); -} - -//------------------------------------------------------------------------------------------------- -Bool Object::isFactionStructure(void) const -{ - return isAnyKindOf( KINDOFMASK_FS ); -} - -//------------------------------------------------------------------------------------------------- -Bool Object::isNonFactionStructure(void) const -{ - return isStructure() && !isFactionStructure(); -} - -void localIsHero( Object *obj, void* userData ) -{ - Bool *hero = (Bool*)userData; - - if( obj && obj->isKindOf( KINDOF_HERO ) ) - { - *hero = TRUE; - } -} - -//------------------------------------------------------------------------------------------------- -Bool Object::isHero(void) const -{ - ContainModuleInterface *contain = getContain(); - if( contain ) - { - Bool heroInside = FALSE; - contain->iterateContained( localIsHero, (void*)(&heroInside), FALSE ); - if( heroInside ) - { - return TRUE; - } - } - return isKindOf( KINDOF_HERO ); -} - -//------------------------------------------------------------------------------------------------- -void Object::setReceivingDifficultyBonus(Bool receive) -{ - if (receive == m_isReceivingDifficultyBonus) { - return; - } - - m_isReceivingDifficultyBonus = receive; - getControllingPlayer()->friend_applyDifficultyBonusesForObject(this, m_isReceivingDifficultyBonus); -} - -//------------------------------------------------------------------------------------------------- -//- DISABLEDNESS STUFF ---------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void Object::setDisabled( DisabledType type ) -{ - setDisabledUntil(type, FOREVER); -} - -//------------------------------------------------------------------------------------------------- -void Object::setDisabledUntil( DisabledType type, UnsignedInt frame ) -{ - Bool edgeCase = !isDisabled(); - - if( type < 0 || type >= DISABLED_COUNT ) - { - DEBUG_CRASH( ("Invalid disabled type value %d specified -- doesn't not exist!", type ) ); - return; - } - - //Handle audio events! - AudioEventRTS sound; - if( type == DISABLED_UNMANNED && !isKindOf( KINDOF_DRONE ) ) - { - //We've been sniped! Play a splatter sound for the pilot losing his face. - sound = TheAudio->getMiscAudio()->m_splatterVehiclePilotsBrain; - sound.setPosition( getPosition() ); - TheAudio->addAudioEvent( &sound ); - } - else if( type == DISABLED_UNDERPOWERED || type == DISABLED_EMP || type == DISABLED_SUBDUED || type == DISABLED_HACKED ) - { - //We've lost power -- make sure we aren't already out of power as the sounds shouldn't happen - //if you were already disabled. - if( !isDisabledByType( DISABLED_UNDERPOWERED ) && - !isDisabledByType( DISABLED_EMP ) && - !isDisabledByType( DISABLED_SUBDUED ) && - !isDisabledByType( DISABLED_HACKED ) ) - { - if( isKindOf( KINDOF_STRUCTURE ) ) - { - sound = TheAudio->getMiscAudio()->m_buildingDisabled; - sound.setPosition( getPosition() ); - TheAudio->addAudioEvent( &sound ); - } - else if( isKindOf( KINDOF_VEHICLE ) ) - { - sound = TheAudio->getMiscAudio()->m_vehicleDisabled; - sound.setPosition( getPosition() ); - TheAudio->addAudioEvent( &sound ); - } - } - } - - if( m_disabledTillFrame[ type ] != frame ) - { - // an edge-test for disabledness, for type. This INCREMENTS m_pauseCount - // srj sez: HELD nevers disables special powers. - if ( type != DISABLED_HELD && !isDisabledByType( type ) ) - pauseAllSpecialPowers( TRUE ); - - m_disabledTillFrame[ type ] = frame; - m_disabledMask.set( type, frame > TheGameLogic->getFrame() ); - - if( m_drawable ) - { - if( isDisabled() ) - { - // Held does not tint anybody. If we are multiply disabled, the other setting will hit the tint, - // and in clear, only-held and not-disabled are both causes to untint. - // Doh. Also shouldn't be tinting when disabled by scripting. - // Doh^2. Also shouldn't be CLEARING tinting if we're disabling by held or script disabledness - // Doh^3. Unmanned is no tint too - if( type != DISABLED_HELD && type != DISABLED_SCRIPT_DISABLED && type != DISABLED_UNMANNED && type != DISABLED_TELEPORT && type != DISABLED_CHRONO) - { - m_drawable->setTintStatus( TINT_STATUS_DISABLED ); - } - } - } - - ContainModuleInterface *contain = getContain(); - if ( contain ) - { - Object *rider = (Object*)contain->friend_getRider(); - if ( rider ) - { - rider->setDisabledUntil(type, frame); - } - } - - if ( isKindOf( KINDOF_SPAWNS_ARE_THE_WEAPONS ) ) - { - SpawnBehaviorInterface *sbi = this->getSpawnBehaviorInterface(); - if ( sbi ) - { - //Kris: Patch 1.01 - November 12, 2003 - //Actually, we want to disable the slaves, not order them to go idle! This fix was made to - //stinger sites getting hit by an EMP to prevent the soldiers from attacking. - //sbi->orderSlavesToGoIdle( CMD_FROM_AI ); // the canattack() will take care of any future attempts to fire - sbi->orderSlavesDisabledUntil( type, frame ); - } - - } - - } - - if( type == DISABLED_UNMANNED && !isKindOf( KINDOF_DRONE ) ) - { - //strange but true: If I am a carbomb, - //my driver actually has a dead-man's - //trigger for my dynamite... - //If he gets sniped, I blow up! Wheeee! - - WeaponSetFlags flags; - flags.set( WEAPONSET_CARBOMB ); - const WeaponTemplateSet* set = getTemplate()->findWeaponTemplateSet( flags ); - if( set && set->testWeaponSetFlag( WEAPONSET_CARBOMB ) ) - { - Object* sniper = TheGameLogic->findObjectByID( getBodyModule()->getLastDamageInfo()->in.m_sourceID ); - if ( sniper ) - sniper->scoreTheKill( this ); - - kill(); - } - else - { - //This vehicle's pilot has been sniped, so we want to clear the veterancy rating (if any) - ExperienceTracker *xpTracker = getExperienceTracker(); - if( xpTracker ) - { - xpTracker->setExperienceAndLevel( 0, FALSE ); - } - //Not only that, but it also loses any healing bonuses it may have earned in its prior life - { - static const NameKeyType key_AutoHealBehavior = NAMEKEY("AutoHealBehavior"); - AutoHealBehavior* autoHeal = (AutoHealBehavior*)(findUpdateModule( key_AutoHealBehavior )); - if (autoHeal) - autoHeal->undoUpgrade(); - - - } - } - - } - - // This will only be called if we were NOT disabled before coming into this function. - if (edgeCase) { - onDisabledEdge(true); - } -} - -//------------------------------------------------------------------------------------------------- -UnsignedInt Object::getDisabledUntil( DisabledType type ) const -{ - if( type == DISABLED_ANY ) - { - UnsignedInt highestFrame = 0; - //Iterate through each disabled type and return the one with the highest frame. - for( Int i = 0; i < DISABLED_COUNT; i++ ) - { - if( m_disabledMask.test( i ) && m_disabledTillFrame[ i ] > highestFrame ) - { - highestFrame = m_disabledTillFrame[ i ]; - } - } - return highestFrame; - } - else if( m_disabledMask.test( type ) ) - { - //Specific query. - return m_disabledTillFrame[ type ]; - } - //Not disabled. - return 0; -} - -//------------------------------------------------------------------------------------------------- -Bool Object::clearDisabled( DisabledType type ) -{ - if( type < 0 || type >= DISABLED_COUNT ) - { - DEBUG_CRASH( ("Invalid disabled type value %d specified -- doesn't not exist!", type ) ); - return FALSE; - } - - if (!isDisabledByType(type)) { - return FALSE; - } - - if( type == DISABLED_UNDERPOWERED || type == DISABLED_EMP || type == DISABLED_SUBDUED || type == DISABLED_HACKED ) - { - //We've regained power-- make sure we aren't still disabled by another type. - AudioEventRTS sound; - if( (!isDisabledByType( DISABLED_UNDERPOWERED ) || type == DISABLED_UNDERPOWERED ) && - (!isDisabledByType( DISABLED_EMP ) || type == DISABLED_EMP ) && - (!isDisabledByType( DISABLED_SUBDUED ) || type == DISABLED_SUBDUED ) && - (!isDisabledByType( DISABLED_HACKED ) || type == DISABLED_HACKED ) ) - { - if( isKindOf( KINDOF_STRUCTURE ) ) - { - sound = TheAudio->getMiscAudio()->m_buildingReenabled; - sound.setPosition( getPosition() ); - TheAudio->addAudioEvent( &sound ); - } - else if( isKindOf( KINDOF_VEHICLE ) ) - { - sound = TheAudio->getMiscAudio()->m_vehicleReenabled; - sound.setPosition( getPosition() ); - TheAudio->addAudioEvent( &sound ); - } - } - } - - - // an edge-test for disabledness, for type. This DECREMENTS m_pauseCount - // srj sez: HELD nevers disables special powers. - if ( type != DISABLED_HELD && isDisabledByType( type ) ) - pauseAllSpecialPowers( FALSE ); - - ContainModuleInterface *contain = getContain(); - if ( contain ) - { - // We explicitly pass stuff in up in the set, so we need to turn it off if it is a forever type - Object *rider = (Object*)contain->friend_getRider(); - if( rider && (m_disabledTillFrame[ type ] == FOREVER) ) - { - rider->clearDisabled(type); - } - } - - if ( isKindOf( KINDOF_SPAWNS_ARE_THE_WEAPONS ) ) - { - SpawnBehaviorInterface *sbi = this->getSpawnBehaviorInterface(); - if ( sbi ) - { - //Kris: Patch 1.02 - December 17, 2003 - //Make sure slaves can recover from being disabled by subdual (stinger site soldier case) - sbi->orderSlavesToClearDisabled( type ); - } - - } - - m_disabledTillFrame[ type ] = NEVER; - m_disabledMask.set( type, 0 ); - - DisabledMaskType exceptions; - exceptions.set(DISABLED_HELD); - exceptions.set(DISABLED_SCRIPT_DISABLED); - exceptions.set(DISABLED_UNMANNED); - exceptions.set(DISABLED_TELEPORT); - exceptions.set(DISABLED_CHRONO); - - DisabledMaskType myFlagsMinusExceptions = getDisabledFlags(); - myFlagsMinusExceptions.clearAndSet(exceptions, DISABLEDMASK_NONE); - - // to clarify, if I am NOT disabled by anything other than DISABLED_HELD, or DISABLED_SCRIPT_DISABLED - - // to clarify, count inverse intersection gives you the number of exceptions you don't have, - // and has nothing to do with checking other disabled types -// if( !isDisabled() || getDisabledFlags().countInverseIntersection( exceptions ) == 0 ) - if( myFlagsMinusExceptions.count() == 0 ) - { - // I have no disabled flag that is not one of the exceptions above. - if (m_drawable) - m_drawable->clearTintStatus( TINT_STATUS_DISABLED ); - } - - checkDisabledStatus();// in case we just edged - - // if we're no longer disabled by anything, then call the edge function. - if (!isDisabled()) { - onDisabledEdge(false); - } - return TRUE; -} - - -//------------------------------------------------------------------------------------------------- -//Checks any timers and clears disabled statii that have expired. -//------------------------------------------------------------------------------------------------- -void Object::checkDisabledStatus() -{ - UnsignedInt now = TheGameLogic->getFrame(); - for( int i = 0; i < DISABLED_COUNT; i++ ) - { - DisabledType type = (DisabledType)i; - if( isDisabledByType( type ) ) - { - if ( now >= m_disabledTillFrame[ i ] ) - { - clearDisabled( type ); // This will also DECREMENT m_pauseCount in all specialpowers - m_disabledMask.set( type, 0 ); - } - } - } -} - -//------------------------------------------------------------------------------------------------- -void Object::pauseAllSpecialPowers( const Bool disabling ) const -{ - for (BehaviorModule** m = m_behaviors; *m; ++m) - { - SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); - if (!sp) - continue; - - sp->pauseCountdown( disabling );// So it will pause if we are disabling. - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -/** Clear the previous entered/exited flags. */ -//------------------------------------------------------------------------------------------------- -void Object::updateTriggerAreaFlags() -{ - Int j = 0; - // Update the flags, and remove any trigger areas that this object isn't inside. - for (Int i=0; igetCollide(); - if (!collide) - continue; - - // check each time thru the loop, in case a collide module sets it - if( getStatusBits().test( OBJECT_STATUS_NO_COLLISIONS ) ) - { -#ifdef DEBUG_CRC - //DEBUG_LOG(("Object::onCollide() - OBJECT_STATUS_NO_COLLISIONS set\n")); -#endif - break; - } -#ifdef DEBUG_CRC - //DEBUG_LOG(("Object::onCollide() - calling collide module\n")); -#endif - collide->onCollide(other, loc, normal); - } -} - -//------------------------------------------------------------------------------------------------- -Bool Object::isSalvageCrate() const -{ - for( BehaviorModule** m = m_behaviors; *m; ++m ) - { - CollideModuleInterface* collide = (*m)->getCollide(); - if( collide && collide->isSalvageCrateCollide() ) - { - return true; - } - } - return false; -} - -//------------------------------------------------------------------------------------------------- -/** - Our owning player is telling us to recheck our UpgradeModules, as an upgrade has completed - */ -void Object::updateUpgradeModules() -{ - if( testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) ) - return; // No upgrade can run if we are under construction. The three places that clear UnderConstruction will re-update us. - - if( testStatus( OBJECT_STATUS_DESTROYED ) ) - return; // Patch 1.03 -- Fixes crash when you upgrade a fake GLA command center to a real one if (toxic or demo). - - if( getControllingPlayer() == NULL ) - return; // This can only happen in game teardown. No upgrades for you without a player. Weird crashes are bad. - - UpgradeMaskType playerMask = getControllingPlayer()->getCompletedUpgradeMask(); - UpgradeMaskType objectMask = getObjectCompletedUpgradeMask(); - UpgradeMaskType maskToCheck = playerMask; - maskToCheck.set( objectMask ); - // We need to add in all of the already owned upgrades to handle "AND" requiring upgrades. - // We combine all the masks in case someone has a Object AND Player combination - - for (BehaviorModule** module = m_behaviors; *module; ++module) - { - UpgradeModuleInterface* upgrade = (*module)->getUpgrade(); - if (!upgrade) - continue; - - if( !upgrade->isAlreadyUpgraded() ) - { - upgrade->attemptUpgrade( maskToCheck ); - } - } -} - -//------------------------------------------------------------------------------------------------- -//This function sucks. -//It was added for objects that can disguise as other objects and contain upgraded subobject overrides. -//A concrete example is the bomb truck. Different payloads are displayed based on which upgrades have been -//made. When the bomb truck disguises as something else, these subobjects are lost because the vector is -//stored in W3DDrawModule. When we revert back to the original bomb truck, we call this function to -//recalculate those upgraded subobjects. -//------------------------------------------------------------------------------------------------- -void Object::forceRefreshSubObjectUpgradeStatus() -{ - for (BehaviorModule** module = m_behaviors; *module; ++module) - { - UpgradeModuleInterface* upgrade = (*module)->getUpgrade(); - if (!upgrade) - continue; - - if( upgrade->isSubObjectsUpgrade() ) - { - upgrade->forceRefreshUpgrade(); - } - } -} - -//------------------------------------------------------------------------------------------------- -/** Returns whether an object entered or exited an area. */ -//------------------------------------------------------------------------------------------------- -Bool Object::didEnterOrExit() const -{ - if (isKindOf(KINDOF_INERT)) { - return FALSE; - } - // note that this needs to return true if we - // entered or exited on the current frame OR - // the previous frame... since the current execution - // order is ScriptEngine, then ObjectUpdates, - // enter/exits detected in ObjectUpdate on frame N - // won't be noticed by the ScriptEngine till frame N+1. - UnsignedInt now = TheGameLogic->getFrame(); - return m_enteredOrExitedFrame == now || m_enteredOrExitedFrame == now - 1; -} - -//------------------------------------------------------------------------------------------------- -/** Returns whether an object entered an area. */ -//------------------------------------------------------------------------------------------------- -Bool Object::didEnter(const PolygonTrigger *pTrigger) const -{ - if (!didEnterOrExit()) - return false; - - DEBUG_ASSERTCRASH(!isKindOf(KINDOF_INERT), ("Asking whether an inert object entered or exited. This is invalid.\n")); - - for (Int i=0; igetUpdateExitInterface()) != NULL ) - break; - } - - // If you don't have a fancy one, you may have one from your contain module, - // since if you can contain something, they will need to get out. - if( exitInterface == NULL ) - { - ContainModuleInterface *cmod = getContain(); - if( cmod ) - { - exitInterface = cmod->getContainExitInterface(); - } - } - - return exitInterface; - -} // end getObjectExitInterface - -//------------------------------------------------------------------------------------------------- -/** Checks the object against trigger areas when the position changes. */ -//------------------------------------------------------------------------------------------------- -void Object::setTriggerAreaFlagsForChangeInPosition() -{ - // projectiles cannot trigger areas. (jkmcd) - // neither can inert objects, like the radar ping, etc. (jkmcd) - if (isKindOf(KINDOF_PROJECTILE) || isKindOf(KINDOF_INERT)) - return; - - ICoord3D iPos; - Coord3D pos = *getPosition(); - iPos.x = REAL_TO_INT(pos.x); - iPos.y = REAL_TO_INT(pos.y); - iPos.z = 0; // Trigger areas compare on xy only. - if (m_iPos.x == iPos.x && m_iPos.y == iPos.y) - { - return; // didn't move enough to change integer position. - } - - if (!isKindOf(KINDOF_IMMOBILE)) { - if (isKindOf(KINDOF_INFANTRY) || isKindOf(KINDOF_VEHICLE) ) { - TheGameClient->notifyTerrainObjectMoved(this); - } - } - - if (getAIUpdateInterface()) - { - TheAI->pathfinder()->updatePos(this, getPosition()); - } - - UnsignedInt now = TheGameLogic->getFrame(); - if (m_enteredOrExitedFrame != 0 && m_enteredOrExitedFrame != now) - updateTriggerAreaFlags(); - - // Check for exited. - Int i; - for (i=0; ipointInTrigger(m_iPos)) - { - m_triggerInfo[i].isInside = false; - m_triggerInfo[i].exited = true; - m_enteredOrExitedFrame = now; - if (m_team) - m_team->setEnteredExited(); - TheGameLogic->updateObjectsChangedTriggerAreas(); -#ifdef RTS_DEBUG - //TheScriptEngine->AppendDebugMessage("Object exited.", false); -#endif - } - } - - m_iPos = iPos; - - for (const PolygonTrigger *pTrig = PolygonTrigger::getFirstPolygonTrigger(); pTrig; pTrig = pTrig->getNext()) - { - Bool skip = false; - for (i = 0; i < m_numTriggerAreasActive; i++) - { - if (m_triggerInfo[i].pTrigger == pTrig) - { - // Already handled this one in the check for exited above. - skip = true; - break; - } - } - if (skip) - continue; - if (pTrig->pointInTrigger(m_iPos)) - { - if (m_numTriggerAreasActive < MAX_TRIGGER_AREA_INFOS) - { - m_triggerInfo[m_numTriggerAreasActive].isInside = true; - m_triggerInfo[m_numTriggerAreasActive].entered = true; - m_triggerInfo[m_numTriggerAreasActive].exited = false; - m_triggerInfo[m_numTriggerAreasActive].pTrigger = pTrig; - m_enteredOrExitedFrame = now; - if (m_team) - m_team->setEnteredExited(); - TheGameLogic->updateObjectsChangedTriggerAreas(); - ++m_numTriggerAreasActive; -#ifdef RTS_DEBUG - //TheScriptEngine->AppendDebugMessage("Object entered.", false); -#endif - } - else - { - // Shouldn't happen. - static Bool didWarn = false; - if (!didWarn) - { - didWarn = true; - TheScriptEngine->AppendDebugMessage("***WARNING - Too many nested trigger areas. ***", true); - } - } - } - - } - -} - - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -Bool Object::isInList(Object **pListHead) const -{ - Bool result = m_prev || m_next || *pListHead == this; -#ifdef INTENSE_DEBUG - Bool found = false; - for (Object* o = *pListHead; o; o = o->m_next) - { - if (o == this) - { - found = true; - break; - } - } - DEBUG_ASSERTCRASH(found==result,("inconsistent links in Object::isInList")); -#endif - return result; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void Object::prependToList(Object **pListHead) -{ - DEBUG_ASSERTCRASH(!isInList(pListHead), ("obj is already in a list")); - - m_prev = NULL; - m_next = *pListHead; - if (*pListHead) - (*pListHead)->m_prev = this; - *pListHead = this; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void Object::setLayer(PathfindLayerEnum layer) -{ - if (layer!=m_layer) { -#define no_SET_LAYER_INTENSE_DEBUG -#ifdef SET_LAYER_INTENSE_DEBUG - DEBUG_LOG(("Changing layer from %d to %d\n", m_layer, layer)); - if (m_layer != LAYER_GROUND) { - if (TheTerrainLogic->objectInteractsWithBridgeLayer(this, m_layer)) { - DEBUG_CRASH(("Probably shouldn't be chaging layer. jba.")); - } - } -#endif - TheAI->pathfinder()->removePos(this); - m_layer = layer; - TheAI->pathfinder()->updatePos(this, getPosition()); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void Object::setDestinationLayer(PathfindLayerEnum layer) -{ - if (layer!=m_destinationLayer) { - m_destinationLayer = layer; - } -} - -// ------------------------------------------------------------------------------------------------ -/** Set unique ID */ -// ------------------------------------------------------------------------------------------------ -void Object::setID( ObjectID id ) -{ - - // sanity - DEBUG_ASSERTCRASH( id != INVALID_ID, ("Object::setID - Invalid id\n") ); - - // if id hasn't changed do nothing - if( m_id == id ) - return; - - // remove this objects previous id from the lookup table - TheGameLogic->removeObjectFromLookupTable( this ); - - // assign new id - m_id = id; - - // add new id to lookup table - TheGameLogic->addObjectToLookupTable( this ); - -} // end setID - -// ------------------------------------------------------------------------------------------------ -Real Object::calculateHeightAboveTerrain(void) const -{ - const Coord3D* pos = getPosition(); - Real terrainZ = TheTerrainLogic->getLayerHeight( pos->x, pos->y, m_layer ); - Real myZ = pos->z; - return myZ - terrainZ; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void Object::removeFromList(Object **pListHead) -{ - if (m_next) - m_next->m_prev = m_prev; - - if (m_prev) - m_prev->m_next = m_next; - else - *pListHead = m_next; - - m_prev = NULL; - m_next = NULL; -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void Object::friend_prepareForMapBoundaryAdjust(void) -{ - // NOTE - DO NOT remove from pathfind map. jba. - // NO NO. jba. TheAI->pathfinder()->removeObjectFromPathfindMap( this ); - - // remove from the radar, remove from the partition manager - TheRadar->removeObject(this); - ThePartitionManager->unRegisterObject(this); - - // The whole PartitionManager and all of the Looker data is about to be blown away, - // so forget what I think I have done - m_partitionLastLook->reset(); - m_partitionRevealAllLastLook->reset(); - m_partitionLastShroud->reset(); - - m_partitionLastThreat->reset(); - m_partitionLastValue->reset(); - -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void Object::friend_notifyOfNewMapBoundary(void) -{ - ThePartitionManager->registerObject(this); - TheRadar->addObject(this); - TheAI->pathfinder()->addObjectToPathfindMap( this ); - - // Now that the PartitionManager has finished its reset, we need to relook - handlePartitionCellMaintenance(); - - Region3D mapExtent; - TheTerrainLogic->getExtent(&mapExtent); - if (mapExtent.isInRegionNoZ(getPosition())) - m_privateStatus &= ~OFF_MAP; - else - m_privateStatus |= OFF_MAP; -} - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void Object::calcNaturalRallyPoint(Coord2D *pt) -{ - const Matrix3D *transform = getTransformMatrix(); - Vector3 v; - - // - // get the natural rally point from the template, this coord is in model space relative - // to the model (0,0,0) - // -/* - const Coord3D *naturalRallyPoint; - naturalRallyPoint = m_template->getNaturalRallyPoint(); - v.X = naturalRallyPoint->x; - v.Y = naturalRallyPoint->y; - v.Z = naturalRallyPoint->z; -*/ - v.Set( 0, 0, 0 ); - - // transform the point into world space - transform->Transform_Vector( *transform, v, &v ); - - // we're only concerned with the 2D elements for now - pt->x = v.X; - pt->y = v.Y; - -} - -//------------------------------------------------------------------------------------------------- -Module* Object::findModule(NameKeyType key) const -{ - Module* m = NULL; - - for (BehaviorModule** b = m_behaviors; *b; ++b) - { - if ((*b)->getModuleNameKey() == key) - { -#ifdef INTENSE_DEBUG - if (m == NULL) - { - m = *b; - } - else - { - DEBUG_CRASH(("Duplicate modules found for name %s!\n",TheNameKeyGenerator->keyToName(key).str())); - } -#else - m = *b; - break; -#endif - } - } - - return m; -} - -//------------------------------------------------------------------------------------------------- -/** - * Returns true if object is currently able to move. - */ -Bool Object::isMobile() const -{ - if (isKindOf(KINDOF_IMMOBILE)) - return false; - - // AW: This excemption is needed, because teleporters still need to listen to AI commands when disabled - if( isDisabled() && !isDisabledByType(DISABLED_TELEPORT) ) - return false; - - return true; -} - -//------------------------------------------------------------------------------------------------- -void Object::scoreTheKill( const Object *victim ) -{ - // Do stuff that has nothing to do with experience points here, like tell our Player we killed something - /// @todo Multiplayer score hook location? - - Player* victimController = victim->getControllingPlayer(); - // if the other player is not a playable side (i.e. they are civilian, observer, whatever) - // we shouldn't count the kill. - if (victimController->isPlayableSide() == FALSE) - { - return; - } - - - if ( victim->isKindOf( KINDOF_IGNORED_IN_GUI ) ) - return; - - - Player* controller = getControllingPlayer(); - - if (victimController) - { - victimController->getScoreKeeper()->addObjectLost(victim); - } - - Relationship r = getRelationship(victim); - if (r != ENEMIES) - return; - - // Don't count kills that I do on my own buildings or units, cause thats just silly. - if (controller == victimController) - { - return; - } - - if (controller) - { - controller->getScoreKeeper()->addObjectDestroyed(victim); - controller->addSkillPointsForKill(this, victim); - controller->doBountyForKill(this, victim); - } - - // Now handle experience, if we can gain any - if (m_experienceTracker && m_experienceTracker->isAcceptingExperiencePoints()) - { - // srj sez: per dustin, no experience (et al) for killing things under construction. - if (!victim->testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION)) - { - Int experienceValue = victim->getExperienceTracker()->getExperienceValue( this ); - getExperienceTracker()->addExperiencePoints( experienceValue ); - } - } -} - -//------------------------------------------------------------------------------------------------- -VeterancyLevel Object::getVeterancyLevel() const -{ - return m_experienceTracker ? m_experienceTracker->getVeterancyLevel() : LEVEL_REGULAR; -} - -//------------------------------------------------------------------------------------------------- -void Object::friend_bindToDrawable( Drawable *draw ) -{ - m_drawable = draw; - if (m_drawable) - { - ModelConditionFlags set; - ModelConditionFlags clr; - for (int i = 0; i < WEAPONSET_COUNT; ++i) - { - ModelConditionFlagType mcs = TheWeaponSetTypeToModelConditionTypeMap[i]; - if( mcs != MODELCONDITION_INVALID ) - { - if (m_curWeaponSetFlags.test(i)) - set.set(mcs); - else - clr.set(mcs); - } - } - if (TheGlobalData) - { - if (TheGlobalData->m_forceModelsToFollowTimeOfDay) - { - set.set(MODELCONDITION_NIGHT, (TheGlobalData->m_timeOfDay == TIME_OF_DAY_NIGHT) ? 1 : 0); - } - - if (TheGlobalData->m_forceModelsToFollowWeather) - { - set.set(MODELCONDITION_SNOW, (TheGlobalData->m_weather == WEATHER_SNOWY) ? 1 : 0); - } - } - m_drawable->clearAndSetModelConditionFlags(clr, set); - } - - for (BehaviorModule** b = m_behaviors; *b; ++b) - { - (*b)->onDrawableBoundToObject(); - } -} - -//------------------------------------------------------------------------------------------------- -void Object::setSelectable(Bool selectable) -{ - m_isSelectable = selectable; - if (m_drawable) - { - m_drawable->setSelectable(selectable); - } -} - -//------------------------------------------------------------------------------------------------- -Bool Object::isSelectable() const -{ -// return getTemplate()->isKindOf(KINDOF_ALWAYS_SELECTABLE) -// || (m_isSelectable -// && !testStatus(OBJECT_STATUS_UNSELECTABLE) -// && !isEffectivelyDead() -// && !getTemplate()->isKindOf(KINDOF_DRONE)//Most drones are unselectable from being slaved, but the SpyDrone needs help -// ); - - - if (getTemplate()->isKindOf(KINDOF_ALWAYS_SELECTABLE)) - return TRUE; - - if ( m_isSelectable ) - if ( !testStatus(OBJECT_STATUS_UNSELECTABLE) ) - if ( !isEffectivelyDead() ) - //if ( !getTemplate()->isKindOf(KINDOF_DRONE) )//Most drones are unselectable from being slaved, but the SpyDrone needs help - return TRUE; - - return FALSE; -} - -//------------------------------------------------------------------------------------------------- -Bool Object::isMassSelectable() const -{ - return isSelectable() && !isKindOf(KINDOF_STRUCTURE); -} - -//------------------------------------------------------------------------------------------------- -void Object::setWeaponSetFlag(WeaponSetType wst) -{ - m_curWeaponSetFlags.set(wst); - m_weaponSet.updateWeaponSet(this); - if (m_drawable) - { - m_drawable->setModelConditionState(TheWeaponSetTypeToModelConditionTypeMap[wst]); - } -} - -//------------------------------------------------------------------------------------------------- -void Object::clearWeaponSetFlag(WeaponSetType wst) -{ - m_curWeaponSetFlags.set(wst, 0); - m_weaponSet.updateWeaponSet(this); - if (m_drawable) - { - m_drawable->clearModelConditionState(TheWeaponSetTypeToModelConditionTypeMap[wst]); - } -} - -//------------------------------------------------------------------------------------------------- -Bool Object::hasSpecialPower( SpecialPowerType type ) const -{ - return TEST_SPECIALPOWERMASK( m_specialPowerBits, type ); -} - -//------------------------------------------------------------------------------------------------- -Bool Object::hasAnySpecialPower() const -{ - return SPECIALPOWERMASK_ANY_SET( m_specialPowerBits ); -} - -//------------------------------------------------------------------------------------------------- -void Object::onVeterancyLevelChanged( VeterancyLevel oldLevel, VeterancyLevel newLevel, Bool provideFeedback ) -{ - updateUpgradeModules(); - - const UpgradeTemplate* up = TheUpgradeCenter->findVeterancyUpgrade(newLevel); - if (up) - giveUpgrade(up); - - BodyModuleInterface* body = getBodyModule(); - if (body) - body->onVeterancyLevelChanged( oldLevel, newLevel, provideFeedback ); - - Bool hideAnimationForStealth = FALSE; - if( !isLocallyControlled() && - testStatus( OBJECT_STATUS_STEALTHED ) && - !testStatus( OBJECT_STATUS_DETECTED ) && - !testStatus( OBJECT_STATUS_DISGUISED ) ) - { - hideAnimationForStealth = TRUE; - } - - Bool doAnimation = ( ! hideAnimationForStealth - && (newLevel > oldLevel) - && ( ! isKindOf(KINDOF_IGNORED_IN_GUI))); //First, we plan to do the animation if the level went up - - switch (newLevel) - { - case LEVEL_REGULAR: - clearWeaponSetFlag(WEAPONSET_VETERAN); - clearWeaponSetFlag(WEAPONSET_ELITE); - clearWeaponSetFlag(WEAPONSET_HERO); - clearWeaponBonusCondition(WEAPONBONUSCONDITION_VETERAN); - clearWeaponBonusCondition(WEAPONBONUSCONDITION_ELITE); - clearWeaponBonusCondition(WEAPONBONUSCONDITION_HERO); - doAnimation = FALSE;//... but not if somehow up to Regular - break; - case LEVEL_VETERAN: - setWeaponSetFlag(WEAPONSET_VETERAN); - clearWeaponSetFlag(WEAPONSET_ELITE); - clearWeaponSetFlag(WEAPONSET_HERO); - setWeaponBonusCondition(WEAPONBONUSCONDITION_VETERAN); - clearWeaponBonusCondition(WEAPONBONUSCONDITION_ELITE); - clearWeaponBonusCondition(WEAPONBONUSCONDITION_HERO); - break; - case LEVEL_ELITE: - clearWeaponSetFlag(WEAPONSET_VETERAN); - setWeaponSetFlag(WEAPONSET_ELITE); - clearWeaponSetFlag(WEAPONSET_HERO); - clearWeaponBonusCondition(WEAPONBONUSCONDITION_VETERAN); - setWeaponBonusCondition(WEAPONBONUSCONDITION_ELITE); - clearWeaponBonusCondition(WEAPONBONUSCONDITION_HERO); - break; - case LEVEL_HEROIC: - clearWeaponSetFlag(WEAPONSET_VETERAN); - clearWeaponSetFlag(WEAPONSET_ELITE); - setWeaponSetFlag(WEAPONSET_HERO); - clearWeaponBonusCondition(WEAPONBONUSCONDITION_VETERAN); - clearWeaponBonusCondition(WEAPONBONUSCONDITION_ELITE); - setWeaponBonusCondition(WEAPONBONUSCONDITION_HERO); - break; - } - - if( doAnimation && TheGameLogic->getDrawIconUI() && provideFeedback ) - { - if( TheAnim2DCollection && TheGlobalData->m_levelGainAnimationName.isEmpty() == FALSE ) - { - Anim2DTemplate *animTemplate = TheAnim2DCollection->findTemplate( TheGlobalData->m_levelGainAnimationName ); - - Coord3D pos = *getPosition(); - pos.add(&m_healthBoxOffset); - - TheInGameUI->addWorldAnimation( animTemplate, - &pos, - WORLD_ANIM_FADE_ON_EXPIRE, - TheGlobalData->m_levelGainAnimationDisplayTimeInSeconds, - TheGlobalData->m_levelGainAnimationZRisePerSecond); - } - - AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_unitPromoted; - soundToPlay.setObjectID( getID() ); - TheAudio->addAudioEvent( &soundToPlay ); - } - -} - -//------------------------------------------------------------------------------------------------- -/** - * Returns true if object currently has some kind of attack capability - */ -Bool Object::isAbleToAttack() const -{ - - //****************************************************** - //********* AUTOMATICALLY FALSE CONDITIONS ************* - //****************************************************** - - // For things that may or may not be able to normally attack, but are under a status condition - if( getStatusBits().test( OBJECT_STATUS_NO_ATTACK ) ) - return false; - - // if we're contained within a transport we cannot attack unless it specifically allows us - const Object *containedBy = getContainedBy(); - DEBUG_ASSERTCRASH( (containedBy == NULL) || (containedBy->getContain() != NULL), ("A %s thinks they are contained by something with no contain module!", getTemplate()->getName().str() ) ); - if( containedBy && containedBy->getContain() && !containedBy->getContain()->isPassengerAllowedToFire( getID() ) ) - return false; - - - // We can't fire if under construction - if( testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) ) - return false; - - // or being sold - if( testStatus(OBJECT_STATUS_SOLD) ) - return false; - - if ( isDisabledByType( DISABLED_SUBDUED ) ) - return FALSE; // A Microwave Tank is cooking me - - //We can't fire if we, as a portable structure, are aptly disabled - if ( isKindOf( KINDOF_PORTABLE_STRUCTURE ) || isKindOf( KINDOF_SPAWNS_ARE_THE_WEAPONS )) - { - if( isDisabledByType( DISABLED_HACKED ) || isDisabledByType( DISABLED_EMP ) ) - return false; - - if ( isKindOf( KINDOF_INFANTRY ) ) // I must be a stinger soldier or similar - { - for (BehaviorModule** update = getBehaviorModules(); *update; ++update)//expensive search, limited only to stinger soldiers - { - SlavedUpdateInterface* sdu = (*update)->getSlavedUpdateInterface(); - if ( sdu ) - { - ObjectID slaverID = sdu->getSlaverID(); - if ( slaverID != INVALID_ID ) - { - Object *slaver = TheGameLogic->findObjectByID( slaverID ); - if ( slaver && slaver->isDisabledByType( DISABLED_SUBDUED )) - return FALSE;// if my stinger site is subdued, so am I - } - - break;//only expect one slavedupdate, so stop searching - } - } - } - - - } - - - - //We can't fire if all our weapons are disabled! - //Currently, only turreted weapons can be disabled. - //ONLY DO THIS CHECK IF OUR UNIT DOESN'T HAVE THE - //KINDOF_CAN_ATTACK flag... nuke cannons have disabled - //turrets when not deployed, and need to be able to attack to deploy! - //Strategy centers can't attack when bombardment isn't active! - Bool anyEnabled = FALSE; - Bool anyWeapon = FALSE; - const AIUpdateInterface *ai = getAI(); - if( ai && !isKindOf( KINDOF_CAN_ATTACK ) ) - { - for( Int i = 0; i < WEAPONSLOT_COUNT; i++ ) - { - //Find the weapon in this slot. - Weapon* weapon = getWeaponInWeaponSlot( (WeaponSlotType)i ); - if( !weapon ) - continue; - - anyWeapon = TRUE; - - //We found a weapon, is it a turret? - Real dummy; - WhichTurretType tur = ai->getWhichTurretForWeaponSlot( (WeaponSlotType)i, &dummy ); - if( tur == TURRET_INVALID ) - { - //Currently impossible to disable a non-turreted weapon, so we - //have a non turreted weapon that is enabled. Quit. - anyEnabled = TRUE; - break; - } - - if( ai->isTurretEnabled( tur ) ) - { - //The turret is enable, meaning we have an enabled weapon. Quit. - anyEnabled = TRUE; - break;; - } - } - if( anyWeapon && !anyEnabled ) - { - //We failed to find any active weapons. - return FALSE; - } - } - - - //*************************************** - //********* TRUE CONDITIONS ************* - //*************************************** - - // for certain buildings - if (isKindOf(KINDOF_CAN_ATTACK)) - return true; - - // for garrisonned buildings that can attack sometimes - if( getStatusBits().test( OBJECT_STATUS_CAN_ATTACK ) ) - return true; - - // for weaponless transports. This will make me think I can, but I will check if I literally can by looking - // at passenger weapons in CanAttack. - const ContainModuleInterface* contain = getContain(); - if( contain && contain->isPassengerAllowedToFire( getID() ) && contain->getContainCount() > 0 ) - return true; - - // if we have AI and a weapon, assume we know how to use it - if (getAIUpdateInterface() != NULL && m_weaponSet.hasAnyWeapon()) - { - -// actually, we don't want to do this; we want the troop crawler to be considered "able to attack" -// even if empty, so sayeth Dustin. (srj) -// // special case: if the only damage we do is DEPLOY, we must have some guys contained. -// if (m_weaponSet.hasSingleDamageType(DAMAGE_DEPLOY)) -// { -// return contain->getContainCount() > 0; -// } -// else - { - return true; - } - } - - SpawnBehaviorInterface *spawnInterface = getSpawnBehaviorInterface(); - if( spawnInterface ) - { - if( spawnInterface->canAnySlavesAttack() ) - { - return TRUE; - } - } - - if (getTemplate()->isEnterGuard()) - return TRUE; - -//Default is no - return false; -} - -//------------------------------------------------------------------------------------------------- -/** - * Mask/Un-Mask an object - */ -void Object::maskObject( Bool mask ) -{ - - // set or clear the mask bit - setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ), mask ); - - // - // when masking objects they become unselected ... we do this in any situation for - // any player cause you aren't allowed to select masked objects, if the object is not - // selected (ie, belongs to another player) it's no big deal cause it won't be selected - // anyway - // - - if (mask) - TheGameLogic->deselectObject(this, ~getControllingPlayer()->getPlayerMask(), TRUE); - -} // end maskObject - -//------------------------------------------------------------------------------------------------- -/* - * returns true if the current locomotor is an airborne one - */ -Bool Object::isUsingAirborneLocomotor( void ) const -{ - return ( m_ai && m_ai->getCurLocomotor() && ((m_ai->getCurLocomotor()->getLegalSurfaces() & LOCOMOTORSURFACE_AIR) != 0) ); -} - -//------------------------------------------------------------------------------------------------- -//THIS FUNCTION BELONGS AT THE OBJECT LEVEL BECAUSE THERE IS AT LEAST ONE SPECIAL UNIT -//(ANGRY MOB) WHICH NEEDS LOGIC-SIDE POSITION CALC'S... -//IT WOULD PROBABLY BE WISE TO MOVE ALL THE HARD-CODED DEFAULTS BELOW -//INTO A NEW Drawable::getHealthBox..() WHICH USES GEOM0INFO, MODEL DATA, INI DATA, ETC. -void Object::getHealthBoxPosition(Coord3D& pos) const -{ - pos = *getPosition(); - pos.z += getGeometryInfo().getMaxHeightAbovePosition() + 10; - pos.add(&m_healthBoxOffset); - - // this needs to get moved to the mobspawnerupdate - if (isKindOf(KINDOF_MOB_NEXUS)) // quicker idiot test - { - pos.z += 20;// dear God, I confess my kluge, and repent. - } -} - -//------------------------------------------------------------------------------------------------- -//THIS FUNCTION BELONGS AT THE OBJECT LEVEL BECAUSE THERE IS AT LEAST ONE SPECIAL UNIT -//(ANGRY MOB) WHICH NEEDS LOGIC-SIDE POSITION CALC'S... -//IT WOULD PROBABLY BE WISE TO MOVE ALL THE HARD-CODED DEFAULTS BELOW -//INTO A NEW Drawable::getHealthBox..() WHICH USES GEOM0INFO, MODEL DATA, INI DATA, ETC. -Bool Object::getHealthBoxDimensions(Real &healthBoxHeight, Real &healthBoxWidth) const -{ - -#ifdef CALC_HEALTHBAR_FROM_HITPOINTS - Real maxHP = getBodyModule()->getMaxHealth(); - - if( isKindOf( KINDOF_STRUCTURE ) ) - { - //enforce healthBoxHeightMinimum/Maximum - healthBoxHeight = min(3.0f, max(5.0f, maxHP/50)); - //enforce healthBoxWidthMinimum/Maximum - healthBoxWidth = min(150.0f, max(100.0f, maxHP/10)); - return true; - } - else if ( isKindOf(KINDOF_MOB_NEXUS) ) - { - //enforce healthBoxHeightMinimum/Maximum - healthBoxHeight = min(3.0f, max(5.0f, maxHP/50)); - //enforce healthBoxWidthMinimum/Maximum - healthBoxWidth = min(100.0f, max(66.0f, maxHP/10)); - return true; - } - else if ( isKindOf( KINDOF_IGNORED_IN_GUI ) ) - { - healthBoxHeight = 0; - healthBoxWidth = 0; - return false; - } - else - { - //enforce healthBoxHeightMinimum/Maximum - healthBoxHeight = min(3.0f, max(5.0f, maxHP/50)); - //enforce healthBoxWidthMinimum/Maximum - healthBoxWidth = min(150.0f, max(35.0f, maxHP/10)); - return true; - } -#else - - if ( isKindOf( KINDOF_IGNORED_IN_GUI ) ) - { - healthBoxHeight = 0; - healthBoxWidth = 0; - return false; - } - - //just add the major and minor axes - Real size = MAX(20.0f, MIN(150.0f, (getGeometryInfo().getMajorRadius() + getGeometryInfo().getMinorRadius())) ); - healthBoxHeight = 3.0f; - healthBoxWidth = MAX(20.0f, size * 2.0f); - return TRUE; - -#endif - -} - - -//------------------------------------------------------------------------------------------------- -/** - * Update this object instance with properties from the map object - * - */ -void Object::updateObjValuesFromMapProperties(Dict* properties) -{ - Bool exists; - - AsciiString valStr; - Bool valBool = false; - Int valInt = 0; - Real valReal = 0.0f; - - valStr = properties->getAsciiString(TheKey_objectName, &exists); - if (exists) { - setName(valStr); - } - - valInt = properties->getInt(TheKey_objectMaxHPs, &exists); - if (exists && valInt >= 0) { - BodyModuleInterface* body = getBodyModule(); - if (body) { - body->setMaxHealth(valInt); - } - } - - valInt = properties->getInt(TheKey_objectInitialHealth, &exists); - if (exists) { - BodyModuleInterface* body = getBodyModule(); - if (body) { - body->setInitialHealth(valInt); - } - } - - // set the veterancy level - valInt = properties->getInt(TheKey_objectVeterancy, &exists); - if (exists) { - if (m_experienceTracker && m_experienceTracker->isTrainable()) - { - m_experienceTracker->setVeterancyLevel((VeterancyLevel)valInt); - } - } - - // set the aggressiveness/mood - valInt = properties->getInt(TheKey_objectAggressiveness, &exists); - if (exists) { - AIUpdateInterface *ai = getAIUpdateInterface(); - if (ai) - { - ai->setAttitude((AttitudeType)valInt); - } - } - - // set recruitable - valBool = properties->getBool(TheKey_objectRecruitableAI, &exists); - if (exists) { - if (getAIUpdateInterface()) - { - getAIUpdateInterface()->setIsRecruitable(valBool); - } - } - - // set selectable - valBool = properties->getBool(TheKey_objectSelectable, &exists); - if (exists) { - if (valBool != isSelectable()) { - setSelectable(valBool); - } - } - - // set the stopping distance - valReal = properties->getReal(TheKey_objectStoppingDistance, &exists); - if (exists && valReal >= 0.5f) - { - if (getAIUpdateInterface() && getAIUpdateInterface()->getCurLocomotor()) - { - Locomotor *loco = getAIUpdateInterface()->getCurLocomotor(); - loco->setCloseEnoughDist(valReal); - } - } - - // set the disabledness of this object - valBool = properties->getBool(TheKey_objectEnabled, &exists); - if (exists) { - setScriptStatus(OBJECT_STATUS_SCRIPT_DISABLED, !valBool); - } - - // set the disabledness of this object - valBool = properties->getBool(TheKey_objectPowered, &exists); - if (exists) { - setScriptStatus(OBJECT_STATUS_SCRIPT_UNPOWERED, !valBool); - } - - // set the invulnerability of the object - valBool = properties->getBool(TheKey_objectIndestructible, &exists); - if (exists) { - BodyModuleInterface* body = getBodyModule(); - if (body) { - body->setIndestructible(valBool); - } - } - - // set the sellability of the object - valBool = properties->getBool(TheKey_objectUnsellable, &exists); - if (exists) { - setScriptStatus(OBJECT_STATUS_SCRIPT_UNSELLABLE, valBool); - } - - //Set the player targetable setting of the object - valBool = properties->getBool( TheKey_objectTargetable, &exists ); - if( exists ) - { - setScriptStatus(OBJECT_STATUS_SCRIPT_TARGETABLE, valBool); - } - - // adjust the vision distance of this object, overriding its default vision distance - valInt = properties->getInt(TheKey_objectVisualRange, &exists); - if (exists) - { - if (valInt < 0) - valInt = 0; - m_visionRange = INT_TO_REAL(valInt); - } - - // adjust the shroud clearing distance of this object, overriding its default distance - valInt = properties->getInt(TheKey_objectShroudClearingDistance, &exists); - if (exists) - { - if (valInt < 0) - valInt = 0.0f; - m_shroudClearingRange = INT_TO_REAL(valInt); - } - - - Int upgradeNum = 0; - do - { - AsciiString keyName; - keyName.format("%s%d", TheNameKeyGenerator->keyToName(TheKey_objectGrantUpgrade).str(), upgradeNum); - valStr = properties->getAsciiString(NAMEKEY(keyName), &exists); - - if (exists) - { - const UpgradeTemplate *ut = TheUpgradeCenter->findUpgrade(valStr); - if (ut) - giveUpgrade(ut); - } - else - { - valStr.clear(); - } - - ++upgradeNum; - } while (!valStr.isEmpty()); - - Drawable *drawable = getDrawable(); - if ( drawable ) - { - valInt = properties->getInt(TheKey_objectTime, &exists); - if (exists) - { - switch (valInt) - { - case 1: - drawable->clearModelConditionState(MODELCONDITION_NIGHT); - break; - case 2: - drawable->setModelConditionState(MODELCONDITION_NIGHT); - break; - default: - break; - } - } - - valInt = properties->getInt(TheKey_objectWeather, &exists); - if (exists) - { - switch (valInt) - { - case 1: - drawable->clearModelConditionState(MODELCONDITION_SNOW); - break; - case 2: - drawable->setModelConditionState(MODELCONDITION_SNOW); - break; - default: - break; - } - } - - // See if we are supposed to playing the ambient sound - Bool soundEnabledExists; - Bool soundEnabled = properties->getBool( TheKey_objectSoundAmbientEnabled, &soundEnabledExists ); - - DynamicAudioEventInfo * audioToModify = NULL; - Bool infoModified = false; - valStr = properties->getAsciiString( TheKey_objectSoundAmbient, &exists ); - if ( exists ) - { - if ( valStr.isEmpty() ) - { - drawable->setCustomSoundAmbientOff(); - soundEnabledExists = true; - soundEnabled = false; // Don't bother trying to enable later - } - else - { - const AudioEventInfo * baseInfo = TheAudio->findAudioEventInfo( valStr ); - DEBUG_ASSERTCRASH( baseInfo != NULL, ("Cannot find customized ambient sound '%s'", valStr.str() ) ); - if ( baseInfo != NULL ) - { - audioToModify = newInstance( DynamicAudioEventInfo )( *baseInfo ); - infoModified = true; - } - } - } - - // Don't do anything more to audio if we forced the ambient sound off - if ( !( exists && valStr.isEmpty() ) ) - { - valBool = properties->getBool( TheKey_objectSoundAmbientCustomized, &exists ); - if ( exists && valBool ) - { - if ( audioToModify == NULL ) - { - const AudioEventInfo * baseInfo = drawable->getBaseSoundAmbientInfo( ); - DEBUG_ASSERTCRASH( baseInfo != NULL, ("getBaseSoundAmbientInfo() return NULL" ) ); - if ( baseInfo != NULL ) - { - audioToModify = newInstance( DynamicAudioEventInfo )( *baseInfo ); - } - } - - if ( audioToModify != NULL ) - { - valBool = properties->getBool( TheKey_objectSoundAmbientLooping, &exists ); - if ( exists ) - { - audioToModify->overrideLoopFlag( valBool ); - infoModified = true; - } - - valInt = properties->getInt( TheKey_objectSoundAmbientLoopCount, &exists ); - if ( exists && BitIsSet( audioToModify->m_control, AC_LOOP ) ) - { - audioToModify->overrideLoopCount( valInt ); - infoModified = true; - } - - valReal = properties->getReal( TheKey_objectSoundAmbientMinVolume, &exists ); - if ( exists ) - { - audioToModify->overrideMinVolume( valReal ); - infoModified = true; - } - - valReal = properties->getReal( TheKey_objectSoundAmbientVolume, &exists ); - if ( exists ) - { - audioToModify->overrideVolume( valReal ); - infoModified = true; - } - - valReal = properties->getReal( TheKey_objectSoundAmbientMinRange, &exists ); - if ( exists ) - { - audioToModify->overrideMinRange( valReal ); - infoModified = true; - } - - valReal = properties->getReal( TheKey_objectSoundAmbientMaxRange, &exists ); - if ( exists ) - { - audioToModify->overrideMaxRange( valReal ); - infoModified = true; - } - - valInt = properties->getInt( TheKey_objectSoundAmbientPriority, &exists ); - if ( exists ) - { - audioToModify->overridePriority ( (AudioPriority)valInt ); - infoModified = true; - } - } - } - } - - if ( !soundEnabledExists ) - { - // Decide if the sound should start enabled or not, since the map maker didn't record - // a preference. Enable permanently looping sounds, disable one-shot sounds by default - // NOTE: This test should match the tests done in MapObjectProps::mapObjectPageSound::dictToEnabled() - // when it decided whether or not to show a customized sound as enabled - if ( audioToModify != NULL ) - { - soundEnabled = audioToModify->isPermanentSound(); - soundEnabledExists = true; // To get into enableAmbientSoundFromScript() call. - } - else - { - // Use default audio - const AudioEventInfo * baseInfo = drawable->getBaseSoundAmbientInfo( ); - if ( baseInfo != NULL ) - { - soundEnabled = baseInfo->isPermanentSound(); - soundEnabledExists = true; // To get into enableAmbientSoundFromScript() call. - } - } - } - - if ( soundEnabledExists && !soundEnabled ) - { - // Make sure sound doesn't start playing when we set it - // ...FromScript because this is also controlled by the map designer not the game logic - drawable->enableAmbientSoundFromScript( false ); - } - - if ( infoModified && audioToModify != NULL ) - { - // Give a custom, level-specific name - drawable->mangleCustomAudioName( audioToModify ); - - // Pass to TheAudio - TheAudio->addAudioEventInfo( audioToModify ); - - drawable->setCustomSoundAmbientInfo( audioToModify ); - audioToModify = NULL; // Belongs to TheAudio now - } - - if ( audioToModify != NULL ) - { - audioToModify->deleteInstance(); - audioToModify = NULL; - } - - if ( soundEnabledExists && soundEnabled ) - { - // Play sound now that it is set up, if needed. Don't call if already enabled because that - // can cause sound to play twice - // ...FromScript because this is also controlled by the map designer not the game logic - if ( !drawable->getAmbientSoundEnabledFromScript() ) - { - drawable->enableAmbientSoundFromScript( true ); - } - } - } -} - -//------------------------------------------------------------------------------------------------- -void Object::friend_adjustPowerForPlayer( Bool incoming ) -{ - if (isDisabled() && getTemplate()->getEnergyProduction() > 0) - { - // Disabledness only affects Producers, not Consumers. - return; - } - - if (incoming) { - getControllingPlayer()->getEnergy()->objectEnteringInfluence(this); - } else { - getControllingPlayer()->getEnergy()->objectLeavingInfluence(this); - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//------------------------------------------------------------------------------------------------- -void Object::onDisabledEdge(Bool becomingDisabled) -{ - // rip through the behavior modules and call the onDisabledEdge for any modules that care - for( BehaviorModule **module = m_behaviors; *module; ++module ) - (*module)->onDisabledEdge( becomingDisabled ); - - DozerAIInterface *dozerAI = getAI() ? getAI()->getDozerAIInterface() : NULL; - if( becomingDisabled && dozerAI ) - { - // Have to say goodbye to the thing we might be building or repairing so someone else can do it. - if( dozerAI->getCurrentTask() != DOZER_TASK_INVALID ) - dozerAI->cancelTask( dozerAI->getCurrentTask() ); - } - - Player* controller = getControllingPlayer(); - // can be called during game teardown, thus controller can be null - if (controller) - { - //@todo jkmcd - Colin suggested we rewrite this to use the interface stuff. I agree, but need - // to get some more bugs fixed today. - static NameKeyType radar = NAMEKEY("RadarUpgrade"); - Module *mod = mod = findModule(radar); - if (mod) { - RadarUpgrade *radarMod = (RadarUpgrade*) mod; - if (radarMod->isAlreadyUpgraded()) { - // Need to decrement the count here, because we own a radar upgrade - if (becomingDisabled) { - controller->removeRadar(radarMod->getIsDisableProof()); - } else { - controller->addRadar(radarMod->getIsDisableProof()); - } - } - } - } - - // We will need to adjust power ... somehow ... - Int powerToAdjust = getTemplate()->getEnergyProduction(); - - if( powerToAdjust > 0 ) - { - // We can't affect something that consumes, or else we go low power which removes the consumption - // which makes us not low power so we add the consumption so we go low power... - // This check also guaards the IsDisabled in friend_adjustPower above - static NameKeyType powerPlant = NAMEKEY("PowerPlantUpgrade"); - static NameKeyType overCharge = NAMEKEY("OverchargeBehavior"); - - Module* mod = findModule(powerPlant); - if (mod) { - PowerPlantUpgrade *powerPlantMod = (PowerPlantUpgrade*) mod; - if (powerPlantMod->isAlreadyUpgraded()) { - powerToAdjust += getTemplate()->getEnergyBonus(); - } - } - - mod = findModule(overCharge); - if (mod) { - OverchargeBehavior *overChargeMod = (OverchargeBehavior*) mod; - if (overChargeMod->isOverchargeActive()) { - powerToAdjust += getTemplate()->getEnergyBonus(); - } - } - - // Now, adjust the power for the player. - if (controller) - controller->getEnergy()->adjustPower(powerToAdjust, !becomingDisabled); - } -} - -//------------------------------------------------------------------------------------------------- -/** Object CRC implemtation */ -//------------------------------------------------------------------------------------------------- -void Object::crc( Xfer *xfer ) -{ -#ifdef DEBUG_CRC -// g_logObjectCRCs = TRUE; -// Bool g_logAllObjects = TRUE; - AsciiString logString; - AsciiString tmp; - Bool doLogging = g_logObjectCRCs /* && getControllingPlayer()->getPlayerType() == PLAYER_HUMAN */; - if (doLogging) - { - tmp.format("CRC of Object %d (%s), owned by player %d, team: %d, ", m_id, getTemplate()->getName().str(), getControllingPlayer()->getPlayerIndex(), this->getTeam() ? this->getTeam()->getID() : TEAM_ID_INVALID); - logString.concat(tmp); - } -#endif // DEBUG_CRC - - xfer->xferUnsignedByte(&m_privateStatus); -#ifdef DEBUG_CRC - if (doLogging) - { - tmp.format("m_privateStatus: %X, ", (UnsignedInt)m_privateStatus); - logString.concat(tmp); - } -#endif // DEBUG_CRC - - // This is evil - we cast the const Matrix3D * to a Matrix3D * because the XferCRC class must use - // the same interface as the XferLoad class for save game restore. This only works because - // XferCRC does not modify its data. - xfer->xferUser((Matrix3D *)getTransformMatrix(), sizeof(Matrix3D)); -#ifdef DEBUG_CRC - if (doLogging) - { - XferCRC tmpXfer; - tmpXfer.open("tmp"); - tmpXfer.xferUser((Matrix3D *)getTransformMatrix(), sizeof(Matrix3D)); - tmp.format("getTransformMatrix(): %8.8X, ", tmpXfer.getCRC()); - tmpXfer.close(); - logString.concat(tmp); - } -#endif // DEBUG_CRC - - - xfer->xferUser(&m_id, sizeof(m_id)); -#ifdef DEBUG_CRC - if (doLogging) - { - tmp.format("m_id: %d, ", m_id); - logString.concat(tmp); - } -#endif // DEBUG_CRC - xfer->xferUser(&m_objectUpgradesCompleted, sizeof(Int64)); -#ifdef DEBUG_CRC - if (doLogging) - { - tmp.format("m_objectUpgradesCompleted: %I64X, ", m_objectUpgradesCompleted); - logString.concat(tmp); - } -#endif // DEBUG_CRC - if (m_experienceTracker) - xfer->xferSnapshot( m_experienceTracker ); -#ifdef DEBUG_CRC - if (doLogging) - { - XferCRC tmpXfer; - tmpXfer.open("tmp"); - tmpXfer.xferSnapshot(m_experienceTracker); - tmp.format("m_experienceTracker: %8.8X, ", tmpXfer.getCRC()); - tmpXfer.close(); - logString.concat(tmp); - } -#endif // DEBUG_CRC - - Real health = getBodyModule()->getHealth(); - xfer->xferUser(&health, sizeof(health)); -#ifdef DEBUG_CRC - if (doLogging) - { - tmp.format("health: %g/%8.8X, ", health, AS_INT(health)); - logString.concat(tmp); - } -#endif // DEBUG_CRC - - xfer->xferUnsignedInt(&m_weaponBonusCondition); -#ifdef DEBUG_CRC - if (doLogging) - { - tmp.format("m_weaponBonusCondition: %8.8X, ", m_weaponBonusCondition); - logString.concat(tmp); - } -#endif // DEBUG_CRC - - Real scalar = getBodyModule()->getDamageScalar(); - xfer->xferUser(&scalar, sizeof(scalar)); -#ifdef DEBUG_CRC - if (doLogging) - { - tmp.format("damage scalar: %g/%8.8X\n", scalar, AS_INT(scalar)); - logString.concat(tmp); - - CRCDEBUG_LOG(("%s", logString.str())); - } -#endif // DEBUG_CRC - - for (Int i=0; ixferSnapshot( thisWeapon ); - } - } - -} // end crc - -//------------------------------------------------------------------------------------------------- -/** Object xfer implemtation - * Version Info: - * 1: Initial version - * 2: Xfers m_singleUseCommandUsed... determines if the single use command button has been used or not. - * 3: Xfers the solehealingbenefactor ID and expiration frame - * 4: misc stuff that got missed somehow - * 5: m_isReceivingDifficultyBonus - * 6: We do indeed need to save m_containedBy. The comment misrepresents what the contain module will do. - * 7: save full mtx, not pos+orient. - * 8: Kris: Conversion of object status bits from UnsignedInt to BitFlags<> - * 9: Extra sighting for reveal to all with different range units - */ -//------------------------------------------------------------------------------------------------- -void Object::xfer( Xfer *xfer ) -{ - - // version - const XferVersion currentVersion = 9; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // object ID - ObjectID id = getID(); - xfer->xferObjectID( &id ); - setID( id ); - - DEBUG_LOG(("Xfer Object %s id=%d\n",getTemplate()->getName().str(),id)); - - if (version >= 7) - { - Matrix3D mtx = *getTransformMatrix(); - xfer->xferMatrix3D(&mtx); - setTransformMatrix(&mtx); - } - else - { - // object position - Coord3D pos = *getPosition(); - xfer->xferCoord3D( &pos ); - setPosition( &pos ); - - // orientation - Real orientation = getOrientation(); - xfer->xferReal( &orientation ); - setOrientation( orientation ); - } - - // team - TeamID teamID = m_team ? m_team->getID() : TEAM_ID_INVALID; - xfer->xferUser( &teamID, sizeof( TeamID ) ); - // DON'T set the team yet; must wait till we read our status bits, - // since setTeam can affect the player's power usage, but that could - // be done incorrectly if our status bits aren't accurate yet... (srj) - - // producer id - xfer->xferObjectID( &m_producerID ); - - // builder id - xfer->xferObjectID( &m_builderID ); - - // drawable id - Drawable *draw = getDrawable(); - DrawableID drawableID = draw ? draw->getID() : INVALID_DRAWABLE_ID; - xfer->xferDrawableID( &drawableID ); - if( xfer->getXferMode() == XFER_LOAD ) - { - - // change the ID of the drawable attached to be the same ID as it was when it was saved - draw->setID( drawableID ); - - } // end if - - // internal name - xfer->xferAsciiString( &m_name ); - - // status - if( version >= 8 ) - { - m_status.xfer( xfer ); - } - else - { - //We are loading an old version, so we must convert it from a 32-bit int to a bitflag - UnsignedInt oldStatus; - xfer->xferUnsignedInt( &oldStatus ); - - //Clear our status - m_status.clear(); - - for( int i = 0; i < 32; i++ ) - { - UnsignedInt bit = 1<xferUnsignedByte( &m_scriptStatus ); - - // private status - xfer->xferUnsignedByte( &m_privateStatus ); - - // OK, now that we have xferred our status bits, it's safe to set the team... - if( xfer->getXferMode() == XFER_LOAD ) - { - Team *team = TheTeamFactory->findTeamByID( teamID ); - if( team == NULL ) - { - DEBUG_CRASH(( "Object::xfer - Unable to load team\n" )); - throw SC_INVALID_DATA; - } - const Bool restoring = true; - setOrRestoreTeam( team, restoring ); - } - - // geometry info - xfer->xferSnapshot( &m_geometryInfo ); - - // sighting info, last look - must be saved cause we save PartitionCell::m_shroudLevel - xfer->xferSnapshot( m_partitionLastLook ); - - if( version >= 9 ) - xfer->xferSnapshot( m_partitionRevealAllLastLook ); - - // sighting info, last shroud - must be saved cause we save PartitionCell::m_shroudLevel - xfer->xferSnapshot( m_partitionLastShroud ); - - // vision spied by - xfer->xferUser( m_visionSpiedBy, sizeof( Int ) * MAX_PLAYER_COUNT ); - - // vision spied by mask - xfer->xferUser( &m_visionSpiedMask, sizeof( PlayerMaskType ) ); - - // sighting info, last threat - // John M says we don't need to save this (CBD) -// xfer->xferSnapshot( &m_partitionLastThreat ); - - // sighting info, last value - // John M says we don't need to save this (CBD) -// xfer->xferSnapshot( &m_partitionLastValue ); - - // vision range - xfer->xferReal( &m_visionRange ); - - // shroud clearing range - xfer->xferReal( &m_shroudClearingRange ); - - // shroud range - xfer->xferReal( &m_shroudRange ); - - // disabled mask - m_disabledMask.xfer( xfer ); - - //New var added for version 2. Determines if the single use command button has been used or not. - if( xfer->getXferMode() == XFER_SAVE || version >= 2 ) - { - xfer->xferBool( &m_singleUseCommandUsed ); - } - else - { - m_singleUseCommandUsed = false; - } - - // disabled till frame - xfer->xferUser( m_disabledTillFrame, sizeof( UnsignedInt ) * DISABLED_COUNT ); - - // special model condition until - xfer->xferUnsignedInt( &m_smcUntil ); - - // - // radar data ... when loading, we will remove all objects from the radar and let - // the radar system load itself as a separate chunk of data from the save file - // - if( xfer->getXferMode() == XFER_LOAD && m_radarData ) - TheRadar->removeObject( this ); - - // experience tracker - xfer->xferSnapshot( m_experienceTracker ); - - // - // we do not need to do anything with our m_containedBy pointer, the post process - // of that objects contain module will actually re-do the contain process again - // - // m_containedBy <-- do nothing with this right now - if( version >= 6 ) - { - // No, the contain module is just going to friend_ reach in and set this for us. - // Containers more complicated than Open (like Tunnel) can't do that. Our variable, - // our responsibility. - if( xfer->getXferMode() == XFER_SAVE ) - { - if( m_containedBy != NULL ) - m_xferContainedByID = m_containedBy->getID(); - else - m_xferContainedByID = INVALID_ID; - } - - - xfer->xferObjectID( &m_xferContainedByID ); - } - - // contained by frame - xfer->xferUnsignedInt( &m_containedByFrame ); - - // construction percent - xfer->xferReal( &m_constructionPercent ); - - // upgrades completed - xfer->xferUpgradeMask( &m_objectUpgradesCompleted ); - - // original team name - xfer->xferAsciiString( &m_originalTeamName ); - - // indicator color - xfer->xferColor( &m_indicatorColor ); - - // health box offset - xfer->xferCoord3D( &m_healthBoxOffset ); - - // Entered & exited housekeeping. - Int i; - xfer->xferByte(&m_numTriggerAreasActive); - xfer->xferUnsignedInt(&m_enteredOrExitedFrame); - xfer->xferICoord3D(&m_iPos); - if (m_numTriggerAreasActive<0 || m_numTriggerAreasActive>MAX_TRIGGER_AREA_INFOS) { - DEBUG_CRASH(("Invalid m_numTriggerAreasActive = %d, max is %d", m_numTriggerAreasActive, - MAX_TRIGGER_AREA_INFOS)); - throw SC_INVALID_DATA; - } - for (i=0; igetTriggerName(); - } - xfer->xferAsciiString(&triggerName); - if (xfer->getXferMode() == XFER_LOAD) - { - // - // CBD (11-13-2002) I'm disabling this because it appears there might be some areas with - // empty names, see John A. for more info - // - //if (triggerName.isNotEmpty()) - m_triggerInfo[i].pTrigger = TheTerrainLogic->getTriggerAreaByName(triggerName); - } - xfer->xferByte(&m_triggerInfo[i].entered); - xfer->xferByte(&m_triggerInfo[i].exited); - xfer->xferByte(&m_triggerInfo[i].isInside); - } - // Layer object is pathing on. - xfer->xferUser(&m_layer, sizeof(m_layer)); - - // Layer of current path goal. - xfer->xferUser(&m_destinationLayer, sizeof(m_destinationLayer)); - - // Object selectability. - xfer->xferBool(&m_isSelectable); - - xfer->xferUnsignedInt(&m_safeOcclusionFrame); - - // User formations. - xfer->xferUser(&m_formationID, sizeof(m_formationID)); - if (m_formationID!=NO_FORMATION_ID) { - xfer->xferCoord2D(&m_formationOffset); - } - - // module count - UnsignedShort moduleCount = 0; - for (BehaviorModule** b = m_behaviors; *b; ++b) - ++moduleCount; - - xfer->xferUnsignedShort( &moduleCount ); - AsciiString moduleIdentifier; - BehaviorModule *module; - if( xfer->getXferMode() == XFER_SAVE ) - { - - // go through all modules - for (BehaviorModule** b = m_behaviors; *b; ++b) - { - - // get module - module = *b; - - // write module identifier - moduleIdentifier = TheNameKeyGenerator->keyToName( module->getModuleTagNameKey() ); - DEBUG_ASSERTCRASH( moduleIdentifier != AsciiString::TheEmptyString, - ("Object::xfer - Module tag key does not translate to a string!\n") ); - xfer->xferAsciiString( &moduleIdentifier ); - - // begin a data block - xfer->beginBlock(); - - // xfer data - xfer->xferSnapshot( module ); - - // end data block - xfer->endBlock(); - - } // end for, it - - } // end if, save - else - { - AsciiString otherModuleIdentifier; - - // read all module data - for( UnsignedShort i = 0; i < moduleCount; ++i ) - { - - // read module name - xfer->xferAsciiString( &moduleIdentifier ); - NameKeyType moduleIdentifierKey = TheNameKeyGenerator->nameToKey(moduleIdentifier); - - // find the module with this identifier in the module list - module = NULL; - for (BehaviorModule** b = m_behaviors; b && *b; ++b) - { - - if (moduleIdentifierKey == (*b)->getModuleTagNameKey()) - { - module = *b; - break; - } - - } // end for, moduleIt - - // start of a new block - Int dataSize = xfer->beginBlock(); - - // - // if we didn't find the module, it's quite possible that we have removed - // it from the object definition in a future patch, if that is so, we need to - // skip the module data in the file - // - if( module == NULL ) - { - - // for testing purposes, this module better be found -// DEBUG_CRASH(( "Object::xfer - Module '%s' was indicated in file, but not found on object '%s'(%d)\n", -// moduleIdentifier.str(), getTemplate()->getName().str(), getID() )); - - // skip this data in the file - xfer->skip( dataSize ); - - } // end if - else - { - - // xfer the data into this module - xfer->xferSnapshot( module ); - - } // end else - - // end block - xfer->endBlock(); - - } // end for, i module count recorded in file - - } // end else, load - - - if ( version >= 3 ) - { - xfer->xferObjectID( &m_soleHealingBenefactorID ); - xfer->xferUnsignedInt( &m_soleHealingBenefactorExpirationFrame ); - } - else if ( xfer->getXferMode() == XFER_LOAD ) - { - m_soleHealingBenefactorID = INVALID_ID; - m_soleHealingBenefactorExpirationFrame = 0; - } - - // Doesn't need to be saved. These are created as needed. jba. - //AIGroup* m_group; ///< if non-NULL, we are part of this group of agents - - // don't need to save m_partitionData. - DEBUG_ASSERTCRASH(!(xfer->getXferMode() == XFER_LOAD && m_partitionData == NULL), ("should not be in partitionmgr yet")); - - // don't need to be saved or loaded; are inited & cached for runtime only by our ctor (srj) - //m_repulsorHelper; - //m_smcHelper; - //m_wsHelper; - //m_defectionHelper; - //m_firingTracker; - //m_contain; - //m_body; - //m_ai; - //m_physics; -#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) - //m_hasDiedAlready; -#endif - - if (version >= 4) - { - // xfer the weaponSetFlags FIRST, since we need 'em to restore the weaponSet properly. (srj) - m_curWeaponSetFlags.xfer( xfer ); - xfer->xferUnsignedInt(&m_weaponBonusCondition); - xfer->xferUser(&m_lastWeaponCondition, sizeof(m_lastWeaponCondition)); - - // do the weaponSet itself after all the weapon-related stuff, just in case - xfer->xferSnapshot(&m_weaponSet); - - m_specialPowerBits.xfer( xfer ); - - xfer->xferAsciiString(&m_commandSetStringOverride); - - xfer->xferBool(&m_modulesReady); - } - - if (version >= 5) - { - xfer->xferBool(&m_isReceivingDifficultyBonus); - } - else - m_isReceivingDifficultyBonus = FALSE; - -} // end xfer - -//------------------------------------------------------------------------------------------------- -/** Object load game post process phase */ -//------------------------------------------------------------------------------------------------- -void Object::loadPostProcess() -{ - if( m_xferContainedByID != INVALID_ID ) - m_containedBy = TheGameLogic->findObjectByID(m_xferContainedByID); - else - m_containedBy = NULL; - -} // end loadPostProcess - -//------------------------------------------------------------------------------------------------- -/** Does this object have this upgrade */ -//------------------------------------------------------------------------------------------------- -Bool Object::hasUpgrade( const UpgradeTemplate *upgradeT ) const -{ - if( m_objectUpgradesCompleted.testForAll( upgradeT->getUpgradeMask() ) ) - { - return TRUE; - } - return FALSE; -} // end hasUpgrade - -//------------------------------------------------------------------------------------------------- -/** Is this object capable of having this upgrade */ -//------------------------------------------------------------------------------------------------- -Bool Object::affectedByUpgrade( const UpgradeTemplate *upgradeT ) const -{ - UpgradeMaskType objectMask = getObjectCompletedUpgradeMask(); - UpgradeMaskType playerMask = getControllingPlayer()->getCompletedUpgradeMask(); - UpgradeMaskType maskToCheck = playerMask; - maskToCheck.set( objectMask ); - maskToCheck.set( upgradeT->getUpgradeMask() ); - - // We need to add in all of the already owned upgrades to handle "AND" requiring upgrades. - // We combine all the masks in case someone has a Object AND Player combination - - for (BehaviorModule** module = m_behaviors; *module; ++module) - { - UpgradeModuleInterface* upgrade = (*module)->getUpgrade(); - if (!upgrade) - continue; - - if( upgrade->wouldUpgrade( maskToCheck ) ) - { - // if any of my many upgrade modules would execute in response to this flag, say yes. - return TRUE; - } - } - return FALSE; - -} // end affectedByUpgrade - -//------------------------------------------------------------------------------------------------- -/** Give this upgrade to this object */ -//------------------------------------------------------------------------------------------------- -void Object::giveUpgrade( const UpgradeTemplate *upgradeT ) -{ - if (upgradeT) - { - m_objectUpgradesCompleted.set( upgradeT->getUpgradeMask() ); - - // - // iterate through all the upgrade modules of this object and call the method to - // grant a new upgrade - // - updateUpgradeModules(); - } -} // end giveUpgrade - -//------------------------------------------------------------------------------------------------- -/** Remove this upgrade from this object */ -//------------------------------------------------------------------------------------------------- -void Object::removeUpgrade( const UpgradeTemplate *upgradeT ) -{ - m_objectUpgradesCompleted.clear( upgradeT->getUpgradeMask() ); - for (BehaviorModule** module = m_behaviors; *module; ++module) - { - UpgradeModuleInterface* upgrade = (*module)->getUpgrade(); - if (!upgrade) - continue; - - // Whoa, please note that while the function is called Object::RemoveUpgrade, it is not removing anything - // in the sense of undoing the effects. It is just resetting the upgrade so it may be run again. - upgrade->resetUpgrade( upgradeT->getUpgradeMask() ); - } -} - -//------------------------------------------------------------------------------------------------- -/** Central point for onCapture logic */ -//------------------------------------------------------------------------------------------------- -void Object::onCapture( Player *oldOwner, Player *newOwner ) -{ - // Everybody dhills when they captured so they don't keep doing something the new player might not want him to be doing - if( getAIUpdateInterface() && (oldOwner != newOwner) ) - getAIUpdateInterface()->aiIdle(CMD_FROM_AI); - - // this gets the new owner some points - newOwner->getScoreKeeper()->addObjectCaptured(this); - - // rip through the behavior modules and call the onCapture for any modules that care - for( BehaviorModule **module = m_behaviors; *module; ++module ) - (*module)->onCapture( oldOwner, newOwner ); - - // - // We have to undo our look for the old team and redo it for the new. - // onCapture is used now, so it better be called after ownership changes and not before. - // - handlePartitionCellMaintenance(); - - // Design needs the player to be able to sell buildings he steals from the AI's build list, and this is the - // easiest fix. The only snafu would be a key building build listed by the AI that the player can capture - // and the AI tries to capture back but needs to not sell. In that case, a Cinematic Unsellable version - // of the building needs to be made. This fix has been okayed as the most non-lethal in November. - clearScriptStatus(OBJECT_STATUS_SCRIPT_UNSELLABLE); - - // mark the command bar to redraw - TheControlBar->markUIDirty(); - - if (oldOwner!=newOwner && newOwner->isSkirmishAIPlayer()) { - // The skirmish ai doesn't know what to do with captured faction buildings except sell them. - if (isFactionStructure()) { - TheBuildAssistant->sellObject( this ); - } - } - -} // end onCapture - -//------------------------------------------------------------------------------------------------- -/// Object level events that need to happen upon game death -void Object::onDie( DamageInfo *damageInfo ) -{ - - checkAndDetonateBoobyTrap(NULL);// Already dying, so no need to handle death case of explosion - -#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) - DEBUG_ASSERTCRASH(m_hasDiedAlready == false, ("Object::onDie has been called multiple times. This is invalid. jkmcd")); - m_hasDiedAlready = true; -#endif - - Bool selfInflicted = (damageInfo->in.m_sourceID == getID()); - - // FIRST, call our die modules. - for (BehaviorModule** d = m_behaviors; *d; ++d) - { - DieModuleInterface* die = (*d)->getDie(); - if (die) - die->onDie(damageInfo); - } - - // When objects die we remove from the radar as they're really not interesting anymore - if( m_radarData ) - TheRadar->removeObject( this ); - - // Just in case I have been sporting one of thise fancy Terrain Decals, - //I naturally lose it now, because I'm dead. - Drawable *draw = getDrawable(); - if (draw) draw->setTerrainDecalFadeTarget(0.0f, -0.03f);//fade... - //if (draw) draw->setTerrainDecal(TERRAIN_DECAL_NONE);//pop! - - - // objects that were spawned from something, need to tell their spawner that they have died - Object* spawner = TheGameLogic->findObjectByID( getProducerID() ); - if( spawner ) - { - - // get the spawn behavior interface of the spawner - SpawnBehaviorInterface *spawnerBehavior = spawner->getSpawnBehaviorInterface(); - if( spawnerBehavior ) - spawnerBehavior->onSpawnDeath( getID(), damageInfo ); - - } - - handlePartitionCellMaintenance(); - if(m_team) - m_team->notifyTeamOfObjectDeath(); - - if (isLocallyControlled() && !selfInflicted) // wasLocallyControlled? :-) - { - if (isKindOf(KINDOF_STRUCTURE) && isKindOf(KINDOF_MP_COUNT_FOR_VICTORY)) - { - TheEva->setShouldPlay(EVA_BuldingLost); - } - else if (isKindOf(KINDOF_INFANTRY) || isKindOf(KINDOF_VEHICLE)) - { - TheEva->setShouldPlay(EVA_UnitLost); - //Create a fake radar event so the user can use the spacebar to quickly jump to this! - TheRadar->tryEvent( RADAR_EVENT_FAKE, getPosition() ); - } - } - - // This call won't do anything if we aren't actually in the list. - //Kris: Added NULL check to prevent crash with combat bikes & their riders getting deleted on exit. - if( getControllingPlayer() ) - { - TheInGameUI->removeIdleWorker( this, getControllingPlayer()->getPlayerIndex() ); - } - - //When a GLA hole is in the process of rebuilding, and that rebuild is lost, we need to - //tell anyone attacking it to transfer the attack to the hole that still exists. - if( testStatus( OBJECT_STATUS_RECONSTRUCTING ) ) - { - Object *hole = TheGameLogic->findObjectByID( getProducerID() ); - if( hole ) - { - // set the information in the hole about what to build - RebuildHoleBehaviorInterface *rhbi = RebuildHoleBehavior::getRebuildHoleBehaviorInterfaceFromObject( hole ); - - // sanity - DEBUG_ASSERTCRASH( rhbi, ("Object::onDie() - No Rebuild Hole Behavior interface on hole\n") ); - - // start the rebuild process - if( rhbi ) - { - rhbi->startRebuildProcess( getTemplate(), getID() ); - } - - //Transfer any attackers from the destroyed building to the hole. - for ( Object *obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() ) - { - AIUpdateInterface* ai = obj->getAI(); - if (!ai) - continue; - - ai->transferAttack( getID(), hole->getID() ); - } - } - } - -} - -//------------------------------------------------------------------------------------------------- -void Object::setWeaponBonusCondition(WeaponBonusConditionType wst) -{ - WeaponBonusConditionFlags oldCondition = m_weaponBonusCondition; - m_weaponBonusCondition |= (1 << wst); - - if( oldCondition != m_weaponBonusCondition ) - { - // Our weapon bonus just changed, so we need to immediately update our weapons - m_weaponSet.weaponSetOnWeaponBonusChange(this); - } -} - -//------------------------------------------------------------------------------------------------- -void Object::clearWeaponBonusCondition(WeaponBonusConditionType wst) -{ - WeaponBonusConditionFlags oldCondition = m_weaponBonusCondition; - m_weaponBonusCondition &= ~(1 << wst); - - if( oldCondition != m_weaponBonusCondition ) - { - // Our weapon bonus just changed, so we need to immediately update our weapons - m_weaponSet.weaponSetOnWeaponBonusChange(this); - } -} - -//------------------------------------------------------------------------------------------------- -/** - A weapon cannot be in charge of maintaining condition flags as it is all event driven. - I will maintain my ModelCondition myself if it should change. Firing is set by firing logic, - so I don't include it here. It is only the states that expire on timers that noone watches - that I am concerned with. -*/ -//------------------------------------------------------------------------------------------------- -void Object::adjustModelConditionForWeaponStatus() -{ - UnsignedInt now = TheGameLogic->getFrame(); - - for (int i = 0; i < WEAPONSLOT_COUNT; ++i) - { - const Weapon* w = m_weaponSet.getWeaponInWeaponSlot((WeaponSlotType)i); - if (!w) - { - m_lastWeaponCondition[i] = WSF_NONE; - continue; - } - - WeaponSetConditionType conditionToSet = WSF_INVALID; - if (i != m_weaponSet.getCurWeaponSlot()) - { - // if this isn't the current weapon, then we never set ANYTHING for it. - conditionToSet = WSF_NONE; - } - else if (w->getLastShotFrame() == now) - { - // yep, this overrides any weapon-status condition! - conditionToSet = WSF_FIRING; - } - else if (!testStatus( OBJECT_STATUS_IS_ATTACKING )) - { - // srj sez: not 100% sure about this one, but the problem is: say we were attacking, - // then issue a move command. if we didn't do this here, we might still have a 'firing' - // pose, because his weapon might be in 'reloading' mode. since we're not attacking, however, - // we really don't care, so we just force the issue here. (This might still need tweaking for the pursue state.) - conditionToSet = WSF_NONE; - } - else - { - WeaponStatus newStatus = w->getStatus(); - - const static WeaponSetConditionType s_wsfLookup[WEAPON_STATUS_COUNT] = - { - WSF_NONE, // READY_TO_FIRE, - WSF_NONE, // OUT_OF_AMMO, - WSF_BETWEEN, // BETWEEN_FIRING_SHOTS, - WSF_RELOADING, // RELOADING_CLIP, - WSF_PREATTACK // PRE_ATTACK, - }; - conditionToSet = s_wsfLookup[newStatus]; - - // special case this: say we are firing in bursts: pow-pow-pow-pause, etc. - // then we might have a frame where we have reloaded and are ready-to-fire, - // but haven't fired yet this frame. in that case, use 'between' so we still have - // a firing pose, 'cuz if we use 'none' we will 'pop' back to idle for a frame. (srj) - // additional note: only do if aiming or firing, since we could also be in this state if - // we are approaching or pursuing a target! (srj) - if (newStatus == READY_TO_FIRE && conditionToSet == WSF_NONE && testStatus( OBJECT_STATUS_IS_ATTACKING ) && - (testStatus( OBJECT_STATUS_IS_AIMING_WEAPON ) || testStatus( OBJECT_STATUS_IS_FIRING_WEAPON ))) - { - conditionToSet = WSF_BETWEEN; - } - - } - - if (m_drawable) - { - m_drawable->updateDrawableClipStatus( w->getRemainingAmmo(), w->getClipSize(), w->getWeaponSlot() ); - if (conditionToSet != WSF_INVALID && conditionToSet != m_lastWeaponCondition[i]) - { - m_lastWeaponCondition[i] = conditionToSet; - ModelConditionFlags c = m_weaponSet.getModelConditionForWeaponSlot((WeaponSlotType)i, conditionToSet); - m_drawable->clearAndSetModelConditionFlags(s_allWeaponFireFlags[i], c); - if (conditionToSet == WSF_PREATTACK) - { - // in the preattack state, adjust the speed of the preattack anim to match the actual time it will take - UnsignedInt preAttackDone = w->getPreAttackFinishedFrame(); - if (preAttackDone > now) - m_drawable->setAnimationLoopDuration(preAttackDone - now); - } - } - } - } -} - -//------------------------------------------------------------------------------------------------- -/// We have moved a 'significant' amount, so do maintenence that can be considered 'cell-based' -void Object::onPartitionCellChange() -{ - handlePartitionCellMaintenance(); -} - -//------------------------------------------------------------------------------------------------- -void Object::handlePartitionCellMaintenance() -{ - handleShroud(); - handleValueMap(); - handleThreatMap(); -} - -//------------------------------------------------------------------------------------------------- -void Object::handleShroud() -{ - // Undo last looking - unlook(); - // and shrouding - unshroud(); - - // redo shrouding - shroud(); - // Redo looking - look(); -} - -//------------------------------------------------------------------------------------------------- -void Object::handleValueMap() -{ - removeValue(); - addValue(); -} - -//------------------------------------------------------------------------------------------------- -void Object::handleThreatMap() -{ - removeThreat(); - addThreat(); -} - -//------------------------------------------------------------------------------------------------- -void Object::addValue() -{ - if( !m_partitionLastValue->isInvalid() ) - { - DEBUG_CRASH( ("An Object is adding value, but hasn't removed his previous value.") ); - return; - } - - if (!getControllingPlayer()) - return; - - if( getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) || isEffectivelyDead() || getShroudClearingRange() <= 0.0f ) - return; - - - m_partitionLastValue->m_where = *getPosition(); - m_partitionLastValue->m_data = getTemplate()->friend_getBuildCost(); - - m_partitionLastValue->m_forWhom = getControllingPlayer()->getPlayerMask(); - m_partitionLastValue->m_howFar = getVisionRange(); // we are valuable all the way to where we can target. - - ThePartitionManager->doValueAffect(m_partitionLastValue->m_where.x, - m_partitionLastValue->m_where.y, - m_partitionLastValue->m_howFar, - m_partitionLastValue->m_data, - m_partitionLastValue->m_forWhom - ); -} - -//------------------------------------------------------------------------------------------------- -void Object::removeValue() -{ - if( m_partitionLastValue->isInvalid() ) - { - // removing before adding is valid, cause we always remove before adding. (So the first remove - // will occur before the first add) - return; - } - - ThePartitionManager->undoValueAffect(m_partitionLastValue->m_where.x, - m_partitionLastValue->m_where.y, - m_partitionLastValue->m_howFar, - m_partitionLastValue->m_data, - m_partitionLastValue->m_forWhom - ); - - m_partitionLastValue->reset(); -} - -//------------------------------------------------------------------------------------------------- -void Object::addThreat() -{ - if( !m_partitionLastThreat->isInvalid() ) - { - DEBUG_CRASH( ("An Object is adding threat, but hasn't removed his previous threat. (He hasn't finished the threat?)") ); - return; - } - - if (!getControllingPlayer()) - return; - - if( getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) || isEffectivelyDead() || getShroudClearingRange() <= 0.0f ) - return; - - - m_partitionLastThreat->m_where = *getPosition(); - m_partitionLastThreat->m_data = getTemplate()->getThreatValue(); - - m_partitionLastThreat->m_forWhom = getControllingPlayer()->getPlayerMask(); - m_partitionLastThreat->m_howFar = getVisionRange(); // we are threatening all the way to where we can target. - - ThePartitionManager->doThreatAffect(m_partitionLastThreat->m_where.x, - m_partitionLastThreat->m_where.y, - m_partitionLastThreat->m_howFar, - m_partitionLastThreat->m_data, - m_partitionLastThreat->m_forWhom - ); -} - -//------------------------------------------------------------------------------------------------- -void Object::removeThreat() -{ - if( m_partitionLastThreat->isInvalid() ) - { - // removing before adding is valid, cause we always remove before adding. (So the first remove - // will occur before the first add) - return; - } - - ThePartitionManager->undoThreatAffect(m_partitionLastThreat->m_where.x, - m_partitionLastThreat->m_where.y, - m_partitionLastThreat->m_howFar, - m_partitionLastThreat->m_data, - m_partitionLastThreat->m_forWhom - ); - - m_partitionLastThreat->reset(); -} - - - -//------------------------------------------------------------------------------------------------- -void Object::look() -{ - if( ! m_partitionLastLook->isInvalid() ) - { - DEBUG_CRASH( ("An Object is looking, but hasn't unlooked the last one.") ); - return; - } - - Player* controller = getControllingPlayer(); - if ( controller ) - { - // I removed the check for objects under construction by request of designers since - // they want constructing objects to have a reduced sight range now. -MW - // dead or blind things don't reveal shroud - - - - // Some things get Destroyed directly without hitting Death. - if( !isDestroyed() && !isEffectivelyDead() ) - { - - ContainModuleInterface * contain = (getContainedBy() ? getContainedBy()->getContain() : NULL); - if ( contain && !contain->isGarrisonable() ) - return;// dont look, 'cause you are in a tunnel, now - // GS 10-20 Need to expand that exception to all transports or else you get a perma reveal where - // you entered the transport. Remember, this hackiness is caused by the fact that we never realized that - // garrisoned buildings weren't looking, we were just seeing the leftover last look of the guy inside. - // Otherwise we'd just have enclosingContainer control looking which is the 'correct' answer. - - Real shroudClearingRange = getShroudClearingRange(); - if( shroudClearingRange > 0.0f ) - { - PlayerMaskType lookingMask = 0; - - if ( isKindOf(KINDOF_REVEAL_TO_ALL) ) - { - lookingMask = PLAYERMASK_ALL; - } - else - { - for( Int currentIndex = ThePlayerList->getPlayerCount() - 1; currentIndex >=0; currentIndex-- ) - { - const Player *currentPlayer = ThePlayerList->getNthPlayer( currentIndex ); - - // Build mask of of allies who can see me. - // This is the Object-centric game level that cares - if( getControllingPlayer()->getRelationship( currentPlayer->getDefaultTeam() ) == ALLIES ) - { - lookingMask |= currentPlayer->getPlayerMask(); - } - } - - // Other players can also be looking through our eyes. - lookingMask |= m_visionSpiedMask; - } - - Coord3D pos = *getPosition(); - ThePartitionManager->doShroudReveal( pos.x, pos.y, shroudClearingRange, lookingMask ); - - m_partitionLastLook->m_where = pos; - m_partitionLastLook->m_forWhom = lookingMask; - m_partitionLastLook->m_howFar = getShroudClearingRange(); - - // DEBUG_LOG(( "A %s looks at %f, %f for %x at range %f\n", - // getTemplate()->getName().str(), - // pos.x, - // pos.y, - // lookingMask, - // getShroudClearingRange() - // )); - } - - //Now reveal to everyone if we're special. Note this works differently than KINDOF_REVEAL_TO_ALL because - //the kindof uses the same range as allies, spies, and owners would see. This template based shroud - //reveal to all range can specify a different value so we can get a much smaller reveal distance. - // And don't reveal while under construction. When finished, a refresh occurs, so don't worry. - Real shroudRevealToAllRange = getTemplate()->getShroudRevealToAllRange(); - if( shroudRevealToAllRange > 0.0f && !testStatus( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) - { - //Kris: August 20, 2003 - //Seeing I added this concept, I'm changing it now to only reveal to all when the unit is visible. If it's stealthed, - //we won't reveal it anymore (stealth general scudstorm). - Bool stealthedAndNotDetected = testStatus( OBJECT_STATUS_STEALTHED ) && !testStatus( OBJECT_STATUS_DETECTED ) && !testStatus( OBJECT_STATUS_DISGUISED ); - if( !stealthedAndNotDetected ) - { - Coord3D pos = *getPosition(); - PlayerMaskType thePlayersMask = ThePlayerList->getPlayersWithRelationship( getControllingPlayer()->getPlayerIndex(), ALLOW_ENEMIES | ALLOW_NEUTRAL ); - ThePartitionManager->doShroudReveal( pos.x, pos.y, shroudRevealToAllRange, thePlayersMask ); - m_partitionRevealAllLastLook->m_where = pos; - m_partitionRevealAllLastLook->m_forWhom = thePlayersMask; - m_partitionRevealAllLastLook->m_howFar = shroudRevealToAllRange; - } - } - } - } -} - -//------------------------------------------------------------------------------------------------- -void Object::unlook() -{ - if( m_partitionLastLook->isInvalid() ) - { - // Your very first action will be an unlook, so of course you haven't looked yet. This is not an error - // This early return prevents an extra unlook if you never looked. Like you have 0 vision. - return; - } - - ThePartitionManager->queueUndoShroudReveal(m_partitionLastLook->m_where.x, - m_partitionLastLook->m_where.y, - m_partitionLastLook->m_howFar, - m_partitionLastLook->m_forWhom - ); - -// DEBUG_LOG(( "A %s queues an unlook at %f, %f for %x at range %f\n", -// getTemplate()->getName().str(), -// m_partitionLastLook.m_where.x, -// m_partitionLastLook.m_where.y, -// m_partitionLastLook.m_forWhom, -// m_partitionLastLook.m_howFar -// )); - - m_partitionLastLook->reset(); - - if( !m_partitionRevealAllLastLook->isInvalid() ) - { - ThePartitionManager->queueUndoShroudReveal(m_partitionRevealAllLastLook->m_where.x, - m_partitionRevealAllLastLook->m_where.y, - m_partitionRevealAllLastLook->m_howFar, - m_partitionRevealAllLastLook->m_forWhom - ); - - m_partitionRevealAllLastLook->reset(); - } -} - -//------------------------------------------------------------------------------------------------- -void Object::shroud() -{ - if( ! m_partitionLastShroud->isInvalid() ) - { - DEBUG_CRASH( ("An Object is shrouding, but hasn't unshrouded the last one.") ); - return; - } - - Player* controller = getControllingPlayer(); - if ( controller ) - { - // things under construction don't shroud. (srj), nor do dead or blind things - if( !getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) && !isEffectivelyDead() && getShroudRange() > 0.0f ) - { - PlayerMaskType shroudingMask = 0; - for( Int currentIndex = ThePlayerList->getPlayerCount() - 1; currentIndex >=0; currentIndex-- ) - { - const Player *currentPlayer = ThePlayerList->getNthPlayer( currentIndex ); - //Build mask of NON-allies. This is the Object-centric game level that cares - if( getControllingPlayer()->getRelationship( currentPlayer->getDefaultTeam() ) != ALLIES ) - { - shroudingMask |= currentPlayer->getPlayerMask(); - } - } - - Coord3D pos = *getPosition(); - ThePartitionManager->doShroudCover(pos.x, pos.y, - getShroudRange(), - shroudingMask); - - m_partitionLastShroud->m_where = pos; - m_partitionLastShroud->m_forWhom = shroudingMask; - m_partitionLastShroud->m_howFar = getShroudRange(); - } - } -} - -//------------------------------------------------------------------------------------------------- -void Object::unshroud() -{ - if( m_partitionLastShroud->isInvalid() ) - { - // Your very first action will be an unlook, so of course you haven't looked yet. This is not an error - // This early return prevents an extra unlook if you never looked. Like you have 0 shroud generation. - return; - } - - ThePartitionManager->undoShroudCover(m_partitionLastShroud->m_where.x, - m_partitionLastShroud->m_where.y, - m_partitionLastShroud->m_howFar, - m_partitionLastShroud->m_forWhom); - - m_partitionLastShroud->reset(); -} - -//------------------------------------------------------------------------------------------------- -Real Object::getVisionRange() const -{ -#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) - if (TheGlobalData->m_debugVisibility) - { - Vector3 pos(m_visionRange, 0, 0); - for (int i = 0; i < TheGlobalData->m_debugVisibilityTileCount; ++i) - { - pos.Rotate_Z(1.0f * i / TheGlobalData->m_debugVisibilityTileCount * 2 * PI); - Coord3D coord = { pos.X + getPosition()->x, pos.Y + getPosition()->y, pos.Z + getPosition()->z }; - - addIcon(&coord, TheGlobalData->m_debugVisibilityTileWidth, - TheGlobalData->m_debugVisibilityTileDuration, - TheGlobalData->m_debugVisibilityTargettableColor); - } - } -#endif - return m_visionRange; -} - -//------------------------------------------------------------------------------------------------- -void Object::setVisionRange( Real newVisionRange ) -{ - m_visionRange = newVisionRange; -} - -//------------------------------------------------------------------------------------------------- -Real Object::getShroudClearingRange() const -{ - Real shroudClearingRange=m_shroudClearingRange; - - if( getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) - { - //structures under construction have limited vision range. For now, base it - //on the geometry extents so the structure can only see itself. - shroudClearingRange = getGeometryInfo().getBoundingCircleRadius(); - } - -#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) - if (TheGlobalData->m_debugVisibility) - { - Vector3 pos(shroudClearingRange, 0, 0); - for (int i = 0; i < TheGlobalData->m_debugVisibilityTileCount; ++i) - { - pos.Rotate_Z(1.0f * i / TheGlobalData->m_debugVisibilityTileCount * 2 * PI); - Coord3D coord = { pos.X + getPosition()->x, pos.Y + getPosition()->y, pos.Z + getPosition()->z }; - - addIcon(&coord, TheGlobalData->m_debugVisibilityTileWidth, - TheGlobalData->m_debugVisibilityTileDuration, - TheGlobalData->m_debugVisibilityDeshroudColor); - } - } -#endif - - return shroudClearingRange; -} - -//------------------------------------------------------------------------------------------------- -void Object::setShroudClearingRange( Real newShroudClearingRange ) -{ - if( newShroudClearingRange != m_shroudClearingRange ) - { - // The partition cell refresh is a slow operation, so only do it if you really have to. - // Range change is a valid reason to relook. - m_shroudClearingRange = newShroudClearingRange; - - /* - Complete and total monkey hack fix. - - The problem: newObject doesn't get an initial pos, so all objects start at 0,0,0. - Most code paths instantly move 'em to a good pos, but in some cases, that is too late: - If we have search-and-destroy battle plan, we will apply it at that point, and clear out - a vision range based on our current (wrong) location. Doh! - - So, this just sez: if you are at 0,0,0, don't call handlePartitionCellMaintenance()... since - you will either (1) be moved elsewhere immediately, thus forcing it to be called via - another route anyway, or (2) not be moved, which means you are a very naughty and worthless - object anyway and we should just ignore you. - - Proper fix for next version is to require initial pos to be passed in to newObject so that - all objects can start at their proper initial position from the start of the ctor. - - (srj) - */ - const Coord3D* pos = getPosition(); - if (pos->x || pos->y || pos->z) - { - handlePartitionCellMaintenance(); - } - } -} - -//------------------------------------------------------------------------------------------------- -Real Object::getShroudRange() const -{ -#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) - if (TheGlobalData->m_debugVisibility) - { - Vector3 pos(m_shroudRange, 0, 0); - for (int i = 0; i < TheGlobalData->m_debugVisibilityTileCount; ++i) - { - pos.Rotate_Z(1.0f * i / TheGlobalData->m_debugVisibilityTileCount * 2 * PI); - Coord3D coord = { pos.X + getPosition()->x, pos.Y + getPosition()->y, pos.Z + getPosition()->z }; - - addIcon(&coord, TheGlobalData->m_debugVisibilityTileWidth, - TheGlobalData->m_debugVisibilityTileDuration, - TheGlobalData->m_debugVisibilityGapColor); - } - } -#endif - - return m_shroudRange; -} - -//------------------------------------------------------------------------------------------------- -void Object::setShroudRange( Real newShroudRange ) -{ - m_shroudRange = newShroudRange; -} - -//------------------------------------------------------------------------------------------------- -void Object::setVisionSpied(Bool setting, Int byWhom) -{ - Bool needRefresh = FALSE; // If this setting is an edge trigger on the reference count, I need to refresh - - if( setting ) - { - m_visionSpiedBy[ byWhom ] = m_visionSpiedBy[ byWhom ] + 1; - if( m_visionSpiedBy[ byWhom ] == 1 ) - needRefresh = TRUE; - } - else - { - m_visionSpiedBy[ byWhom ] = m_visionSpiedBy[ byWhom ] - 1; - if( m_visionSpiedBy[ byWhom ] == 0 ) - needRefresh = TRUE; - } - - if( needRefresh ) - { - PlayerMaskType workingMask = 0; - for (Int i = 0; i < MAX_PLAYER_COUNT; ++i) - { - if( m_visionSpiedBy[i] > 0 ) - BitSet( workingMask, ( 1 << i ) ); - else - BitClear( workingMask, ( 1 << i ) ); - } - - m_visionSpiedMask = workingMask; - - handlePartitionCellMaintenance(); - } -} - -//------------------------------------------------------------------------------------------------- -void Object::doStatusDamage( ObjectStatusTypes status, Real duration ) -{ - if(m_statusDamageHelper) - m_statusDamageHelper->doStatusDamage(status, duration); -} - -//------------------------------------------------------------------------------------------------- -void Object::doTempWeaponBonus( WeaponBonusConditionType status, UnsignedInt duration, TintStatus tintStatus) -{ - if(m_tempWeaponBonusHelper) - m_tempWeaponBonusHelper->doTempWeaponBonus(status, duration, tintStatus); -} - -//------------------------------------------------------------------------------------------------- -void Object::notifySubdualDamage( Real amount ) -{ - if(m_subdualDamageHelper) - m_subdualDamageHelper->notifySubdualDamage( amount ); - - // If we are gaining subdual damage, we are slowly tinting - if( getDrawable() ) - { - if( amount > 0 ) - getDrawable()->setTintStatus(TINT_STATUS_GAINING_SUBDUAL_DAMAGE); - else - getDrawable()->clearTintStatus(TINT_STATUS_GAINING_SUBDUAL_DAMAGE); - } -} - -//------------------------------------------------------------------------------------------------- -void Object::notifyChronoDamage(Real amount) -{ - if (m_chronoDamageHelper) - m_chronoDamageHelper->notifyChronoDamage(amount); - - //Real progress = INT_TO_REAL(now - m_dieFrame) / INT_TO_REAL(m_destructionFrame - m_dieFrame); - - BodyModuleInterface* body = getBodyModule(); - Drawable* draw = getDrawable(); - if (body != NULL && draw != NULL) { - - Real chronoTh = TheGlobalData->m_chronoDamageDisableThreshold * body->getMaxHealth(); - Real chronoDmg = body->getCurrentChronoDamageAmount(); - if (chronoDmg > chronoTh) { - Real progress = (chronoDmg - chronoTh) / (body->getMaxHealth() - chronoTh); - progress = min(1.0f, max(0.0f, progress)); - - Real alpha0 = TheGlobalData->m_chronoDisableAlphaStart; - Real alpha1 = TheGlobalData->m_chronoDisableAlphaEnd; - Real opacity = (1.0 - progress) * alpha0 + progress * alpha1; - - // DEBUG_LOG(("Object::notifyChronoDamage - progress = %f, alpha = %f\n", progress, opacity)); - - draw->setDrawableOpacity(opacity); - //draw->setEffectiveOpacity(opacity); - //draw->setSecondMaterialPassOpacity(opacity); - - } - else if (amount < 0) { - draw->setDrawableOpacity(1.0); - // DEBUG_LOG(("Object::notifyChronoDamage - reset opacity\n")); - } - } - - //If we are gaining chrono damage, we are slowly tinting - if (getDrawable()) - { - if (amount > 0) - getDrawable()->setTintStatus(TINT_STATUS_GAINING_CHRONO_DAMAGE); - else - getDrawable()->clearTintStatus(TINT_STATUS_GAINING_CHRONO_DAMAGE); - } -} - -//------------------------------------------------------------------------------------------------- -/** Given a special power template, find the module in the object that can implement it. - * There can be at most one */ -//------------------------------------------------------------------------------------------------- -SpecialPowerModuleInterface *Object::getSpecialPowerModule( const SpecialPowerTemplate *specialPowerTemplate ) const -{ - - // sanity - if( specialPowerTemplate == NULL ) - return NULL; - - // search the modules for the one with the matching template - for( BehaviorModule** m = m_behaviors; *m; ++m ) - { - SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); - if (!sp) - continue; - - if( sp->isModuleForPower( specialPowerTemplate ) ) - return sp; - } - - return NULL; - -} - -//------------------------------------------------------------------------------------------------- -/** Execute special power */ -//------------------------------------------------------------------------------------------------- -void Object::doSpecialPower( const SpecialPowerTemplate *specialPowerTemplate, UnsignedInt commandOptions, Bool forced ) -{ - - if (isDisabled()) - return; - - // sanity - if( !forced && TheSpecialPowerStore->canUseSpecialPower( this, specialPowerTemplate ) == FALSE ) - return; - - // get the module and execute - SpecialPowerModuleInterface *mod = getSpecialPowerModule( specialPowerTemplate ); - if( mod ) - mod->doSpecialPower( commandOptions ); - -} - -//------------------------------------------------------------------------------------------------- -/** Execute special power */ -//------------------------------------------------------------------------------------------------- -void Object::doSpecialPowerAtObject( const SpecialPowerTemplate *specialPowerTemplate, Object *obj, UnsignedInt commandOptions, Bool forced ) -{ - - if (isDisabled()) - return; - - // sanity - if( !forced && TheSpecialPowerStore->canUseSpecialPower( this, specialPowerTemplate ) == FALSE ) - return; - - // get the module and execute - SpecialPowerModuleInterface *mod = getSpecialPowerModule( specialPowerTemplate ); - if( mod ) - mod->doSpecialPowerAtObject( obj, commandOptions ); -} - -//------------------------------------------------------------------------------------------------- -/** Execute special power */ -//------------------------------------------------------------------------------------------------- -void Object::doSpecialPowerAtLocation( const SpecialPowerTemplate *specialPowerTemplate, - const Coord3D *loc, Real angle, UnsignedInt commandOptions, Bool forced ) -{ - - if (isDisabled()) - return; - - // sanity - if( !forced && TheSpecialPowerStore->canUseSpecialPower( this, specialPowerTemplate ) == FALSE ) - return; - - // get the module and execute - SpecialPowerModuleInterface *mod = getSpecialPowerModule( specialPowerTemplate ); - if( mod ) - mod->doSpecialPowerAtLocation( loc, angle, commandOptions ); - -} - -//------------------------------------------------------------------------------------------------- -/** Execute special power */ -//------------------------------------------------------------------------------------------------- -void Object::doSpecialPowerUsingWaypoints( const SpecialPowerTemplate *specialPowerTemplate, const Waypoint *way, UnsignedInt commandOptions, Bool forced ) -{ - - if (isDisabled()) - return; - - // sanity - if( !forced && TheSpecialPowerStore->canUseSpecialPower( this, specialPowerTemplate ) == FALSE ) - return; - - // get the module and execute - SpecialPowerModuleInterface *mod = getSpecialPowerModule( specialPowerTemplate ); - if( mod ) - mod->doSpecialPowerUsingWaypoints( way, commandOptions ); - -} - -//------------------------------------------------------------------------------------------------- -/** Execute command button ability */ -//------------------------------------------------------------------------------------------------- -void Object::doCommandButton( const CommandButton *commandButton, CommandSourceType cmdSource ) -{ - if (isDisabled()) - return; - - AIUpdateInterface *ai = getAIUpdateInterface(); - if( commandButton ) - { - switch( commandButton->getCommandType() ) - { - case GUI_COMMAND_SPECIAL_POWER: - if( commandButton->getSpecialPowerTemplate() ) - { - CommandOption commandOptions = (CommandOption)(commandButton->getOptions() | COMMAND_FIRED_BY_SCRIPT); - doSpecialPower( commandButton->getSpecialPowerTemplate(), commandOptions, cmdSource == CMD_FROM_SCRIPT ); - return; - } - break; - case GUI_COMMAND_STOP: - if( ai ) - { - ai->aiIdle( cmdSource ); - return; - } - break; - - case GUI_COMMAND_SWITCH_WEAPON: - { - WeaponSlotType weaponSlot = commandButton->getWeaponSlot(); - // GUI_COMMAND_SWITCH_WEAPON switches until un-switched, or switched to something else. - setWeaponLock( weaponSlot, LOCKED_PERMANENTLY ); - return; - } - - case GUI_COMMAND_FIRE_WEAPON: - if( ai ) - { - if( !BitIsSet( commandButton->getOptions(), COMMAND_OPTION_NEED_OBJECT_TARGET ) && !BitIsSet( commandButton->getOptions(), NEED_TARGET_POS ) ) - { - setWeaponLock( commandButton->getWeaponSlot(), LOCKED_TEMPORARILY ); - //LOCATION BASED FIRE WEAPON - ai->aiAttackPosition( NULL, commandButton->getMaxShotsToFire(), cmdSource ); - } - else - { - DEBUG_CRASH( ("WARNING: Script doCommandButton for button %s cannot fire weapon with NO POSITION. Skipping.", commandButton->getName().str()) ); - } - return; - } - break; - - case GUI_COMMAND_OBJECT_UPGRADE: - case GUI_COMMAND_PLAYER_UPGRADE: - { - const UpgradeTemplate *upgradeT = commandButton->getUpgradeTemplate(); - DEBUG_ASSERTCRASH( upgradeT, ("Undefined upgrade '%s' in player upgrade command\n", "UNKNOWN") ); - // sanity - if( upgradeT == NULL ) - break; - if( upgradeT->getUpgradeType() == UPGRADE_TYPE_OBJECT ) - { - if( hasUpgrade( upgradeT ) || !affectedByUpgrade( upgradeT ) ) - break; - } - // producer must have a production update - ProductionUpdateInterface *pu = getProductionUpdateInterface(); - if( pu == NULL ) - break; - // queue the upgrade "research" - pu->queueUpgrade( upgradeT ); - } - return; - case GUI_COMMAND_UNIT_BUILD: - case GUI_COMMAND_DOZER_CONSTRUCT: { - const ThingTemplate *tt = commandButton->getThingTemplate(); - ProductionUpdateInterface *pu = this->getProductionUpdateInterface(); - if (pu && tt) { - pu->queueCreateUnit( tt, pu->requestUniqueUnitID()); - return; - } - break; - } - case GUI_COMMAND_HACK_INTERNET:{ - if( ai ) - { - ai->aiHackInternet( cmdSource ); - return; - } - break; - } - - case GUI_COMMAND_SELL: - TheBuildAssistant->sellObject( this ); - return; - - //Feel free to implement object based command buttons. - case GUI_COMMAND_COMBATDROP: - case GUI_COMMAND_DOZER_CONSTRUCT_CANCEL: - case GUI_COMMAND_CANCEL_UNIT_BUILD: - case GUI_COMMAND_CANCEL_UPGRADE: - case GUI_COMMAND_ATTACK_MOVE: - case GUI_COMMAND_GUARD: - case GUI_COMMAND_GUARD_WITHOUT_PURSUIT: - case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY: - case GUI_COMMAND_WAYPOINTS: - case GUI_COMMAND_EXIT_CONTAINER: - case GUI_COMMAND_EVACUATE: - case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT: - case GUI_COMMAND_BEACON_DELETE: - case GUI_COMMAND_SET_RALLY_POINT: - case GUI_COMMAND_TOGGLE_OVERCHARGE: -#ifdef ALLOW_SURRENDER - case GUI_COMMAND_POW_RETURN_TO_PRISON: -#endif - case GUICOMMANDMODE_HIJACK_VEHICLE: - case GUICOMMANDMODE_CONVERT_TO_CARBOMB: -#ifdef ALLOW_SURRENDER - case GUICOMMANDMODE_PICK_UP_PRISONER: -#endif - default: - break; - } - DEBUG_CRASH( ("WARNING: Script doCommandButton for button %s not implemented. Doing nothing.", commandButton->getName().str()) ); - } -} - -//------------------------------------------------------------------------------------------------- -/** Execute command button ability directed at an object target */ -//------------------------------------------------------------------------------------------------- -void Object::doCommandButtonAtObject( const CommandButton *commandButton, Object *obj, CommandSourceType cmdSource ) -{ - if (isDisabled()) - return; - - AIUpdateInterface *ai = getAIUpdateInterface(); - if( commandButton ) - { - switch( commandButton->getCommandType() ) - { - case GUI_COMMAND_COMBATDROP: - if( ai ) - { - ai->aiCombatDrop( obj, *(obj->getPosition()), cmdSource ); - } - return; - case GUI_COMMAND_SPECIAL_POWER: - { - if( commandButton->getSpecialPowerTemplate() ) - { - CommandOption commandOptions = (CommandOption)(commandButton->getOptions() | COMMAND_FIRED_BY_SCRIPT); - doSpecialPowerAtObject( commandButton->getSpecialPowerTemplate(), obj, commandOptions, cmdSource == CMD_FROM_SCRIPT ); - } - return; - } - - case GUI_COMMAND_STOP: - if( ai ) - { - ai->aiIdle( cmdSource ); - } - return; - - case GUI_COMMAND_FIRE_WEAPON: - if( ai ) - { - if( BitIsSet( commandButton->getOptions(), COMMAND_OPTION_NEED_OBJECT_TARGET ) ) - { - //OBJECT BASED FIRE WEAPON - if( !obj ) - { - break; - } - - if( !commandButton->isValidObjectTarget( this, obj ) ) - { - break; - } - - setWeaponLock( commandButton->getWeaponSlot(), LOCKED_TEMPORARILY ); - - if( BitIsSet( commandButton->getOptions(), ATTACK_OBJECTS_POSITION ) ) - { - //Actually, you know what.... we want to attack the object's location instead. - ai->aiAttackPosition( obj->getPosition(), commandButton->getMaxShotsToFire(), cmdSource ); - } - else - { - ai->aiAttackObject( obj, commandButton->getMaxShotsToFire(), cmdSource ); - } - } - else - { - DEBUG_CRASH( ("WARNING: Script doCommandButtonAtObject for button %s cannot fire weapon at AN OBJECT. Skipping.", commandButton->getName().str()) ); - } - return; - } - break; - - case GUICOMMANDMODE_HIJACK_VEHICLE: - case GUICOMMANDMODE_CONVERT_TO_CARBOMB: - case GUICOMMANDMODE_SABOTAGE_BUILDING: - if( ai ) - { - ai->aiEnter( obj, cmdSource ); - } - return; - - //Feel free to implement object based command buttons. - case GUI_COMMAND_DOZER_CONSTRUCT: - case GUI_COMMAND_DOZER_CONSTRUCT_CANCEL: - case GUI_COMMAND_UNIT_BUILD: - case GUI_COMMAND_CANCEL_UNIT_BUILD: - case GUI_COMMAND_PLAYER_UPGRADE: - case GUI_COMMAND_OBJECT_UPGRADE: - case GUI_COMMAND_CANCEL_UPGRADE: - case GUI_COMMAND_ATTACK_MOVE: - case GUI_COMMAND_GUARD: - case GUI_COMMAND_GUARD_WITHOUT_PURSUIT: - case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY: - case GUI_COMMAND_WAYPOINTS: - case GUI_COMMAND_EXIT_CONTAINER: - case GUI_COMMAND_EVACUATE: - case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT: - case GUI_COMMAND_BEACON_DELETE: - case GUI_COMMAND_SET_RALLY_POINT: - case GUI_COMMAND_SELL: - case GUI_COMMAND_HACK_INTERNET: - case GUI_COMMAND_TOGGLE_OVERCHARGE: - case GUI_COMMAND_SWITCH_WEAPON: - -#ifdef ALLOW_SURRENDER - case GUI_COMMAND_POW_RETURN_TO_PRISON: - case GUICOMMANDMODE_PICK_UP_PRISONER: -#endif - default: - break; - } - DEBUG_CRASH( ("WARNING: Script doCommandButtonAtObject for button %s not implemented. Doing nothing.", commandButton->getName().str()) ); - } -} - -//------------------------------------------------------------------------------------------------- -/** Execute command button ability directed at a location */ -//------------------------------------------------------------------------------------------------- -void Object::doCommandButtonAtPosition( const CommandButton *commandButton, const Coord3D *pos, CommandSourceType cmdSource ) -{ - if (isDisabled()) - return; - - AIUpdateInterface *ai = getAIUpdateInterface(); - if( commandButton ) - { - switch( commandButton->getCommandType() ) - { - case GUI_COMMAND_SPECIAL_POWER: - { - if( commandButton->getSpecialPowerTemplate() ) - { - CommandOption commandOptions = (CommandOption)(commandButton->getOptions() | COMMAND_FIRED_BY_SCRIPT); - doSpecialPowerAtLocation( commandButton->getSpecialPowerTemplate(), pos, INVALID_ANGLE, commandOptions, cmdSource == CMD_FROM_SCRIPT ); - return; - } - break; - } - case GUI_COMMAND_ATTACK_MOVE: - if( ai ) - { - ai->aiAttackMoveToPosition( pos, commandButton->getMaxShotsToFire(), cmdSource ); - return; - } - break; - case GUI_COMMAND_STOP: - if( ai ) - { - ai->aiIdle( cmdSource ); - return; - } - break; - - case GUI_COMMAND_DOZER_CONSTRUCT: - TheBuildAssistant->buildObjectNow( this, commandButton->getThingTemplate(), pos, 0.0f, getControllingPlayer() ); - return; - - case GUI_COMMAND_FIRE_WEAPON: - if( ai ) - { - if( BitIsSet( commandButton->getOptions(), NEED_TARGET_POS ) ) - { - //LOCATION BASED FIRE WEAPON - if( !pos ) - { - break; - } - setWeaponLock( commandButton->getWeaponSlot(), LOCKED_TEMPORARILY ); - ai->aiAttackPosition( pos, commandButton->getMaxShotsToFire(), cmdSource ); - } - else - { - DEBUG_CRASH( ("WARNING: Script doCommandButtonAtPosition for button %s cannot fire weapon at A POSITION. Skipping.", commandButton->getName().str()) ); - } - return; - } - break; - - case GUI_COMMAND_DOZER_CONSTRUCT_CANCEL: - case GUI_COMMAND_UNIT_BUILD: - case GUI_COMMAND_CANCEL_UNIT_BUILD: - case GUI_COMMAND_PLAYER_UPGRADE: - case GUI_COMMAND_OBJECT_UPGRADE: - case GUI_COMMAND_CANCEL_UPGRADE: - case GUI_COMMAND_GUARD: - case GUI_COMMAND_GUARD_WITHOUT_PURSUIT: - case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY: - case GUI_COMMAND_WAYPOINTS: - case GUI_COMMAND_EXIT_CONTAINER: - case GUI_COMMAND_EVACUATE: - case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT: - case GUI_COMMAND_BEACON_DELETE: - case GUI_COMMAND_SET_RALLY_POINT: - case GUI_COMMAND_SELL: - case GUI_COMMAND_HACK_INTERNET: - case GUI_COMMAND_TOGGLE_OVERCHARGE: -#ifdef ALLOW_SURRENDER - case GUI_COMMAND_POW_RETURN_TO_PRISON: -#endif - case GUI_COMMAND_COMBATDROP: - case GUI_COMMAND_SWITCH_WEAPON: - case GUICOMMANDMODE_HIJACK_VEHICLE: - case GUICOMMANDMODE_CONVERT_TO_CARBOMB: -#ifdef ALLOW_SURRENDER - case GUICOMMANDMODE_PICK_UP_PRISONER: -#endif - default: - break; - } - DEBUG_CRASH( ("WARNING: Script doCommandButtonAtPosition for button %s not implemented. Doing nothing.", commandButton->getName().str()) ); - } -} - -//------------------------------------------------------------------------------------------------- -/** Execute command button ability directed at a location */ -//------------------------------------------------------------------------------------------------- -void Object::doCommandButtonUsingWaypoints( const CommandButton *commandButton, const Waypoint *way, CommandSourceType cmdSource ) -{ - if (isDisabled()) - return; - - if( commandButton ) - { - if( !BitIsSet( commandButton->getOptions(), CAN_USE_WAYPOINTS ) ) - { - //Our button doesn't support waypoints. - DEBUG_CRASH( ("WARNING: Script doCommandButtonUsingWaypoints for button %s lacks CAN_USE_WAYPOINTS option. Doing nothing.", commandButton->getName().str()) ); - return; - } - switch( commandButton->getCommandType() ) - { - case GUI_COMMAND_SPECIAL_POWER: - { - if( commandButton->getSpecialPowerTemplate() ) - { - CommandOption commandOptions = (CommandOption)(commandButton->getOptions() | COMMAND_FIRED_BY_SCRIPT); - doSpecialPowerUsingWaypoints( commandButton->getSpecialPowerTemplate(), way, commandOptions, cmdSource == CMD_FROM_SCRIPT ); - return; - } - break; - } - case GUI_COMMAND_ATTACK_MOVE: - case GUI_COMMAND_STOP: - case GUI_COMMAND_DOZER_CONSTRUCT: - case GUI_COMMAND_DOZER_CONSTRUCT_CANCEL: - case GUI_COMMAND_UNIT_BUILD: - case GUI_COMMAND_CANCEL_UNIT_BUILD: - case GUI_COMMAND_PLAYER_UPGRADE: - case GUI_COMMAND_OBJECT_UPGRADE: - case GUI_COMMAND_CANCEL_UPGRADE: - case GUI_COMMAND_GUARD: - case GUI_COMMAND_GUARD_WITHOUT_PURSUIT: - case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY: - case GUI_COMMAND_WAYPOINTS: - case GUI_COMMAND_EXIT_CONTAINER: - case GUI_COMMAND_EVACUATE: - case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT: - case GUI_COMMAND_BEACON_DELETE: - case GUI_COMMAND_SET_RALLY_POINT: - case GUI_COMMAND_SELL: - case GUI_COMMAND_FIRE_WEAPON: - case GUI_COMMAND_HACK_INTERNET: - case GUI_COMMAND_TOGGLE_OVERCHARGE: -#ifdef ALLOW_SURRENDER - case GUI_COMMAND_POW_RETURN_TO_PRISON: -#endif - case GUI_COMMAND_COMBATDROP: - case GUI_COMMAND_SWITCH_WEAPON: - case GUICOMMANDMODE_HIJACK_VEHICLE: - case GUICOMMANDMODE_CONVERT_TO_CARBOMB: -#ifdef ALLOW_SURRENDER - case GUICOMMANDMODE_PICK_UP_PRISONER: -#endif - default: - break; - } - DEBUG_CRASH( ("WARNING: Script doCommandButtonUsingWaypoints for button %s not implemented. Doing nothing.", commandButton->getName().str()) ); - } -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void Object::clearLeechRangeModeForAllWeapons() -{ - m_weaponSet.clearLeechRangeModeForAllWeapons(); -} - -// ------------------------------------------------------------------------------------------------ -/** Search our update modules for a production update interface and return it if one is found */ -// ------------------------------------------------------------------------------------------------ -ProductionUpdateInterface* Object::getProductionUpdateInterface( void ) -{ - ProductionUpdateInterface *pui; - - // tell our update modules that we intend to do this special power. - for( BehaviorModule** u = m_behaviors; *u; ++u ) - { - - pui = (*u)->getProductionUpdateInterface(); - if( pui ) - return pui; - - } // end for - - return NULL; - -} // end getProductionUpdateInterface - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -DockUpdateInterface *Object::getDockUpdateInterface( void ) -{ - DockUpdateInterface *dock = NULL; - - for( BehaviorModule **u = m_behaviors; *u; ++u ) - { - if( (dock = (*u)->getDockUpdateInterface()) != NULL ) - return dock; - } - - return NULL; - -} // end getDockUpdateInterface - -// ------------------------------------------------------------------------------------------------ -// Search our special power modules for a specific one. -// ------------------------------------------------------------------------------------------------ -SpecialPowerModuleInterface* Object::findSpecialPowerModuleInterface( SpecialPowerType type ) const -{ - for (BehaviorModule** m = m_behaviors; *m; ++m) - { - SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); - if (!sp) - continue; - - const SpecialPowerTemplate *spTemplate = sp->getSpecialPowerTemplate(); - if (spTemplate && spTemplate->getSpecialPowerType() == type || type == SPECIAL_INVALID ) - { - return sp; - } - } - return NULL; -} - -// ------------------------------------------------------------------------------------------------ -// Search our special power modules for the first occurrence of a shortcut special. -// ------------------------------------------------------------------------------------------------ -SpecialPowerModuleInterface* Object::findAnyShortcutSpecialPowerModuleInterface() const -{ - for( BehaviorModule** m = m_behaviors; *m; ++m ) - { - SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); - if (!sp) - continue; - - const SpecialPowerTemplate *spTemplate = sp->getSpecialPowerTemplate(); - if( spTemplate && spTemplate->isShortcutPower() ) - { - return sp; - } - } - return NULL; -} - -// ------------------------------------------------------------------------------------------------ -/** Get spawn behavior interface from object */ -// ------------------------------------------------------------------------------------------------ -SpawnBehaviorInterface* Object::getSpawnBehaviorInterface() const -{ - for (BehaviorModule** m = m_behaviors; *m; ++m) - { - SpawnBehaviorInterface *sbi = (*m)->getSpawnBehaviorInterface(); - if( sbi ) - { - return sbi; - } - } - return NULL; -} // end getSpawnBehaviorInterfaceFromObject - -// ------------------------------------------------------------------------------------------------ -ProjectileUpdateInterface* Object::getProjectileUpdateInterface() const -{ - for (BehaviorModule** m = m_behaviors; *m; ++m) - { - ProjectileUpdateInterface *pui = (*m)->getProjectileUpdateInterface(); - if( pui ) - { - return pui; - } - } - return NULL; -} - -// ------------------------------------------------------------------------------------------------ -// Simply find the special power module that is currently allowing plotting of positions to target. -// ------------------------------------------------------------------------------------------------ -SpecialPowerUpdateInterface* Object::findSpecialPowerWithOverridableDestinationActive( SpecialPowerType type ) const -{ - for( BehaviorModule** u = m_behaviors; *u; ++u ) - { - SpecialPowerUpdateInterface *spInterface = (*u)->getSpecialPowerUpdateInterface(); - if( spInterface ) - { - if( spInterface->doesSpecialPowerHaveOverridableDestinationActive() ) - { - return spInterface; - } - } - } // end for - return NULL; -} - -// ------------------------------------------------------------------------------------------------ -// Simply find the special power module that is potentially allowed to plot positions to target. -// ------------------------------------------------------------------------------------------------ -SpecialPowerUpdateInterface* Object::findSpecialPowerWithOverridableDestination( SpecialPowerType type ) const -{ - for( BehaviorModule** u = m_behaviors; *u; ++u ) - { - SpecialPowerUpdateInterface *spInterface = (*u)->getSpecialPowerUpdateInterface(); - if( spInterface ) - { - if( spInterface->doesSpecialPowerHaveOverridableDestination() ) - { - return spInterface; - } - } - } // end for - return NULL; -} - - -// ------------------------------------------------------------------------------------------------ -// Search our special ability updates for a specific one. -// ------------------------------------------------------------------------------------------------ -SpecialAbilityUpdate* Object::findSpecialAbilityUpdate( SpecialPowerType type ) const -{ - for( BehaviorModule** u = m_behaviors; *u; ++u ) - { - SpecialPowerUpdateInterface *spInterface = (*u)->getSpecialPowerUpdateInterface(); - if( spInterface && spInterface->isSpecialAbility() ) - { - SpecialAbilityUpdate *spUpdate = (SpecialAbilityUpdate*)spInterface; - if( spUpdate->getSpecialPowerType() == type ) - { - return spUpdate; - } - } - } // end for - - return NULL; -} - -// ------------------------------------------------------------------------------------------------ -SpecialPowerCompletionDie* Object::findSpecialPowerCompletionDie() const -{ - static NameKeyType key_SpecialPowerCompletionDie = NAMEKEY("SpecialPowerCompletionDie"); - return (SpecialPowerCompletionDie*)findModule(key_SpecialPowerCompletionDie); -} - -// ------------------------------------------------------------------------------------------------ -Int Object::getNumConsecutiveShotsFiredAtTarget( const Object *victim ) const -{ - return m_firingTracker ? m_firingTracker->getNumConsecutiveShotsAtVictim( victim ) : 0; -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -Bool Object::getSingleLogicalBonePosition(const char* boneName, Coord3D* position, Matrix3D* transform) const -{ - if (m_drawable && m_drawable->getPristineBonePositions( boneName, 0, position, transform, 1 ) == 1 ) - { - m_drawable->convertBonePosToWorldPos( position, transform, position, transform ); - return true; - } - else - { - if (position) - *position = *getPosition(); - if (transform) - *transform = *getTransformMatrix(); - return false; - } -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -Bool Object::getSingleLogicalBonePositionOnTurret( WhichTurretType whichTurret, const char* boneName, Coord3D* position, Matrix3D* transform ) const -{ - Coord3D turretPosition; - Coord3D bonePosition; - if( getDrawable() == NULL || getAI() == NULL ) - return FALSE; - - // We need to find the TurretBone's pristine position. - getDrawable()->getProjectileLaunchOffset( PRIMARY_WEAPON, 1, NULL, whichTurret, &turretPosition, NULL ); - // And the required bone's pristine position - if( getDrawable()->getPristineBonePositions(boneName, 0, &bonePosition, NULL, 1) != 1 ) - return FALSE; - //Then we mojo the Logic position of the required bone like Missile firing does. Using the logic twist of the turret - Real turretRotation; - getAI()->getTurretRotAndPitch( whichTurret, &turretRotation, NULL ); - - Matrix3D boneOffset(TRUE);// This will be from the turret to the requested bone - -// Vector3 bonePositionVector( bonePosition.x - turretPosition.x, -// bonePosition.y - turretPosition.y, -// bonePosition.z - turretPosition.z ); - Vector3 bonePositionVector( bonePosition.x, - bonePosition.y, - bonePosition.z ); - boneOffset.Translate(bonePositionVector); - - Matrix3D turnAdjustment(TRUE);// this is the turret twist to be applied to the final answer - - turnAdjustment.Translate( turretPosition.x, turretPosition.y, turretPosition.z ); - turnAdjustment.In_Place_Pre_Rotate_Z(turretRotation); - turnAdjustment.Translate( -turretPosition.x, -turretPosition.y, -turretPosition.z ); - - Matrix3D boneLogicTransform; - boneLogicTransform.mul( turnAdjustment, boneOffset ); - - Matrix3D worldTransform; - convertBonePosToWorldPos(NULL, &boneLogicTransform, NULL, &worldTransform); - - Vector3 tmp = worldTransform.Get_Translation(); - Coord3D worldPos; - worldPos.x = tmp.X; - worldPos.y = tmp.Y; - worldPos.z = tmp.Z; - - if( position ) - *position = worldPos; - if( transform ) - *transform = worldTransform; - - return TRUE; -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -Int Object::getMultiLogicalBonePosition(const char* boneNamePrefix, Int maxBones, - Coord3D* positions, Matrix3D* transforms, - Bool convertToWorld ) const -{ - Int count; - if (m_drawable && (count = m_drawable->getPristineBonePositions( boneNamePrefix, 1, positions, transforms, maxBones )) > 0 ) - { - if( convertToWorld ) - { - for (Int i = 0; i < count; ++i) - m_drawable->convertBonePosToWorldPos( positions ? &positions[i] : NULL, transforms ? &transforms[i] : NULL, positions ? &positions[i] : NULL, transforms ? &transforms[i] : NULL ); - } - return count; - } - else - { - return 0; - } -} - -//============================================================================= -const AsciiString& Object::getCommandSetString() const -{ - if (m_commandSetStringOverride.isNotEmpty()) - return m_commandSetStringOverride; - - return getTemplate()->friend_getCommandSetString(); -} - -//============================================================================= -Bool Object::canProduceUpgrade( const UpgradeTemplate *upgrade ) -{ - // We need to have the button to make the upgrade. CommandSets are a weird Logic/Client hybrid. - const CommandSet *set = TheControlBar->findCommandSet(getCommandSetString()); - - for( Int buttonIndex = 0; buttonIndex < MAX_COMMANDS_PER_SET; buttonIndex++ ) - { - const CommandButton *button = set->getCommandButton(buttonIndex); - if( button && button->getUpgradeTemplate() && (button->getUpgradeTemplate() == upgrade) ) - return TRUE; // getUpgradeTemplate only returns something if it is actually an upgrade - } - - return FALSE;// Cheatin' punk. -} - -//============================================================================= -// Object::defect, and related methods = -//============================================================================= -void Object::defect( Team* newTeam, UnsignedInt detectionTime ) -{ - if ( isContained() ) //@todo (KRIS?) make contained units unselectable, until then... lorenzen - { - return; - } - - Player *player = getControllingPlayer(); - if ( !player ) - return; - - Team* myTeam = player->getDefaultTeam(); - if ( myTeam == newTeam ) // can't defect from my own team, that would be silly - return; - - // things that are under construction, or sold, cannot defect. - if (testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) || - testStatus(OBJECT_STATUS_SOLD)) - { - return; - } - - // Before switch //////////////////////////////////////// - - //Design says: - ProductionUpdateInterface *production = getProductionUpdateInterface(); - if ( production ) - { - production->cancelAndRefundAllProduction(); - } - - // pop it up on the radar, so as to warn those who care - // do this first, since after setTeam() the infiltrator - // becomes the controllingplayer, not me - - // But don't do this is if the new team is not a real team. "'Enemy' infiltration" wouldn't make - // sense, and we are probably just reverting a cave or something. - if( friend_getRadarData() && newTeam->getControllingPlayer()->isPlayableSide() && myTeam->getControllingPlayer()->isPlayableSide()) - { - TheRadar->tryInfiltrationEvent( this ); - } - - friend_setUndetectedDefector( detectionTime > 0 ); - - if (m_defectionHelper) - m_defectionHelper->startDefectionTimer(detectionTime); - - // Switch //////////////////////////////////////// - setTeam( newTeam ); - - // After switch //////////////////////////////////////// - - AIUpdateInterface *ai = getAI(); - - handlePartitionCellMaintenance();// to clear the shoud for my new master - - if ( ai ) - { - ai->aiIdle( CMD_FROM_AI ); - } - - // Play our sound indicating we've been defected. (weird verbage, but true.) - AudioEventRTS voiceDefect = *getTemplate()->getVoiceDefect(); - voiceDefect.setObjectID(getID()); - TheAudio->addAudioEvent(&voiceDefect); - - //make the new recruit the only selected thing, awaiting new command to move, attack, etc... - Drawable *dr = getDrawable(); - if (dr) - { - dr->flashAsSelected(); //This is the first of several flashes which get cue'd by doDefectorUpdateStuff() - AudioEventRTS defectorTimerSound = TheAudio->getMiscAudio()->m_defectorTimerTickSound; - defectorTimerSound.setObjectID( getID() ); - TheAudio->addAudioEvent(&defectorTimerSound); - } - - ContainModuleInterface *ct = getContain(); - if( ct && ct->isKickOutOnCapture() ) - { - // Caves really really don't want to do this. - ct->removeAllContained( TRUE ); - } - - // if it has parking places, defect anything parked there. - for (BehaviorModule** i = getBehaviorModules(); *i; ++i) - { - ParkingPlaceBehaviorInterface* pp = (*i)->getParkingPlaceBehaviorInterface(); - if (pp) - { - pp->defectAllParkedUnits(newTeam, detectionTime); - break; - } - } - - // defect any mines that are owned by this structure, right now. - // unfortunately, structures don't keep list of mines they own, so we must do - // this the hard way :-( [fortunately, this doens't happen very often, so this - // is probably an acceptable, if icky, solution.] (srj) - for (Object* mine = TheGameLogic->getFirstObject(); mine; mine = mine->getNextObject()) - { - if (mine->isKindOf(KINDOF_MINE)) - { - if (mine->getProducerID() == this->getID()) - { - mine->setTeam(newTeam); - } - } - } - -} - -//============================================================================= -// Object::goInvulnerable -//============================================================================= -void Object::goInvulnerable( UnsignedInt time ) -{ - const Bool WITHOUT_DEFECTOR_FX = FALSE; - - - friend_setUndetectedDefector( time > 0 ); - - if (m_defectionHelper) - m_defectionHelper->startDefectionTimer(time, WITHOUT_DEFECTOR_FX); - -} - -// ------------------------------------------------------------------------------------------------ -/** Return the radar priority for this object type */ -// ------------------------------------------------------------------------------------------------ -RadarPriorityType Object::getRadarPriority( void ) const -{ - RadarPriorityType priority = RADAR_PRIORITY_INVALID; - - // first, get the priority at the thing template level - priority = getTemplate()->getDefaultRadarPriority(); - - // - // there are some objects that we want to show up on the radar when they have - // certain properties ... here we will check for those properties unless the INI - // setting of "not on radar" has been manually entered which explicitly forbids an - // object from being on the radar ... by default objects get an "invalid" priority - // on the radar and this means that we are free to decide one here if we want - // - if( priority == RADAR_PRIORITY_INVALID ) - { - - // objects that are "garrisonable" show up on the radar - ContainModuleInterface *cmi = getContain(); - if( cmi && cmi->isGarrisonable() ) - priority = RADAR_PRIORITY_STRUCTURE; - - // objects that are "capturable" show up on the radar - if( isKindOf( KINDOF_CAPTURABLE ) ) - priority = RADAR_PRIORITY_STRUCTURE; - - - } // end if - - // Carbombs will show up as units regardless of their default priority - if ( testStatus( OBJECT_STATUS_IS_CARBOMB ) ) - priority = RADAR_PRIORITY_UNIT; - - - // return the priority we're going to use - return priority; - -} // end getRadarPriority - -// ------------------------------------------------------------------------------------------------ -AIGroup *Object::getGroup(void) -{ - return m_group; -} - -//------------------------------------------------------------------------------------------------- -void Object::enterGroup( AIGroup *group ) -{ -// DEBUG_LOG(("***AIGROUP %x involved in enterGroup on %x\n", group, this)); - // if we are in another group, remove ourselves from it first - leaveGroup(); - - m_group = group; -} - -//------------------------------------------------------------------------------------------------- -void Object::leaveGroup( void ) -{ -// DEBUG_LOG(("***AIGROUP %x involved in leaveGroup on %x\n", m_group, this)); - // if we are in a group, remove ourselves from it - if (m_group) - { - // to avoid recursion, set m_group to NULL before removing - AIGroup *group = m_group; - m_group = NULL; - group->remove( this ); - } -} - -//------------------------------------------------------------------------------------------------- -Real Object::getCarrierDeckHeight() const -{ - Object *producer = TheGameLogic->findObjectByID( getProducerID() ); - if( producer ) - { - // Find a parking place behavior. - for( BehaviorModule** i = producer->getBehaviorModules(); *i; ++i ) - { - ParkingPlaceBehaviorInterface* pp = (*i)->getParkingPlaceBehaviorInterface(); - if( pp ) - { - return pp->getLandingDeckHeightOffset(); - } - } - } - return 0.0f; -} - -//------------------------------------------------------------------------------------------------- -CountermeasuresBehaviorInterface* Object::getCountermeasuresBehaviorInterface() -{ - for( BehaviorModule** i = getBehaviorModules(); *i; ++i ) - { - CountermeasuresBehaviorInterface* cbi = (*i)->getCountermeasuresBehaviorInterface(); - if( cbi ) - { - return cbi; - } - } - return NULL; -} - -//------------------------------------------------------------------------------------------------- -const CountermeasuresBehaviorInterface* Object::getCountermeasuresBehaviorInterface() const -{ - for( BehaviorModule** i = getBehaviorModules(); *i; ++i ) - { - const CountermeasuresBehaviorInterface* cbi = (*i)->getCountermeasuresBehaviorInterface(); - if( cbi ) - { - return cbi; - } - } - return NULL; -} - -//------------------------------------------------------------------------------------------------- -Bool Object::hasCountermeasures() const -{ - const CountermeasuresBehaviorInterface* cbi = getCountermeasuresBehaviorInterface(); - if( cbi && cbi->isActive() ) - { - return TRUE; - } - return FALSE; -} - -//------------------------------------------------------------------------------------------------- -void Object::reportMissileForCountermeasures( Object *missile ) -{ - for( BehaviorModule** i = getBehaviorModules(); *i; ++i ) - { - CountermeasuresBehaviorInterface* cbi = (*i)->getCountermeasuresBehaviorInterface(); - if( cbi ) - { - cbi->reportMissileForCountermeasures( missile ); - } - } -} - -//------------------------------------------------------------------------------------------------- -ObjectID Object::calculateCountermeasureToDivertTo( const Object& victim ) -{ - AIUpdateInterface *ai = getAI(); - if( ai ) - { - for( BehaviorModule** i = victim.getBehaviorModules(); *i; ++i ) - { - CountermeasuresBehaviorInterface* cbi = (*i)->getCountermeasuresBehaviorInterface(); - if( cbi ) - { - ObjectID decoyID = cbi->calculateCountermeasureToDivertTo( victim ); - return decoyID; - } - } - } - return INVALID_ID; -} +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE Object.cpp //////////////////////////////////////////////////////////////////////////////// +// Simple base object +// Author: Michael S. Booth, October 2000 +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine +#define DEFINE_WEAPONCONDITIONMAP +#include "Common/BitFlagsIO.h" +#include "Common/BuildAssistant.h" +#include "Common/Dict.h" +#include "Common/GameCommon.h" +#include "Common/GameEngine.h" +#include "Common/GameState.h" +#include "Common/ModuleFactory.h" +#include "Common/Player.h" +#include "Common/PlayerList.h" +#include "Common/Radar.h" +#include "Common/SpecialPower.h" +#include "Common/Team.h" +#include "Common/ThingFactory.h" +#include "Common/ThingTemplate.h" +#include "Common/Upgrade.h" +#include "Common/WellKnownKeys.h" +#include "Common/Xfer.h" +#include "Common/XferCRC.h" +#include "Common/PerfTimer.h" + +#include "GameClient/Anim2D.h" +#include "GameClient/ControlBar.h" +#include "GameClient/Drawable.h" +#include "GameClient/Eva.h" +#include "GameClient/GameClient.h" +#include "GameClient/InGameUI.h" + +#include "GameLogic/AI.h" +#include "GameLogic/AIPathfind.h" +#include "GameLogic/ExperienceTracker.h" +#include "GameLogic/FiringTracker.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/Locomotor.h" + +#include "GameLogic/Module/AIUpdate.h" +#include "GameLogic/Module/AutoHealBehavior.h" +#include "GameLogic/Module/BehaviorModule.h" +#include "GameLogic/Module/BodyModule.h" +#include "GameLogic/Module/CollideModule.h" +#include "GameLogic/Module/ContainModule.h" +#include "GameLogic/Module/CountermeasuresBehavior.h" +#include "GameLogic/Module/CreateModule.h" +#include "GameLogic/Module/DamageModule.h" +#include "GameLogic/Module/DeletionUpdate.h" +#include "GameLogic/Module/DestroyModule.h" +#include "GameLogic/Module/DieModule.h" +#include "GameLogic/Module/DozerAIUpdate.h" +#include "GameLogic/Module/ObjectDefectionHelper.h" +#include "GameLogic/Module/ObjectRepulsorHelper.h" +#include "GameLogic/Module/ObjectSMCHelper.h" +#include "GameLogic/Module/ObjectWeaponStatusHelper.h" +#include "GameLogic/Module/OverchargeBehavior.h" +#include "GameLogic/Module/PhysicsUpdate.h" +#include "GameLogic/Module/PowerPlantUpgrade.h" +#include "GameLogic/Module/ProductionUpdate.h" +#include "GameLogic/Module/RadarUpgrade.h" +#include "GameLogic/Module/RebuildHoleBehavior.h" +#include "GameLogic/Module/SpawnBehavior.h" +#include "GameLogic/Module/SpecialPowerModule.h" +#include "GameLogic/Module/SpecialAbilityUpdate.h" +#include "GameLogic/Module/StatusDamageHelper.h" +#include "GameLogic/Module/StickyBombUpdate.h" +#include "GameLogic/Module/SubdualDamageHelper.h" +#include "GameLogic/Module/ChronoDamageHelper.h" +#include "GameLogic/Module/TempWeaponBonusHelper.h" +#include "GameLogic/Module/ToppleUpdate.h" +#include "GameLogic/Module/UpdateModule.h" +#include "GameLogic/Module/UpgradeModule.h" + +#include "GameLogic/Object.h" +#include "GameLogic/PartitionManager.h" +#include "GameLogic/PolygonTrigger.h" +#include "GameLogic/ScriptEngine.h" +#include "GameLogic/Weapon.h" +#include "GameLogic/WeaponSet.h" +#include "GameLogic/Module/RadarUpdate.h" +#include "GameLogic/Module/PowerPlantUpdate.h" + +#include "Common/CRCDebug.h" +#include "Common/MiscAudio.h" +#include "Common/AudioEventInfo.h" +#include "Common/DynamicAudioEventInfo.h" + +#ifdef RTS_INTERNAL +// for occasional debugging... +//#pragma optimize("", off) +//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes") +#endif + +#ifdef DEBUG_OBJECT_ID_EXISTS +ObjectID TheObjectIDToDebug = INVALID_ID; +#endif + +// ------------------------------------------------------------------------------------------------ +static const ModelConditionFlags s_allWeaponFireFlags[WEAPONSLOT_COUNT] = +{ + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_A, + MODELCONDITION_BETWEEN_FIRING_SHOTS_A, + MODELCONDITION_RELOADING_A, + MODELCONDITION_PREATTACK_A, + MODELCONDITION_USING_WEAPON_A + ), + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_B, + MODELCONDITION_BETWEEN_FIRING_SHOTS_B, + MODELCONDITION_RELOADING_B, + MODELCONDITION_PREATTACK_B, + MODELCONDITION_USING_WEAPON_B + ), + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_C, + MODELCONDITION_BETWEEN_FIRING_SHOTS_C, + MODELCONDITION_RELOADING_C, + MODELCONDITION_PREATTACK_C, + MODELCONDITION_USING_WEAPON_C + ), + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_D, + MODELCONDITION_BETWEEN_FIRING_SHOTS_D, + MODELCONDITION_RELOADING_D, + MODELCONDITION_PREATTACK_D, + MODELCONDITION_USING_WEAPON_D + ), + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_E, + MODELCONDITION_BETWEEN_FIRING_SHOTS_E, + MODELCONDITION_RELOADING_E, + MODELCONDITION_PREATTACK_E, + MODELCONDITION_USING_WEAPON_E + ), + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_F, + MODELCONDITION_BETWEEN_FIRING_SHOTS_F, + MODELCONDITION_RELOADING_F, + MODELCONDITION_PREATTACK_F, + MODELCONDITION_USING_WEAPON_F + ), + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_G, + MODELCONDITION_BETWEEN_FIRING_SHOTS_G, + MODELCONDITION_RELOADING_G, + MODELCONDITION_PREATTACK_G, + MODELCONDITION_USING_WEAPON_G + ), + MAKE_MODELCONDITION_MASK5( + MODELCONDITION_FIRING_H, + MODELCONDITION_BETWEEN_FIRING_SHOTS_H, + MODELCONDITION_RELOADING_H, + MODELCONDITION_PREATTACK_H, + MODELCONDITION_USING_WEAPON_H + ) +}; + +//------------------------------------------------------------------------------------------------- +extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color); + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +AsciiString DebugDescribeObject(const Object *obj) +{ + if (!obj) + return ""; + + AsciiString ret; + + if (obj->getName().isNotEmpty()) + { + ret.format("Object %d (%s) [%s, owned by player %d (%ls)]", + obj->getID(), obj->getName().str(), obj->getTemplate()->getName().str(), + obj->getControllingPlayer()->getPlayerIndex(), + obj->getControllingPlayer()->getPlayerDisplayName().str()); + } + else + { + ret.format("Object %d [%s, owned by player %d (%ls)]", + obj->getID(), obj->getTemplate()->getName().str(), + obj->getControllingPlayer()->getPlayerIndex(), + obj->getControllingPlayer()->getPlayerDisplayName().str()); + } + + return ret; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Object::Object( const ThingTemplate *tt, const ObjectStatusMaskType &objectStatusMask, Team *team ) : + Thing(tt), + m_indicatorColor(0), + m_ai(NULL), + m_physics(NULL), + m_geometryInfo(tt->getTemplateGeometryInfo()), + m_containedBy(NULL), + m_xferContainedByID(INVALID_ID), + m_containedByFrame(0), + m_behaviors(NULL), + m_body(NULL), + m_contain(NULL), + m_stealth(NULL), + m_partitionData(NULL), + m_radarData(NULL), + m_drawable(NULL), + m_next(NULL), + m_prev(NULL), + m_team(NULL), + m_experienceTracker(NULL), + m_firingTracker(NULL), + m_repulsorHelper(NULL), + m_statusDamageHelper(NULL), + m_tempWeaponBonusHelper(NULL), + m_subdualDamageHelper(NULL), + m_chronoDamageHelper(NULL), + m_smcHelper(NULL), + m_wsHelper(NULL), + m_defectionHelper(NULL), + m_partitionLastLook(NULL), + m_partitionRevealAllLastLook(NULL), + m_partitionLastShroud(NULL), + m_partitionLastThreat(NULL), + m_partitionLastValue(NULL), + m_smcUntil(NEVER), + m_privateStatus(0), + m_formationID(NO_FORMATION_ID), + m_isReceivingDifficultyBonus(FALSE), + m_singleUseCommandUsed(FALSE), + m_scriptStatus(0), + m_enteredOrExitedFrame(0), + m_visionSpiedMask (PLAYERMASK_NONE), + m_numTriggerAreasActive(0) +{ +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + m_hasDiedAlready = false; +#endif + //Modules have not been created yet! + m_modulesReady = false; + + // Force the thing template to use the most overridden version of itself - jkmcd + // Note that after this, the object will be using m_template, which forces the usage of the + // most overridden version of tt, so this is okay. + tt = (const ThingTemplate *) tt->getFinalOverride(); + + Int i, modIdx; + AsciiString modName; + + //Added By Sadullah Nader + //Initializations inserted + m_formationOffset.x = m_formationOffset.y = 0.0f; + m_iPos.zero(); + // + for (i = 0; i < MAX_PLAYER_COUNT; ++i) + { + m_visionSpiedBy[i] = 0; + } + + for( i = 0; i < DISABLED_COUNT; i++ ) + { + m_disabledTillFrame[ i ] = NEVER; + } + + m_weaponBonusCondition = 0; + m_curWeaponSetFlags.clear(); + + // sanity + if( TheGameLogic == NULL || tt == NULL ) + { + + assert( 0 ); + return; + + } // end if + + // Object's set of these persist for the life of the object. + m_partitionLastLook = newInstance(SightingInfo); + m_partitionLastLook->reset(); + m_partitionRevealAllLastLook = newInstance(SightingInfo); + m_partitionRevealAllLastLook->reset(); + m_partitionLastShroud = newInstance(SightingInfo); + m_partitionLastShroud->reset(); + m_partitionLastThreat = newInstance(SightingInfo); + m_partitionLastThreat->reset(); + m_partitionLastValue = newInstance(SightingInfo); + m_partitionLastValue->reset(); + + // must set ID to zero, since some of these set methods + // will cause network messages to be sent + // which use this ID. + m_id = INVALID_ID; + m_producerID = INVALID_ID; + m_builderID = INVALID_ID; + + m_status = objectStatusMask; + m_layer = LAYER_GROUND; + + m_group = NULL; + + m_constructionPercent = CONSTRUCTION_COMPLETE; // complete by default + + m_visionRange = tt->friend_calcVisionRange(); + m_shroudClearingRange = tt->friend_calcShroudClearingRange(); + if( m_shroudClearingRange == -1.0f ) + m_shroudClearingRange = m_visionRange;// Backwards compatible, and perfectly logical default to assign + m_shroudRange = 0.0f; + + m_singleUseCommandUsed = false; + + // assign unique object id + setID( TheGameLogic->allocateObjectID() ); + + // + // allocate any modules we need to, we should keep + // this at or near the end of the drawable construction so that we have + // all the valid data about the thing when we create the module + // + Int totalModules = tt->getBehaviorModuleInfo().getCount() + NUM_SLEEP_HELPERS; // need to take into account all the helper modules + + // allocate the publicModule arrays +// pool[]ify + m_behaviors = MSGNEW("ModulePtrs") BehaviorModule*[totalModules + 1]; + BehaviorModule** curB = m_behaviors; + const ModuleInfo& mi = tt->getBehaviorModuleInfo(); + + // set m_team to null before the first call, to avoid naughtiness... + // If no team is specified in the constructor, then assign the object + // to the neutral team. + setTeam(team ? team : ThePlayerList->getNeutralPlayer()->getDefaultTeam()); + + // the helpers are done first -- even before Behaviors! -- in case a module needs + // to call something that uses them. + static const NameKeyType smcHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_SMCHelper" ); + static ObjectSMCHelperModuleData smcModuleData; + smcModuleData.setModuleTagNameKey( smcHelperModuleDataTagNameKey ); + m_smcHelper = newInstance(ObjectSMCHelper)(this, &smcModuleData); + *curB++ = m_smcHelper; + + //Inactive bodies can't take special damage since they can't take damage + Bool isInactiveBody = FALSE; + for( Int infoIndex = 0; infoIndex < mi.getCount(); ++infoIndex ) + { + modName = mi.getNthName(infoIndex); + if (modName.isEmpty()) + continue; + + if( modName.compare("InactiveBody") == 0 ) + { + isInactiveBody = TRUE; + break; + } + } + + if( !isInactiveBody ) + { + static const NameKeyType statusHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_StatusDamageHelper" ); + static StatusDamageHelperModuleData statusModuleData; + statusModuleData.setModuleTagNameKey( statusHelperModuleDataTagNameKey ); + m_statusDamageHelper = newInstance(StatusDamageHelper)(this, &statusModuleData); + *curB++ = m_statusDamageHelper; + + static const NameKeyType subdualHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_SubdualDamageHelper" ); + static SubdualDamageHelperModuleData subdualModuleData; + subdualModuleData.setModuleTagNameKey( subdualHelperModuleDataTagNameKey ); + m_subdualDamageHelper = newInstance(SubdualDamageHelper)(this, &subdualModuleData); + *curB++ = m_subdualDamageHelper; + + static const NameKeyType chronoHelperModuleDataTagNameKey = NAMEKEY("ModuleTag_ChronoDamageHelper"); + static ChronoDamageHelperModuleData chronoModuleData; + chronoModuleData.setModuleTagNameKey(chronoHelperModuleDataTagNameKey); + m_chronoDamageHelper = newInstance(ChronoDamageHelper)(this, &chronoModuleData); + *curB++ = m_chronoDamageHelper; + } + + if (TheAI != NULL + && TheAI->getAiData()->m_enableRepulsors + && isKindOf(KINDOF_CAN_BE_REPULSED)) + { + // if we can ever be a temporary-repulsor, make a repulsor helper. (srj) + static const NameKeyType repulsorHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_RepulsorHelper" ); + static ObjectRepulsorHelperModuleData repulsorModuleData; + repulsorModuleData.setModuleTagNameKey( repulsorHelperModuleDataTagNameKey ); + m_repulsorHelper = newInstance(ObjectRepulsorHelper)(this, &repulsorModuleData); + *curB++ = m_repulsorHelper; + } + + /** @todo srj -- figure out how to create this only on demand. + currently we don't have a good way to add/remove update modules from + an object on-the-fly, so we fake it here, and just skip the creation + if it is impossible for this object to ever defect... */ + + // shrubbery cannot defect. no, really. + if (!tt->isKindOf(KINDOF_SHRUBBERY)) + { + static const NameKeyType defectionModuleDataTagNameKey = NAMEKEY( "ModuleTag_DefectionHelper" ); + static ObjectDefectionHelperModuleData defectionModuleData; + defectionModuleData.setModuleTagNameKey( defectionModuleDataTagNameKey ); + m_defectionHelper = newInstance(ObjectDefectionHelper)(this, &defectionModuleData); + *curB++ = m_defectionHelper; + } + + if (tt->canPossiblyHaveAnyWeapon()) + { + // we only need a firingtracker and wshelper if we can possibly have a weapon. + static const NameKeyType weaponStatusModuleDataTagNameKey = NAMEKEY( "ModuleTag_WeaponStatusHelper" ); + static ObjectWeaponStatusHelperModuleData weaponStatusModuleData; + weaponStatusModuleData.setModuleTagNameKey( weaponStatusModuleDataTagNameKey ); + m_wsHelper = newInstance(ObjectWeaponStatusHelper)(this, &weaponStatusModuleData); + *curB++ = m_wsHelper; + + static const NameKeyType firingTrackerModuleDataTagNameKey = NAMEKEY( "ModuleTag_FiringTrackerHelper" ); + static FiringTrackerModuleData firingTrackerModuleData; + firingTrackerModuleData.setModuleTagNameKey( firingTrackerModuleDataTagNameKey ); + m_firingTracker = newInstance(FiringTracker)(this, &firingTrackerModuleData); + *curB++ = m_firingTracker; + + static const NameKeyType tempWeaponBonusHelperModuleDataTagNameKey = NAMEKEY( "ModuleTag_TempWeaponBonusHelper" ); + static TempWeaponBonusHelperModuleData tempWeaponBonusModuleData; + tempWeaponBonusModuleData.setModuleTagNameKey( tempWeaponBonusHelperModuleDataTagNameKey ); + m_tempWeaponBonusHelper = newInstance(TempWeaponBonusHelper)(this, &tempWeaponBonusModuleData); + *curB++ = m_tempWeaponBonusHelper; + } + + // behaviors are always done first, so they get into the publicModule arrays + // before anything else. + for (modIdx = 0; modIdx < mi.getCount(); ++modIdx) + { + modName = mi.getNthName(modIdx); + if (modName.isEmpty()) + continue; + + BehaviorModule* newMod = (BehaviorModule*)TheModuleFactory->newModule(this, modName, mi.getNthData(modIdx), MODULETYPE_BEHAVIOR); + *curB++ = newMod; + + BodyModuleInterface* body = newMod->getBody(); + if (body) + { + DEBUG_ASSERTCRASH(m_body == NULL, ("Duplicate bodies")); + m_body = body; + } + + ContainModuleInterface* contain = newMod->getContain(); + if (contain) + { + DEBUG_ASSERTCRASH(m_contain == NULL, ("Duplicate containers")); + m_contain = contain; + } + + StealthUpdate* stealth = (StealthUpdate*)newMod->getStealth(); + if ( stealth ) + { + DEBUG_ASSERTCRASH( m_stealth == NULL, ("DuplicateStealthUpdates!") ); + m_stealth = stealth; + } + + + AIUpdateInterface* ai = newMod->getAIUpdateInterface(); + if (ai) + { + if( m_ai ) + { + DEBUG_ASSERTCRASH( m_ai == NULL, ("%s has more than one AI module. This is illegal!\n", getTemplate()->getName().str()) ); + } + m_ai = ai; + } + + static NameKeyType key_PhysicsUpdate = NAMEKEY("PhysicsBehavior"); + if (newMod->getModuleNameKey() == key_PhysicsUpdate) + { + DEBUG_ASSERTCRASH(m_physics == NULL, ("You should never have more than one Physics module (%s)\n",getTemplate()->getName().str())); + m_physics = (PhysicsBehavior*)newMod; + } + } + + *curB = NULL; + + AIUpdateInterface *ai = getAIUpdateInterface(); + if (ai) { + ai->setAttitude(getTeam()->getPrototype()->getTemplateInfo()->m_initialTeamAttitude); + if (m_team && m_team->getPrototype() && m_team->getPrototype()->getAttackPriorityName().isNotEmpty()) { + AsciiString name = m_team->getPrototype()->getAttackPriorityName(); + const AttackPriorityInfo *info = TheScriptEngine->getAttackInfo(name); + if (info && info->getName().isNotEmpty()) { + ai->setAttackInfo(info); + } + } + } + + // allocate experience tracker + m_experienceTracker = newInstance(ExperienceTracker)(this); + + // If a valid team has been assigned me, then I have a Player I can ask about my starting level + const Player* controller = getControllingPlayer(); + m_experienceTracker->setVeterancyLevel( controller->getProductionVeterancyLevel( getTemplate()->getName() ) ); + + /// allow for inter-Module resolution + for (BehaviorModule** b = m_behaviors; *b; ++b) + { + (*b)->onObjectCreated(); + } + + m_numTriggerAreasActive = 0; + m_enteredOrExitedFrame = 0; + m_isSelectable = tt->isKindOf(KINDOF_SELECTABLE); + + m_healthBoxOffset.zero();// this is used for units that are amorphous, like angry mob + + //Modules have now been completely created! + m_modulesReady = true; + + TheRadar->addObject( this ); + + // register the object with the GameLogic + TheGameLogic->registerObject( this ); + + //disable occlusion for some time after object is created to allow them to exit the factory/building. + m_safeOcclusionFrame = TheGameLogic->getFrame()+tt->getOcclusionDelay(); + + + m_soleHealingBenefactorID = INVALID_ID; ///< who is the only other object that can give me this non-stacking heal benefit? + m_soleHealingBenefactorExpirationFrame = 0; ///< on what frame can I accept healing (thus to switch) from a new benefactor + + + +} // end Object + +//------------------------------------------------------------------------------------------------- +/** Emit message announcing object's creation + * Note: Have to do this in virtual init() method because virtual methods + * don't become virtual until AFTER the constructor has completed, and we + * need to send our type in this message via virtual getType(). */ +//------------------------------------------------------------------------------------------------- +void Object::initObject() +{ + // Weapons & Damage ------------------------------------------------------------------------------------------------- + // Force the initial weapon set to be instantiated & reloaded. + + //GS No Bad Wrong + // The flags are constructed to empty, and between then and now they may be set in valid ways by onCreate modules. + // We don't want to blow that away. updateWeaponSet is safe to call on its own, so I will move that to the end. +// m_curWeaponSetFlags.clear(); +// m_weaponSet.updateWeaponSet(this); +// m_weaponBonusCondition = 0; + + for (int i = 0; i < WEAPONSLOT_COUNT; ++i) + m_lastWeaponCondition[i] = WSF_INVALID; + + // emit message announcing object's creation + TheGameLogic->sendObjectCreated( this ); + + // If I have a valid team assigned, I can run through my Upgrade modules with his flags + updateUpgradeModules(); + + //If the player has battle plans (America Strategy Center), then apply those bonuses + //to this object if applicable. Internally it validates certain kinds of objects. + const Player* controller = getControllingPlayer(); + if (controller) + { + if (!getReceivingDifficultyBonus() && TheScriptEngine->getObjectsShouldReceiveDifficultyBonus()) + { + setReceivingDifficultyBonus(TRUE); + } + + if (controller->getNumBattlePlansActive() > 0) + { + controller->applyBattlePlanBonusesForObject( this ); + } + } + + + //For each special power module that we have, add it's type to the specialpower bits. This is + //for optimal access later. + for (BehaviorModule** m = m_behaviors; *m; ++m) + { + SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); + if (!sp) + continue; + + const SpecialPowerTemplate *spTemplate = sp->getSpecialPowerTemplate(); + if( spTemplate ) + { + SET_SPECIALPOWERMASK( m_specialPowerBits, spTemplate->getSpecialPowerType() ); + } + } + + // Kris -- All missiles must be projectiles! This is the perfect place to assert them! + // srj: yes, but only in debug... +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + if( !isKindOf( KINDOF_PROJECTILE ) ) + { + if( isKindOf( KINDOF_SMALL_MISSILE ) || isKindOf( KINDOF_BALLISTIC_MISSILE ) ) + { + //Warning only... + DEBUG_CRASH( ("Missile %s must also be a KindOf = PROJECTILE in addition to being either a SMALL_MISSILE or PROJECTILE_MISSILE -- call Kris (36844) for questions!", getTemplate()->getName().str() ) ); + } + } +#endif + if (!isKindOf(KINDOF_PROJECTILE) && !isKindOf(KINDOF_INERT)) { + // Notify script conditions to update conditions that consider unit counts. + // We ignore projectiles cause they are frequently created & destroyed, and are not + // of general interest. Normal unit count tests consider tanks or infantry or planes, etc. jba. + TheScriptEngine->notifyOfObjectCreationOrDestruction(); + TheGameLogic->updateObjectsChangedTriggerAreas(); + } + + // Everything (like weaponSet flags) is inited, so check if the WeaponSet needs to change. + m_weaponSet.updateWeaponSet(this); + + if( isKindOf( KINDOF_MINE ) || isKindOf( KINDOF_BOOBY_TRAP ) || isKindOf( KINDOF_DEMOTRAP ) ) + { + ThePlayerList->getNeutralPlayer()->getAcademyStats()->recordMine(); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Object::~Object() +{ + + // tell the AI the building is gone + /// @todo Generalize the notion of objects entering and leaving the world, so we don't have to special case this + TheAI->pathfinder()->removeObjectFromPathfindMap( this ); + + if (!isKindOf(KINDOF_PROJECTILE) && !isKindOf(KINDOF_INERT)) { + // Notify script conditions to update conditions that consider unit counts. + // We ignore projectiles cause they are frequently created & destroyed, and are not + // of general interest. Normal unit count tests consider tanks or infantry or planes, etc. jba. + TheGameLogic->updateObjectsChangedTriggerAreas(); + TheScriptEngine->notifyOfObjectCreationOrDestruction(); + } + + // + // remove from radar before we NULL out the team ... the order of ops are critical here + // because the radar code will sometimes look at the team info and it is assumed through + // the team and player code that the team is valid + // + if( m_radarData ) + TheRadar->removeObject( this ); + + // emit message announcing object's destruction. Again, order is important; we must do this + // before wiping out the team. + TheGameLogic->sendObjectDestroyed( this ); + + // empty the team + setTeam( NULL ); + + // Object's set of these persist for the life of the object. + m_partitionLastLook->deleteInstance(); + m_partitionLastLook = NULL; + m_partitionRevealAllLastLook->deleteInstance(); + m_partitionRevealAllLastLook = NULL; + m_partitionLastShroud->deleteInstance(); + m_partitionLastShroud = NULL; + m_partitionLastThreat->deleteInstance(); + m_partitionLastThreat = NULL; + m_partitionLastValue->deleteInstance(); + m_partitionLastValue = NULL; + + // remove the object from the partition system if present + if( m_partitionData ) + ThePartitionManager->unRegisterObject( this ); + + // if we are in a group, remove us + if (m_group) + m_group->remove( this ); + + // note, do NOT free these, there are just a shadow copy! + m_ai = NULL; + m_physics = NULL; + + // delete any modules present + for (BehaviorModule** b = m_behaviors; *b; ++b) + { + (*b)->deleteInstance(); + *b = NULL; // in case other modules call findModule from their dtor! + } + + delete [] m_behaviors; + m_behaviors = NULL; + + if( m_experienceTracker ) + m_experienceTracker->deleteInstance(); + + m_experienceTracker = NULL; + + // we don't need to delete these, there were deleted on the m_behaviors list + m_firingTracker = NULL; + m_repulsorHelper = NULL; + + m_statusDamageHelper = NULL; + m_tempWeaponBonusHelper = NULL; + m_subdualDamageHelper = NULL; + m_chronoDamageHelper = NULL; + m_smcHelper = NULL; + m_wsHelper = NULL; + m_defectionHelper = NULL; + + // reset id to zero so we never mistaken grab "dead" objects + m_id = INVALID_ID; + + // Instead of removing it from the named cache, notify the script engine that it has died. + // The script engine will remove it from the cache if necessary. The script engine needs to take + // a crack at this in case it is the current "This Object" pointer. + TheScriptEngine->notifyOfObjectDestruction(this); +} + +//------------------------------------------------------------------------------------------------- +/// this object now contained in "containedBy" +//------------------------------------------------------------------------------------------------- +void Object::onContainedBy( Object *containedBy ) +{ + setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_UNSELECTABLE ) ); + if (containedBy && containedBy->getContain()->isEnclosingContainerFor(this)) + setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) ); + else + clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) ); + m_containedBy = containedBy; + m_containedByFrame = TheGameLogic->getFrame(); + + handlePartitionCellMaintenance(); // which should unlook me now that I am contained + +} + +//------------------------------------------------------------------------------------------------- +/// this object no longer contained in "containedBy" +//------------------------------------------------------------------------------------------------- +void Object::onRemovedFrom( Object *removedFrom ) +{ + clearStatus( MAKE_OBJECT_STATUS_MASK2( OBJECT_STATUS_MASKED, OBJECT_STATUS_UNSELECTABLE ) ); + m_containedBy = NULL; + m_containedByFrame = 0; + + handlePartitionCellMaintenance(); // get a clean look, now that I am outdoors, again + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Int Object::getTransportSlotCount() const +{ + Int count = getTemplate()->getRawTransportSlotCount(); + ContainModuleInterface* contain = getContain(); + if ( contain && contain->isSpecialZeroSlotContainer() ) + { + count = 0; + const ContainedItemsList* items = contain->getContainedItemsList(); + if (items) + { + for (ContainedItemsList::const_iterator it = items->begin(); it != items->end(); ++it) + { + count += (*it)->getTransportSlotCount(); + } + } + } + return count; +} + +//------------------------------------------------------------------------------------------------- +/** Run from GameLogic::destroyObject */ +//------------------------------------------------------------------------------------------------- +void Object::onDestroy() +{ + + // This is the old cleanUpContain safeguard. Say goodbye so they don't try to look us up. + if( m_containedBy && m_containedBy->getContain() ) + { + m_containedBy->getContain()->removeFromContain( this ); + } + + // + // run the onDelete on all modules present so they each have an opportunity to cleanup + // anything they need to ... including talking to any other modules + // + for (BehaviorModule** b = m_behaviors; *b; ++b) + { + (*b)->onDelete(); + } + + //Have to remove ourself from looking as well. RebuildHoleWorkers definately hit here. + handlePartitionCellMaintenance(); +} // end onDestroy + +//============================================================================= +//============================================================================= +void Object::setGeometryInfo(const GeometryInfo& geom) +{ + m_geometryInfo = geom; + if( m_partitionData ) + { + // if our geometry changes, we unregister and re-register with the partitionmgr + // so that our size gets updated appropriately. this shouldn't be a problem + // unless setGeometryInfo gets called frequently. (srj) + ThePartitionManager->unRegisterObject( this ); + ThePartitionManager->registerObject( this ); + } + + if (m_drawable) + m_drawable->reactToGeometryChange(); +} + +//============================================================================= +//============================================================================= +void Object::setGeometryInfoZ( Real newZ ) +{ + // A Z change only does not need to un/register with the PartitionManager + m_geometryInfo.setMaxHeightAbovePosition( newZ ); + + if (m_drawable) + m_drawable->reactToGeometryChange(); +} + +//============================================================================= +void Object::friend_setUndetectedDefector( Bool status ) +{ + if (status) + m_privateStatus |= UNDETECTED_DEFECTOR; + else + m_privateStatus &= ~UNDETECTED_DEFECTOR; +} + +//============================================================================= +void Object::restoreOriginalTeam() +{ + if( m_team == NULL || m_originalTeamName.isEmpty() ) + return; + + Team* origTeam = TheTeamFactory->findTeam(m_originalTeamName); + if (origTeam == NULL) + { + DEBUG_CRASH(("Object original team (%s) could not be found or created! (srj)\n",m_originalTeamName.str())); + return; + } + + if (m_team == origTeam) + { + DEBUG_CRASH(("Object appears to still be on its original team, so why are we attempting to restore it? (srj)\n")); + return; + } + + setTeam(origTeam); +} + +//============================================================================= +//============================================================================= +void Object::setTeam( Team *team ) +{ + // In order to prevent spawning useful units for a player after he dies, we + // just assign objects to the neutral player if we try to misbehave. + if (team && !team->getControllingPlayer()->isPlayerActive()) + team = ThePlayerList->getNeutralPlayer()->getDefaultTeam(); + + setTemporaryTeam(team); + m_originalTeamName = m_team ? m_team->getName() : AsciiString::TheEmptyString; +} + +//============================================================================= +//============================================================================= +void Object::setTemporaryTeam( Team *team ) +{ + const Bool restoring = false; + setOrRestoreTeam(team, restoring); +} + +//============================================================================= +//============================================================================= +void Object::setOrRestoreTeam( Team* team, Bool restoring ) +{ + // don't do anything if the team hasn't changed + if( m_team == team ) + return; + + Team* oldTeam = m_team; + + // Before Switch ////////////////////////// + if (m_team) + { + if (m_team->isInList_TeamMemberList(this)) + { + m_team->removeFrom_TeamMemberList(this); + m_team->getControllingPlayer()->becomingTeamMember(this, false); + } + } + + // Switch ////////////////////////// + m_team = team; + + // After Switch ////////////////////////// + if (m_team) + { + if (!m_team->isInList_TeamMemberList(this)) + { + m_team->prependTo_TeamMemberList(this); + m_team->getControllingPlayer()->becomingTeamMember(this, true); + } + + // now, adjust the attitude of the unit to its new team. + const TeamPrototype* proto = m_team->getPrototype(); + if (proto && proto->getTemplateInfo()) + { + AIUpdateInterface *ai = getAIUpdateInterface(); + if (ai) + { + ai->setAttitude(proto->getTemplateInfo()->m_initialTeamAttitude); + if (proto->getAttackPriorityName().isNotEmpty()) { + AsciiString name = proto->getAttackPriorityName(); + const AttackPriorityInfo *info = TheScriptEngine->getAttackInfo(name); + if (info && info->getName().isNotEmpty()) { + ai->setAttackInfo(info); + } + } + } + } + // emit message announcing object's new alliance + Drawable *draw = getDrawable(); + if (draw) + draw->changedTeam(); + } + + // This can't just go in ::defect, because some things just do setTeam. The act of + // setting a new team needs to tell the modules and do other important stuff. + // And it needs to happen after the switch. + if( oldTeam && team && !restoring ) + onCapture( oldTeam->getControllingPlayer(), team->getControllingPlayer() ); + + // + // the team changed we have a change in priorities on the radar if we are + // a candidate for the radar as it is + // + if( m_radarData ) + { + + // removing it and adding it will cause a resort to happen + TheRadar->removeObject( this ); + TheRadar->addObject( this ); + } + + // Tell TheInGameUI that the object has changed hands + Int oldPlayerIndex = (oldTeam)?(oldTeam->getControllingPlayer()->getPlayerIndex()):-1; + Int newPlayerIndex = (m_team)?(m_team->getControllingPlayer()->getPlayerIndex()):-1; + if (oldPlayerIndex != newPlayerIndex) + TheInGameUI->objectChangedTeam(this, oldPlayerIndex, newPlayerIndex); +} + +//============================================================================= +enum +{ + BOOBY_TRAP_SCAN_RANGE = 25 +}; +Bool Object::checkAndDetonateBoobyTrap(const Object *victim) +{ + if( !testStatus(OBJECT_STATUS_BOOBY_TRAPPED) ) + return FALSE; + + PartitionFilterAcceptByKindOf kindFilter(MAKE_KINDOF_MASK(KINDOF_BOOBY_TRAP), KINDOFMASK_NONE); + PartitionFilterSameMapStatus filterMapStatus(this); + PartitionFilter *filters[3]; + filters[0] = &kindFilter; + filters[1] = &filterMapStatus; + filters[2] = NULL; + + ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( getPosition(), BOOBY_TRAP_SCAN_RANGE + getGeometryInfo().getBoundingCircleRadius(), + FROM_CENTER_2D, filters, ITER_SORTED_NEAR_TO_FAR ); + MemoryPoolObjectHolder hold(iter);// This is the magic thing that frees the dynamically made iter in its destructor + + Object *ourBoobyTrap = NULL; + for( Object *other = iter->first(); other; other = iter->next() ) + { + if( other->getProducerID() == getID() )// Sticky bombs call the thing they are on their producer for just such an occasion + { + ourBoobyTrap = other; + break; + } + } + + if( ourBoobyTrap ) + { + static NameKeyType key_StickyBombUpdate = NAMEKEY( "StickyBombUpdate" ); + StickyBombUpdate *update = (StickyBombUpdate*)ourBoobyTrap->findUpdateModule( key_StickyBombUpdate ); + if( update ) + { + if( victim && ourBoobyTrap->getControllingPlayer()->getRelationship(victim->getTeam()) == ALLIES ) + return FALSE;// Friends don't touch friends boobies. + + update->detonate(); + return TRUE;// Booby Trapped status will be cleared by stickybomb, as they set it + } + } + + return FALSE; +} + +//============================================================================= +void Object::setStatus( ObjectStatusMaskType objectStatus, Bool set ) +{ + ObjectStatusMaskType oldStatus = m_status; + + if (set) + m_status.set( objectStatus ); + else + m_status.clear( objectStatus ); + + if (m_status != oldStatus) + { + if( set && objectStatus.test( OBJECT_STATUS_REPULSOR ) && m_repulsorHelper != NULL ) + { + // Damaged repulsable civilians scare (repulse) other civs, but only + // for a short amount of time... use the repulsor helper to turn off repulsion shortly. + m_repulsorHelper->sleepUntil(TheGameLogic->getFrame() + 2*LOGICFRAMES_PER_SECOND); + } + + if( objectStatus.test( OBJECT_STATUS_STEALTHED ) || objectStatus.test( OBJECT_STATUS_DETECTED ) || objectStatus.test( OBJECT_STATUS_DISGUISED ) ) + { + //Kris: Aug 20, 2003 + //When any of the three key status bits for stealth go on or off, then handle partition updates for vision. + if( getTemplate()->getShroudRevealToAllRange() > 0.0f ) + { + handlePartitionCellMaintenance(); + } + } + + + // when an object's construction status changes, it needs to have its partition data updated, + // in order to maintain the shroud correctly. + if( m_status.test( OBJECT_STATUS_UNDER_CONSTRUCTION ) != oldStatus.test( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) + { + + // CHECK FOR MINES, AND DETONATE THEM NOW + ObjectIterator *iter = + ThePartitionManager->iteratePotentialCollisions( getPosition(), getGeometryInfo(), getOrientation() ); + MemoryPoolObjectHolder hold( iter ); + Object *them; + for( them = iter->first(); them; them = iter->next() ) + { + if (them->isKindOf( KINDOF_MINE )) + { + //DETONATE ANY ENEMY MINES, OR DELETE FRIENDLY ONES + Relationship r = getRelationship(them); + if (r == ENEMIES) + { + them->kill(); // detonate mine + } + else + { + TheGameLogic->destroyObject(them); + } + } + }// next object + + if (m_partitionData) + m_partitionData->makeDirty(true); + } + + } + +} + +//============================================================================= +void Object::setScriptStatus( ObjectScriptStatusBit bit, Bool set ) +{ + UnsignedInt oldScriptStatus = m_scriptStatus; + + if( set ) + { + m_scriptStatus |= bit; + } + else + { + m_scriptStatus &= ~bit; + } + + if( m_scriptStatus != oldScriptStatus ) + { + if( (m_scriptStatus & OBJECT_STATUS_SCRIPT_DISABLED) != (oldScriptStatus & OBJECT_STATUS_SCRIPT_DISABLED) ) + { + if( m_partitionData ) + { + // if an object becomes disabled or unpowered, then you have to update its partition data because it will + // change how far it can see. + m_partitionData->makeDirty(true); + } + if( m_scriptStatus & OBJECT_STATUS_SCRIPT_DISABLED ) + { + //I am now disabled, so tell the main game engine! + setDisabled( DISABLED_SCRIPT_DISABLED ); + } + else + { + //I am no longer disabled, so tell the main game engine! + clearDisabled( DISABLED_SCRIPT_DISABLED ); + } + } + if( (m_scriptStatus & OBJECT_STATUS_SCRIPT_UNPOWERED) != (oldScriptStatus & OBJECT_STATUS_SCRIPT_UNPOWERED) ) + { + if( m_partitionData ) + { + // if an object becomes disabled or unpowered, then you have to update its partition data because it will + // change how far it can see. + m_partitionData->makeDirty(true); + } + if( m_scriptStatus & OBJECT_STATUS_SCRIPT_UNPOWERED ) + { + //I am now underpowered, so tell the main game engine! + setDisabled( DISABLED_SCRIPT_UNDERPOWERED ); + } + else + { + //I am no longer undperpowered, so tell the main game engine! + clearDisabled( DISABLED_SCRIPT_UNDERPOWERED ); + } + } + } +} + +//============================================================================= +Bool Object::canCrushOrSquish(Object *otherObj, CrushSquishTestType testType ) const +{ + DEBUG_ASSERTCRASH(this, ("null this in canCrushOrSquish")); + + if( !otherObj ) + { + //Can't crush anything. + return false; + } + + if( isDisabledByType( DISABLED_UNMANNED ) ) + { + //Unmanned vehicles cannot crush troops. This was happening when Jarmen Kell sniped + //the vehicle and booted the guys out while still moving, as the vehicle is now + //on a different team. + return false; + } + + UnsignedByte crusherLevel = getCrusherLevel(); + + // order matters: we want to know if I consider it to be an ally, not vice versa + if( getRelationship( otherObj ) == ALLIES ) + { + //Friends don't let friends crush friends. + return false; + } + + if( !crusherLevel ) + { + //Can't crush anything! + return false; + } + + //Test this case for generic infantry getting squished by vehicles! + if( testType == TEST_SQUISH_ONLY || testType == TEST_CRUSH_OR_SQUISH ) + { + + //**************************************************************************************** + //NOTE: This section of code is used by the pathfinder to determine if the object should + // move to the target. I don't think it's the right place to check for this because + // the semantics check to see if we can squish something -- not approach it. However + // I'm not moving it for fear of some major breakage! -- KM + //Bool squisher = crusherLevel > 0; + //if( !squisher ) + //{ + // Weapon *weapon = getCurrentWeapon(); + // if( weapon && weapon->isContactWeapon() ) + // { + // squisher = true; + // } + //} + //if( squisher ) + //NOTE2: *** IF YOU REENABLE THIS CODE -- Move the "if( !crusherLevel ) return false" below + // this squish section. + //**************************************************************************************** + { + // See if other is squishable + static NameKeyType key_squish = NAMEKEY( "SquishCollide" ); + if( otherObj->findModule( key_squish ) ) + { + return true; // squishable. + } + } + } + + + UnsignedByte crushableLevel = otherObj->getCrushableLevel(); + + if( testType == TEST_CRUSH_ONLY || testType == TEST_CRUSH_OR_SQUISH ) + { + if( crusherLevel > crushableLevel ) + { + return true; + } + } + + return false; +} + +//------------------------------------------------------------------------------------------------- +UnsignedByte Object::getCrusherLevel() const +{ + return getTemplate()->getCrusherLevel(); +} + +//------------------------------------------------------------------------------------------------- +UnsignedByte Object::getCrushableLevel() const +{ + return getTemplate()->getCrushableLevel(); +} + + +// ------------------------------------------------------------------------------------------------ +/** Topple an object, if possible */ +// ------------------------------------------------------------------------------------------------ +void Object::topple( const Coord3D *toppleDirection, Real toppleSpeed, UnsignedInt options ) +{ + static NameKeyType key_ToppleUpdate = NAMEKEY("ToppleUpdate"); + + ToppleUpdate* toppleUpdate = (ToppleUpdate*)findModule(key_ToppleUpdate); + if( toppleUpdate && toppleUpdate->isAbleToBeToppled() ) + { + + // apply the topple force + toppleUpdate->applyTopplingForce( toppleDirection, toppleSpeed, options ); + + } // end if + +} // end topple + +//============================================================================= +void Object::setArmorSetFlag(ArmorSetType ast) +{ + m_body->setArmorSetFlag(ast); +} + +//============================================================================= +void Object::clearArmorSetFlag(ArmorSetType ast) +{ + m_body->clearArmorSetFlag(ast); +} + +//============================================================================= +Bool Object::testArmorSetFlag(ArmorSetType ast) const +{ + return m_body->testArmorSetFlag(ast); +} + +//============================================================================= +void Object::reloadAllAmmo(Bool now) +{ + m_weaponSet.reloadAllAmmo(this, now); +} + +//============================================================================= +Bool Object::isOutOfAmmo() const +{ + return m_weaponSet.isOutOfAmmo(); +} + +//============================================================================= +Bool Object::hasAnyWeapon() const +{ + return m_weaponSet.hasAnyWeapon(); +} + +//============================================================================= +Bool Object::hasAnyDamageWeapon() const +{ + //First check to see if we have any weapons -- if not return false. + if( !m_weaponSet.hasAnyDamageWeapon() ) + { + return FALSE; + } + return TRUE; +} + +//============================================================================= +UnsignedInt Object::getMostPercentReadyToFireAnyWeapon() const +{ + return m_weaponSet.getMostPercentReadyToFireAnyWeapon(); +} + +//============================================================================= +Bool Object::getWeaponInWeaponSlotSyncedToSlot(WeaponSlotType thisSlot, WeaponSlotType otherSlot) const +{ + CommandSourceMask mask = getWeaponInWeaponSlotCommandSourceMask(thisSlot); + + //Bool value0a = mask & (1 << CMD_SYNC_TO_PRIMARY); + //Bool value0b = (otherSlot == PRIMARY_WEAPON); + //Bool value1a = mask & (1 << CMD_SYNC_TO_SECONDARY); + //Bool value1b = (otherSlot == SECONDARY_WEAPON); + //Bool value2a = mask & (1 << CMD_SYNC_TO_TERTIARY); + //Bool value2b = (otherSlot == TERTIARY_WEAPON); + + //DEBUG_LOG(("- getWeaponInWeaponSlotSyncedToSlot (thisSlot=%d, otherSlot=%d): mask = %d --> value0 = %d/%d, value1 = %d/%d, value2 = %d/%d.\n", + // thisSlot, otherSlot, static_cast(mask), value0a, value0b, value1a, value1b, value2a, value2b)); + + return ((Int)mask >= 0) && + ((mask & (1 << CMD_SYNC_TO_PRIMARY) && otherSlot == PRIMARY_WEAPON) || + (mask & (1 << CMD_SYNC_TO_SECONDARY) && otherSlot == SECONDARY_WEAPON) || + (mask & (1 << CMD_SYNC_TO_TERTIARY) && otherSlot == TERTIARY_WEAPON) || + (mask & (1 << CMD_SYNC_TO_FOUR) && otherSlot == WEAPON_FOUR) || + (mask & (1 << CMD_SYNC_TO_FIVE) && otherSlot == WEAPON_FIVE) || + (mask & (1 << CMD_SYNC_TO_SIX) && otherSlot == WEAPON_SIX) || + (mask & (1 << CMD_SYNC_TO_SEVEN) && otherSlot == WEAPON_SEVEN) || + (mask & (1 << CMD_SYNC_TO_EIGHT) && otherSlot == WEAPON_EIGHT)); + +} + +//============================================================================= +Bool Object::hasWeaponToDealDamageType(DamageType typeToDeal) const +{ + return m_weaponSet.hasWeaponToDealDamageType(typeToDeal); +} + +//============================================================================= +Real Object::getLargestWeaponRange() const +{ + Real retVal = -1; + for (Int i = PRIMARY_WEAPON; i < WEAPONSLOT_COUNT; ++i) { + Weapon* weapon = m_weaponSet.getWeaponInWeaponSlot((WeaponSlotType)i); + if (!weapon) { + continue; + } + + Real tmpVal = weapon->getAttackRange(this); + if (tmpVal > retVal) { + retVal = tmpVal; + } + } + return retVal; +} + +//============================================================================= +void Object::setFiringConditionForCurrentWeapon() const +{ + if (m_drawable) + { + WeaponSlotType wslot = m_weaponSet.getCurWeaponSlot(); + ModelConditionFlags c = m_weaponSet.getModelConditionForWeaponSlot(wslot, WSF_FIRING); + m_drawable->clearAndSetModelConditionFlags(s_allWeaponFireFlags[wslot], c); + } +} + +//============================================================================= +void Object::setModelConditionState( ModelConditionFlagType a ) +{ + if (m_drawable) + { + m_drawable->setModelConditionState(a); + } +} + +//============================================================================= +void Object::clearModelConditionState( ModelConditionFlagType a ) +{ + if (m_drawable) + { + m_drawable->clearModelConditionState(a); + } +} + +//============================================================================= +void Object::clearAndSetModelConditionState( ModelConditionFlagType clr, ModelConditionFlagType set ) +{ + if (m_drawable) + { + m_drawable->clearAndSetModelConditionState(clr, set); + } +} + +//============================================================================= +void Object::clearModelConditionFlags( const ModelConditionFlags& clr ) +{ + if (m_drawable) + { + m_drawable->clearModelConditionFlags(clr); + } +} + +//============================================================================= +void Object::setModelConditionFlags( const ModelConditionFlags& set ) +{ + if (m_drawable) + { + m_drawable->setModelConditionFlags(set); + } +} + +//============================================================================= +void Object::clearAndSetModelConditionFlags( const ModelConditionFlags& clr, const ModelConditionFlags& set ) +{ + if (m_drawable) + { + m_drawable->clearAndSetModelConditionFlags(clr, set); + } +} + +//============================================================================= +// Special model states are states that are turned on for a period of time, and +// turned off automatically -- used for cheer, and scripted special moment +// animations. Setting a special state will automatically clear any other +// special states that may be turned on so you can only have one at a time. +//============================================================================= +void Object::setSpecialModelConditionState( ModelConditionFlagType set, UnsignedInt frames ) +{ + clearSpecialModelConditionStates(); + + setModelConditionState( set ); + + if( frames == 0 ) + { + frames = 1; + } + + m_smcUntil = TheGameLogic->getFrame() + frames; + m_smcHelper->sleepUntil(m_smcUntil); +} + +//============================================================================= +void Object::clearSpecialModelConditionStates() +{ + clearModelConditionFlags( MAKE_MODELCONDITION_MASK( MODELCONDITION_SPECIAL_CHEERING ) ); + m_smcUntil = NEVER; +} + +// Lorenzen has some interest in this, ask before deleting +//============================================================================= +//const ModelConditionFlags& Object::getModelConditionFlags() const +//{ +// if (m_drawable) +// { +// return m_drawable->getModelConditionFlags(); +// } +// else +// { +// DEBUG_CRASH(("NULL Drawable at this point, you can't get modelconditionflags now.")); +// static ModelConditionFlags noFlags; +// return noFlags; +// } +//} + +//============================================================================= +Weapon* Object::getCurrentWeapon(WeaponSlotType* wslot) +{ + if (!m_weaponSet.hasAnyWeapon()) + return NULL; + + if (wslot) + *wslot = m_weaponSet.getCurWeaponSlot(); + return m_weaponSet.getCurWeapon(); +} + +//============================================================================= +const Weapon* Object::getCurrentWeapon(WeaponSlotType* wslot) const +{ + if (!m_weaponSet.hasAnyWeapon()) + return NULL; + + if (wslot) + *wslot = m_weaponSet.getCurWeaponSlot(); + return m_weaponSet.getCurWeapon(); +} + +//============================================================================= +Weapon* Object::findWaypointFollowingCapableWeapon() +{ + return m_weaponSet.findWaypointFollowingCapableWeapon(); +} + +//============================================================================= +Bool Object::getAmmoPipShowingInfo(Int& numTotal, Int& numFull) const +{ +/// @todo srj -- may need to cache this inside weaponset. + const Weapon* w = m_weaponSet.findAmmoPipShowingWeapon(); + if (w) + { + numTotal = w->getClipSize(); + numFull = w->getRemainingAmmo(); + return true; + } + else + { + return false; + } +} + +//============================================================================= +/* + NOTE: getAbleToAttackSpecificObject NO LONGER internally calls isAbleToAttack(), + since that isn't an incredibly fast call, and this is called repeatedly in some inner loops + where we already know that isAbleToAttack() == true. so you should always + call isAbleToAttack prior to calling this! (srj) +*/ +CanAttackResult Object::getAbleToAttackSpecificObject( AbleToAttackType t, const Object* target, CommandSourceType commandSource, WeaponSlotType specificSlot ) const +{ + // NO! BAD! WRONG! + // If we can't attack at all, then we cannot attack this + //if (!isAbleToAttack()) + // return FALSE; + + // Otherwise leave it up to our weapons. + return m_weaponSet.getAbleToAttackSpecificObject( t, this, target, commandSource, specificSlot ); +} + +//============================================================================= +//Used for base defenses and otherwise stationary units to see if you can attack a position potentially out of range. +CanAttackResult Object::getAbleToUseWeaponAgainstTarget( AbleToAttackType attackType, const Object *victim, const Coord3D *pos, CommandSourceType commandSource, WeaponSlotType specificSlot ) const +{ + return m_weaponSet.getAbleToUseWeaponAgainstTarget( attackType, this, victim, pos, commandSource, specificSlot ); +} + + +//============================================================================= +Bool Object::chooseBestWeaponForTarget(const Object* target, WeaponChoiceCriteria criteria, CommandSourceType cmdSource ) +{ + return m_weaponSet.chooseBestWeaponForTarget(this, target, criteria, cmdSource ); +} + +//DECLARE_PERF_TIMER(fireCurrentWeapon) +//============================================================================= +void Object::fireCurrentWeapon(Object *target) +{ + //USE_PERF_TIMER(fireCurrentWeapon) + + // victim may have already been destroyed + if (target == NULL) + return; + + Weapon* weapon = m_weaponSet.getCurWeapon(); + if (weapon && (weapon->getStatus() == READY_TO_FIRE)) + { + Bool reloaded = weapon->fireWeapon(this, target); + DEBUG_ASSERTCRASH(m_firingTracker, ("hey, we are firing but have no firing tracker. this is wrong.")); + if (m_firingTracker) + m_firingTracker->shotFired(weapon, target->getID()); + if (reloaded) + releaseWeaponLock(LOCKED_TEMPORARILY); // release any temporary locks. + + friend_setUndetectedDefector( FALSE );// My secret is out + } +} + +//============================================================================= +void Object::fireCurrentWeapon(const Coord3D* pos) +{ + //USE_PERF_TIMER(fireCurrentWeapon) + + if (pos == NULL) + return; + + Weapon* weapon = m_weaponSet.getCurWeapon(); + if (weapon && (weapon->getStatus() == READY_TO_FIRE)) + { + Bool reloaded = weapon->fireWeapon(this, pos); + DEBUG_ASSERTCRASH(m_firingTracker, ("hey, we are firing but have no firing tracker. this is wrong.")); + if (m_firingTracker) + m_firingTracker->shotFired(weapon, INVALID_ID); + if (reloaded) + releaseWeaponLock(LOCKED_TEMPORARILY); // release any temporary locks. + + friend_setUndetectedDefector( FALSE );// My secret is out + } +} + +//============================================================================== +void Object::notifyFiringTrackerShotFired( const Weapon* weaponFired, ObjectID victimID ) +{ + if ( m_firingTracker ) + m_firingTracker->shotFired( weaponFired, victimID ); +} + + +//============================================================================= +void Object::preFireCurrentWeapon( const Object *victim ) +{ + Weapon* weapon = m_weaponSet.getCurWeapon(); + + //If we are going to be capable of firing our weapon NEXT frame, set the pre-attack + //up now. This gets called by AIAttackFireWeaponState::onEnter().. but the update happens + //next frame. + if (weapon && TheGameLogic->getFrame() + 1 >= weapon->getPossibleNextShotFrame() ) + { + weapon->preFireWeapon( this, victim ); + friend_setUndetectedDefector( FALSE );// My secret is out + } +} + +//============================================================================= +void Object::preFireCurrentWeapon(const Coord3D* pos) +{ + Weapon* weapon = m_weaponSet.getCurWeapon(); + + //If we are going to be capable of firing our weapon NEXT frame, set the pre-attack + //up now. This gets called by AIAttackFireWeaponState::onEnter().. but the update happens + //next frame. + if (weapon && TheGameLogic->getFrame() + 1 >= weapon->getPossibleNextShotFrame()) + { + weapon->preFireWeapon(this, pos); + friend_setUndetectedDefector(FALSE);// My secret is out + } +} + +// ============================================================================ +/** Using the firing tracker, return the frame a shot was last fired on */ +// ============================================================================ +UnsignedInt Object::getLastShotFiredFrame() const +{ + UnsignedInt recent = 0; + for (int i = 0; i < WEAPONSLOT_COUNT; ++i) + { + const Weapon* w = m_weaponSet.getWeaponInWeaponSlot((WeaponSlotType)i); + if (w) + { + UnsignedInt when = w->getLastShotFrame(); + if (when > recent) + recent = when; + } + } + return recent; +} + +// ============================================================================ +/** Get the victim ID we last shot at */ +// ============================================================================ +ObjectID Object::getLastVictimID() const +{ + return m_firingTracker ? m_firingTracker->getLastShotVictim() : INVALID_ID; +} + +//============================================================================= +// Object::getRelationship +//============================================================================= +Relationship Object::getRelationship(const Object *that) const +{ + const Team *myTeam = getTeam(); + + if (myTeam && that) + { + if (getIsUndetectedDefector()) + { + return NEUTRAL; // so my AI does not give away my position by auto acquire + } + else if (that->getIsUndetectedDefector()) + { + return ALLIES; // so I treat undetecteddefectors like they were my very own + } + else + { + return myTeam->getRelationship( that->getTeam() ); + } + } + + return NEUTRAL; + +} + +//============================================================================= +// Object::getControllingPlayer +//============================================================================= +Player * Object::getControllingPlayer() const +{ + const Team* myTeam = this->getTeam(); + if (myTeam) + return myTeam->getControllingPlayer(); + + return NULL; +} + +//============================================================================= +void Object::setProducer(const Object* obj) +{ + m_producerID = obj ? obj->getID() : INVALID_ID; +// seems like a good idea, but is not. (srj) +// if (obj) +// m_indicatorColor = obj->m_indicatorColor; +} + +//============================================================================= +void Object::setBuilder( const Object *obj ) +{ + + m_builderID = obj ? obj->getID() : INVALID_ID; + +} + +//============================================================================= +void Object::setCustomIndicatorColor(Color c) +{ + if (m_indicatorColor != c) + { + m_indicatorColor = c; + if (m_drawable) + m_drawable->changedTeam(); + } +} + +//============================================================================= +void Object::removeCustomIndicatorColor() +{ + setCustomIndicatorColor(0); +} + +//============================================================================= +// Object::getIndicatorColor +//============================================================================= +Color Object::getIndicatorColor() const +{ + if (m_indicatorColor == 0) + { + const Team *myTeam = getTeam(); + if (myTeam) + { + const Player* p = myTeam->getControllingPlayer(); + if (p) + { + return p->getPlayerColor(); + } + } + return GameMakeColor(0, 0, 0, 255); + } + else + { + return m_indicatorColor; + } +} + +//============================================================================= +// Object::getNightIndicatorColor - used to make blue/purple easier to see on night models. +//============================================================================= +Color Object::getNightIndicatorColor() const +{ + if (m_indicatorColor == 0) + { + const Team *myTeam = getTeam(); + if (myTeam) + { + const Player* p = myTeam->getControllingPlayer(); + if (p) + { + return p->getPlayerNightColor(); + } + } + return GameMakeColor(0, 0, 0, 255); + } + else + { + return m_indicatorColor; + } +} + +//============================================================================= +// Object::isLocallyControlled +//============================================================================= +Bool Object::isLocallyControlled() const +{ + return getControllingPlayer() == ThePlayerList->getLocalPlayer(); +} + +//============================================================================= +// Object::isLocallyControlled +//============================================================================= +Bool Object::isNeutralControlled() const +{ + return getControllingPlayer() == ThePlayerList->getNeutralPlayer(); +} + +//------------------------------------------------------------------------------------------------- +inline Bool isPosDifferent(const Coord3D* a, const Coord3D* b) +{ + // this is necessary because PhysicsBehavior may generate tiny changes even when + // "standing still", due to roundoff errors. It's important that we only invalidate + // the PartitionManager stuff when the pos/orientation really changes (for efficiency purposes) + // so we must put in some cleverness... + const Real THRESH = 0.01f; + + if (fabs(a->x - b->x) > THRESH) + return true; + + if (fabs(a->y - b->y) > THRESH) + return true; + + if (fabs(a->z - b->z) > THRESH) + return true; + + return false; +} + +//------------------------------------------------------------------------------------------------- +inline Bool isAngleDifferent(Real a, Real b) +{ + // this is necessary because PhysicsBehavior may generate tiny changes even when + // "standing still", due to roundoff errors. It's important that we only invalidate + // the PartitionManager stuff when the pos/orientation really changes (for efficiency purposes) + // so we must put in some cleverness... + + const Real THRESH = 0.01f; // in radians, this is approx 1/2 degree. + + if (fabs(a - b) > THRESH) + return true; + + return false; +} + +//------------------------------------------------------------------------------------------------- +void Object::reactToTurretChange( WhichTurretType turret, Real oldRotation, Real oldPitch ) +{ + Real currentRotation = 0.0f; + Real currentPitch = 0.0f; + if( getAI() ) + { + getAI()->getTurretRotAndPitch( turret, ¤tRotation, ¤tPitch ); + } + Bool rotationChange = (currentRotation != oldRotation); +// Bool pitchChange = (currentPitch != oldPitch); + + if( rotationChange ) + { + if (getContain()) + getContain()->containReactToTransformChange(); + } +} + +//------------------------------------------------------------------------------------------------- +//DECLARE_PERF_TIMER(Object_reactToTransformChange) +void Object::reactToTransformChange(const Matrix3D* oldMtx, const Coord3D* oldPos, Real oldAngle) +{ + //USE_PERF_TIMER(Object_reactToTransformChange) + if(_isnan(getPosition()->x) || _isnan(getPosition()->y) || _isnan(getPosition()->z)) { + DEBUG_CRASH(("Object pos is nan.")); + TheGameLogic->destroyObject(this); + } + if (m_drawable) + { + m_drawable->setTransformMatrix( this->getTransformMatrix() ); + } + + Bool posDiff = isPosDifferent(oldPos, getPosition()); + Bool angDiff = isAngleDifferent(oldAngle, getOrientation()); + + if (posDiff || angDiff) + { + if (m_partitionData) + m_partitionData->makeDirty(true); + + if (getContain()) + getContain()->containReactToTransformChange(); + } + + if (posDiff) + { + setTriggerAreaFlagsForChangeInPosition(); // Update for entered/exited + + Region3D mapExtent; + TheTerrainLogic->getExtent(&mapExtent); + if (mapExtent.isInRegionNoZ(getPosition())) + m_privateStatus &= ~OFF_MAP; + else + m_privateStatus |= OFF_MAP; + } +} + +//------------------------------------------------------------------------------------------------- +ObjectShroudStatus Object::getShroudedStatus(Int playerIndex) const +{ + if (getTemplate()->isKindOf( KINDOF_ALWAYS_VISIBLE )) + return OBJECTSHROUD_CLEAR; + + if (m_partitionData) + return m_partitionData->getShroudedStatus(playerIndex); + + // This can happen for objects removed from the partition system (e.g., + // for soldiers that are garrisoned inside a building). + return OBJECTSHROUD_CLEAR; +} + +//------------------------------------------------------------------------------------------------- +/** Something is attempting to damage this object */ +//------------------------------------------------------------------------------------------------- +void Object::attemptDamage( DamageInfo *damageInfo ) +{ + BodyModuleInterface* body = getBodyModule(); + if (body) + body->attemptDamage( damageInfo ); + + // Process any shockwave forces that might affect this object due to the incurred damage + if (damageInfo->in.m_shockWaveAmount > 0.0f && damageInfo->in.m_shockWaveRadius > 0.0f) + { + //KindOfMaskType immuneToShockwaveKindofs; //NEW RESTRICTIONS ADDED + //immuneToShockwaveKindofs.set(KINDOF_PROJECTILE);// projectiles go idle in midair when they get sw'd //NEW RESTRICTIONS ADDED + //immuneToShockwaveKindofs.set(KINDOF_PRODUCED_AT_HELIPAD);//helicopters go all wonky when they get shockwaved //NEW RESTRICTIONS ADDED + + PhysicsBehavior *behavior = getPhysics(); + if ( behavior && (isAirborneTarget() == FALSE) && (! isKindOf(KINDOF_PROJECTILE) ) ) +// if (behavior && isAnyKindOf( immuneToShockwaveKindofs ) == FALSE )//NEW RESTRICTIONS ADDED + { + // Calculate the shockwave taperoff amount due to distance from ground zero + Real shockWaveScalar = damageInfo->in.m_shockWaveVector.length(); + Real distanceFromCenter = min(1.0f, shockWaveScalar / damageInfo->in.m_shockWaveRadius); + Real distanceTaper = (distanceFromCenter) * (1.0f - damageInfo->in.m_shockWaveTaperOff); + Real shockTaperMult = 1.0f - distanceTaper; + + // Set up the shockwave force to use apply on object + Coord3D shockWaveForce; + shockWaveForce.set( &damageInfo->in.m_shockWaveVector ); + shockWaveForce.normalize(); + shockWaveForce.scale( damageInfo->in.m_shockWaveAmount * shockTaperMult ); + shockWaveForce.z = shockWaveForce.length(); // Apply up force equal to the lateral force for dramatic effect + + // Apply the shock to the object + behavior->applyShock(&shockWaveForce); + + // Add random rotation to the object for drama + + behavior->applyRandomRotation(); + + // Set stunned state due to the shock for the object + behavior->setStunned(true); + + setModelConditionState(MODELCONDITION_STUNNED_FLAILING); + } + } + + + /// @todo track damage dealt/attempted + + // + // if actual damage occurred, and this is an object owned by the local player we + // might do a radar event for under attack. Note that we do not even try + // to do radar events for DAMAGE_PENALTY as that damage type is a type of damage + // that occurs with explicit player knowledge + // + if( damageInfo->out.m_actualDamageDealt > 0.0f && + damageInfo->in.m_damageType != DAMAGE_PENALTY && + damageInfo->in.m_damageType != DAMAGE_HEALING && + getControllingPlayer() && + !BitIsSet(damageInfo->in.m_sourcePlayerMask, getControllingPlayer()->getPlayerMask()) && + m_radarData != NULL && + getControllingPlayer() == ThePlayerList->getLocalPlayer() ) + TheRadar->tryUnderAttackEvent( this ); + +} + +//------------------------------------------------------------------------------------------------- +void Object::attemptHealing(Real amount, const Object* source) +{ + BodyModuleInterface* body = getBodyModule(); + if (body) + { + DamageInfo damageInfo; + damageInfo.in.m_damageType = DAMAGE_HEALING; + damageInfo.in.m_deathType = DEATH_NONE; + damageInfo.in.m_sourceID = source ? source->getID() : INVALID_ID; + damageInfo.in.m_amount = amount; + body->attemptHealing( &damageInfo ); + } +} + +ObjectID Object::getSoleHealingBenefactor( void ) const +{ + UnsignedInt now = TheGameLogic->getFrame(); + if( now > m_soleHealingBenefactorExpirationFrame ) + return INVALID_ID; + + return m_soleHealingBenefactorID; + +} + +Bool Object::attemptHealingFromSoleBenefactor ( Real amount, const Object* source, UnsignedInt duration ) +{///< for the non-stacking healers like ambulance and propaganda + + if( ! source ) // sanity + return FALSE; + + UnsignedInt now = TheGameLogic->getFrame(); + ObjectID id = source->getID(); + +// Either it is ok to accept healing from any who offer or this is my guy, calling again + if( now > m_soleHealingBenefactorExpirationFrame || m_soleHealingBenefactorID == id ) + { + m_soleHealingBenefactorID = id; + m_soleHealingBenefactorExpirationFrame = now + duration; + + BodyModuleInterface* body = getBodyModule(); + if (body) + { + DamageInfo damageInfo; + damageInfo.in.m_damageType = DAMAGE_HEALING; + damageInfo.in.m_deathType = DEATH_NONE; + damageInfo.in.m_sourceID = source ? source->getID() : INVALID_ID; + damageInfo.in.m_amount = amount; + body->attemptHealing( &damageInfo ); + } + + return TRUE; + } + + return FALSE; + +} + + +//------------------------------------------------------------------------------------------------- +Real Object::estimateDamage( DamageInfoInput& damageInfo ) const +{ + BodyModuleInterface* body = getBodyModule(); + if (body) + return body->estimateDamage( damageInfo ); + + return 0.0f; +} + +//------------------------------------------------------------------------------------------------- +/** Do so much damage to an object that it will certainly die */ +//------------------------------------------------------------------------------------------------- +void Object::kill( DamageType damageType, DeathType deathType ) +{ + DamageInfo damageInfo; + + // Do unmodifiable damage equal to their max health to kill. + damageInfo.in.m_damageType = damageType; + damageInfo.in.m_deathType = deathType; + damageInfo.in.m_sourceID = INVALID_ID; + damageInfo.in.m_amount = getBodyModule()->getMaxHealth(); + damageInfo.in.m_kill = TRUE; // Triggers object to die no matter what. + attemptDamage( &damageInfo ); + + DEBUG_ASSERTCRASH(!damageInfo.out.m_noEffect, ("Attempting to kill an unKillable object (InactiveBody?)\n")); + +} // end kill + +//------------------------------------------------------------------------------------------------- +/** Restore max health to this Object */ +//------------------------------------------------------------------------------------------------- +void Object::healCompletely() +{ + attemptHealing(HUGE_DAMAGE_AMOUNT, NULL); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void Object::setEffectivelyDead(Bool dead) +{ + if (dead) + BitSet(m_privateStatus, EFFECTIVELY_DEAD); + else + BitClear(m_privateStatus, EFFECTIVELY_DEAD); + + if (dead) + { + if( m_radarData ) + TheRadar->removeObject( this ); + } +} + +//------------------------------------------------------------------------------------------------- +void Object::setCaptured(Bool isCaptured) +{ + if (isCaptured) + BitSet(m_privateStatus, CAPTURED); + else + { + DEBUG_LOG(("Clearing Captured Status. This should never happen. jkmcd")); + BitClear(m_privateStatus, CAPTURED); + } + + // No need to see if we should skip updates, this flag has no effect on skipping updates. +} + + + +//------------------------------------------------------------------------------------------------- +Bool Object::isStructure(void) const +{ + return isKindOf(KINDOF_STRUCTURE); +} + +//------------------------------------------------------------------------------------------------- +Bool Object::isFactionStructure(void) const +{ + return isAnyKindOf( KINDOFMASK_FS ); +} + +//------------------------------------------------------------------------------------------------- +Bool Object::isNonFactionStructure(void) const +{ + return isStructure() && !isFactionStructure(); +} + +void localIsHero( Object *obj, void* userData ) +{ + Bool *hero = (Bool*)userData; + + if( obj && obj->isKindOf( KINDOF_HERO ) ) + { + *hero = TRUE; + } +} + +//------------------------------------------------------------------------------------------------- +Bool Object::isHero(void) const +{ + ContainModuleInterface *contain = getContain(); + if( contain ) + { + Bool heroInside = FALSE; + contain->iterateContained( localIsHero, (void*)(&heroInside), FALSE ); + if( heroInside ) + { + return TRUE; + } + } + return isKindOf( KINDOF_HERO ); +} + +//------------------------------------------------------------------------------------------------- +void Object::setReceivingDifficultyBonus(Bool receive) +{ + if (receive == m_isReceivingDifficultyBonus) { + return; + } + + m_isReceivingDifficultyBonus = receive; + getControllingPlayer()->friend_applyDifficultyBonusesForObject(this, m_isReceivingDifficultyBonus); +} + +//------------------------------------------------------------------------------------------------- +//- DISABLEDNESS STUFF ---------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void Object::setDisabled( DisabledType type ) +{ + setDisabledUntil(type, FOREVER); +} + +//------------------------------------------------------------------------------------------------- +void Object::setDisabledUntil( DisabledType type, UnsignedInt frame ) +{ + Bool edgeCase = !isDisabled(); + + if( type < 0 || type >= DISABLED_COUNT ) + { + DEBUG_CRASH( ("Invalid disabled type value %d specified -- doesn't not exist!", type ) ); + return; + } + + //Handle audio events! + AudioEventRTS sound; + if( type == DISABLED_UNMANNED && !isKindOf( KINDOF_DRONE ) ) + { + //We've been sniped! Play a splatter sound for the pilot losing his face. + sound = TheAudio->getMiscAudio()->m_splatterVehiclePilotsBrain; + sound.setPosition( getPosition() ); + TheAudio->addAudioEvent( &sound ); + } + else if( type == DISABLED_UNDERPOWERED || type == DISABLED_EMP || type == DISABLED_SUBDUED || type == DISABLED_HACKED ) + { + //We've lost power -- make sure we aren't already out of power as the sounds shouldn't happen + //if you were already disabled. + if( !isDisabledByType( DISABLED_UNDERPOWERED ) && + !isDisabledByType( DISABLED_EMP ) && + !isDisabledByType( DISABLED_SUBDUED ) && + !isDisabledByType( DISABLED_HACKED ) ) + { + if( isKindOf( KINDOF_STRUCTURE ) ) + { + sound = TheAudio->getMiscAudio()->m_buildingDisabled; + sound.setPosition( getPosition() ); + TheAudio->addAudioEvent( &sound ); + } + else if( isKindOf( KINDOF_VEHICLE ) ) + { + sound = TheAudio->getMiscAudio()->m_vehicleDisabled; + sound.setPosition( getPosition() ); + TheAudio->addAudioEvent( &sound ); + } + } + } + + if( m_disabledTillFrame[ type ] != frame ) + { + // an edge-test for disabledness, for type. This INCREMENTS m_pauseCount + // srj sez: HELD nevers disables special powers. + if ( type != DISABLED_HELD && !isDisabledByType( type ) ) + pauseAllSpecialPowers( TRUE ); + + m_disabledTillFrame[ type ] = frame; + m_disabledMask.set( type, frame > TheGameLogic->getFrame() ); + + if( m_drawable ) + { + if( isDisabled() ) + { + // Held does not tint anybody. If we are multiply disabled, the other setting will hit the tint, + // and in clear, only-held and not-disabled are both causes to untint. + // Doh. Also shouldn't be tinting when disabled by scripting. + // Doh^2. Also shouldn't be CLEARING tinting if we're disabling by held or script disabledness + // Doh^3. Unmanned is no tint too + if( type != DISABLED_HELD && type != DISABLED_SCRIPT_DISABLED && type != DISABLED_UNMANNED && type != DISABLED_TELEPORT && type != DISABLED_CHRONO) + { + m_drawable->setTintStatus( TINT_STATUS_DISABLED ); + } + } + } + + ContainModuleInterface *contain = getContain(); + if ( contain ) + { + Object *rider = (Object*)contain->friend_getRider(); + if ( rider ) + { + rider->setDisabledUntil(type, frame); + } + } + + if ( isKindOf( KINDOF_SPAWNS_ARE_THE_WEAPONS ) ) + { + SpawnBehaviorInterface *sbi = this->getSpawnBehaviorInterface(); + if ( sbi ) + { + //Kris: Patch 1.01 - November 12, 2003 + //Actually, we want to disable the slaves, not order them to go idle! This fix was made to + //stinger sites getting hit by an EMP to prevent the soldiers from attacking. + //sbi->orderSlavesToGoIdle( CMD_FROM_AI ); // the canattack() will take care of any future attempts to fire + sbi->orderSlavesDisabledUntil( type, frame ); + } + + } + + } + + if( type == DISABLED_UNMANNED && !isKindOf( KINDOF_DRONE ) ) + { + //strange but true: If I am a carbomb, + //my driver actually has a dead-man's + //trigger for my dynamite... + //If he gets sniped, I blow up! Wheeee! + + WeaponSetFlags flags; + flags.set( WEAPONSET_CARBOMB ); + const WeaponTemplateSet* set = getTemplate()->findWeaponTemplateSet( flags ); + if( set && set->testWeaponSetFlag( WEAPONSET_CARBOMB ) ) + { + Object* sniper = TheGameLogic->findObjectByID( getBodyModule()->getLastDamageInfo()->in.m_sourceID ); + if ( sniper ) + sniper->scoreTheKill( this ); + + kill(); + } + else + { + //This vehicle's pilot has been sniped, so we want to clear the veterancy rating (if any) + ExperienceTracker *xpTracker = getExperienceTracker(); + if( xpTracker ) + { + xpTracker->setExperienceAndLevel( 0, FALSE ); + } + //Not only that, but it also loses any healing bonuses it may have earned in its prior life + { + static const NameKeyType key_AutoHealBehavior = NAMEKEY("AutoHealBehavior"); + AutoHealBehavior* autoHeal = (AutoHealBehavior*)(findUpdateModule( key_AutoHealBehavior )); + if (autoHeal) + autoHeal->undoUpgrade(); + + + } + } + + } + + // This will only be called if we were NOT disabled before coming into this function. + if (edgeCase) { + onDisabledEdge(true); + } +} + +//------------------------------------------------------------------------------------------------- +UnsignedInt Object::getDisabledUntil( DisabledType type ) const +{ + if( type == DISABLED_ANY ) + { + UnsignedInt highestFrame = 0; + //Iterate through each disabled type and return the one with the highest frame. + for( Int i = 0; i < DISABLED_COUNT; i++ ) + { + if( m_disabledMask.test( i ) && m_disabledTillFrame[ i ] > highestFrame ) + { + highestFrame = m_disabledTillFrame[ i ]; + } + } + return highestFrame; + } + else if( m_disabledMask.test( type ) ) + { + //Specific query. + return m_disabledTillFrame[ type ]; + } + //Not disabled. + return 0; +} + +//------------------------------------------------------------------------------------------------- +Bool Object::clearDisabled( DisabledType type ) +{ + if( type < 0 || type >= DISABLED_COUNT ) + { + DEBUG_CRASH( ("Invalid disabled type value %d specified -- doesn't not exist!", type ) ); + return FALSE; + } + + if (!isDisabledByType(type)) { + return FALSE; + } + + if( type == DISABLED_UNDERPOWERED || type == DISABLED_EMP || type == DISABLED_SUBDUED || type == DISABLED_HACKED ) + { + //We've regained power-- make sure we aren't still disabled by another type. + AudioEventRTS sound; + if( (!isDisabledByType( DISABLED_UNDERPOWERED ) || type == DISABLED_UNDERPOWERED ) && + (!isDisabledByType( DISABLED_EMP ) || type == DISABLED_EMP ) && + (!isDisabledByType( DISABLED_SUBDUED ) || type == DISABLED_SUBDUED ) && + (!isDisabledByType( DISABLED_HACKED ) || type == DISABLED_HACKED ) ) + { + if( isKindOf( KINDOF_STRUCTURE ) ) + { + sound = TheAudio->getMiscAudio()->m_buildingReenabled; + sound.setPosition( getPosition() ); + TheAudio->addAudioEvent( &sound ); + } + else if( isKindOf( KINDOF_VEHICLE ) ) + { + sound = TheAudio->getMiscAudio()->m_vehicleReenabled; + sound.setPosition( getPosition() ); + TheAudio->addAudioEvent( &sound ); + } + } + } + + + // an edge-test for disabledness, for type. This DECREMENTS m_pauseCount + // srj sez: HELD nevers disables special powers. + if ( type != DISABLED_HELD && isDisabledByType( type ) ) + pauseAllSpecialPowers( FALSE ); + + ContainModuleInterface *contain = getContain(); + if ( contain ) + { + // We explicitly pass stuff in up in the set, so we need to turn it off if it is a forever type + Object *rider = (Object*)contain->friend_getRider(); + if( rider && (m_disabledTillFrame[ type ] == FOREVER) ) + { + rider->clearDisabled(type); + } + } + + if ( isKindOf( KINDOF_SPAWNS_ARE_THE_WEAPONS ) ) + { + SpawnBehaviorInterface *sbi = this->getSpawnBehaviorInterface(); + if ( sbi ) + { + //Kris: Patch 1.02 - December 17, 2003 + //Make sure slaves can recover from being disabled by subdual (stinger site soldier case) + sbi->orderSlavesToClearDisabled( type ); + } + + } + + m_disabledTillFrame[ type ] = NEVER; + m_disabledMask.set( type, 0 ); + + DisabledMaskType exceptions; + exceptions.set(DISABLED_HELD); + exceptions.set(DISABLED_SCRIPT_DISABLED); + exceptions.set(DISABLED_UNMANNED); + exceptions.set(DISABLED_TELEPORT); + exceptions.set(DISABLED_CHRONO); + + DisabledMaskType myFlagsMinusExceptions = getDisabledFlags(); + myFlagsMinusExceptions.clearAndSet(exceptions, DISABLEDMASK_NONE); + + // to clarify, if I am NOT disabled by anything other than DISABLED_HELD, or DISABLED_SCRIPT_DISABLED + + // to clarify, count inverse intersection gives you the number of exceptions you don't have, + // and has nothing to do with checking other disabled types +// if( !isDisabled() || getDisabledFlags().countInverseIntersection( exceptions ) == 0 ) + if( myFlagsMinusExceptions.count() == 0 ) + { + // I have no disabled flag that is not one of the exceptions above. + if (m_drawable) + m_drawable->clearTintStatus( TINT_STATUS_DISABLED ); + } + + checkDisabledStatus();// in case we just edged + + // if we're no longer disabled by anything, then call the edge function. + if (!isDisabled()) { + onDisabledEdge(false); + } + return TRUE; +} + + +//------------------------------------------------------------------------------------------------- +//Checks any timers and clears disabled statii that have expired. +//------------------------------------------------------------------------------------------------- +void Object::checkDisabledStatus() +{ + UnsignedInt now = TheGameLogic->getFrame(); + for( int i = 0; i < DISABLED_COUNT; i++ ) + { + DisabledType type = (DisabledType)i; + if( isDisabledByType( type ) ) + { + if ( now >= m_disabledTillFrame[ i ] ) + { + clearDisabled( type ); // This will also DECREMENT m_pauseCount in all specialpowers + m_disabledMask.set( type, 0 ); + } + } + } +} + +//------------------------------------------------------------------------------------------------- +void Object::pauseAllSpecialPowers( const Bool disabling ) const +{ + for (BehaviorModule** m = m_behaviors; *m; ++m) + { + SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); + if (!sp) + continue; + + sp->pauseCountdown( disabling );// So it will pause if we are disabling. + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +/** Clear the previous entered/exited flags. */ +//------------------------------------------------------------------------------------------------- +void Object::updateTriggerAreaFlags() +{ + Int j = 0; + // Update the flags, and remove any trigger areas that this object isn't inside. + for (Int i=0; igetCollide(); + if (!collide) + continue; + + // check each time thru the loop, in case a collide module sets it + if( getStatusBits().test( OBJECT_STATUS_NO_COLLISIONS ) ) + { +#ifdef DEBUG_CRC + //DEBUG_LOG(("Object::onCollide() - OBJECT_STATUS_NO_COLLISIONS set\n")); +#endif + break; + } +#ifdef DEBUG_CRC + //DEBUG_LOG(("Object::onCollide() - calling collide module\n")); +#endif + collide->onCollide(other, loc, normal); + } +} + +//------------------------------------------------------------------------------------------------- +Bool Object::isSalvageCrate() const +{ + for( BehaviorModule** m = m_behaviors; *m; ++m ) + { + CollideModuleInterface* collide = (*m)->getCollide(); + if( collide && collide->isSalvageCrateCollide() ) + { + return true; + } + } + return false; +} + +//------------------------------------------------------------------------------------------------- +/** + Our owning player is telling us to recheck our UpgradeModules, as an upgrade has completed + */ +void Object::updateUpgradeModules() +{ + if( testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) ) + return; // No upgrade can run if we are under construction. The three places that clear UnderConstruction will re-update us. + + if( testStatus( OBJECT_STATUS_DESTROYED ) ) + return; // Patch 1.03 -- Fixes crash when you upgrade a fake GLA command center to a real one if (toxic or demo). + + if( getControllingPlayer() == NULL ) + return; // This can only happen in game teardown. No upgrades for you without a player. Weird crashes are bad. + + UpgradeMaskType playerMask = getControllingPlayer()->getCompletedUpgradeMask(); + UpgradeMaskType objectMask = getObjectCompletedUpgradeMask(); + UpgradeMaskType maskToCheck = playerMask; + maskToCheck.set( objectMask ); + // We need to add in all of the already owned upgrades to handle "AND" requiring upgrades. + // We combine all the masks in case someone has a Object AND Player combination + + for (BehaviorModule** module = m_behaviors; *module; ++module) + { + UpgradeModuleInterface* upgrade = (*module)->getUpgrade(); + if (!upgrade) + continue; + + if( !upgrade->isAlreadyUpgraded() ) + { + upgrade->attemptUpgrade( maskToCheck ); + } + } +} + +//------------------------------------------------------------------------------------------------- +//This function sucks. +//It was added for objects that can disguise as other objects and contain upgraded subobject overrides. +//A concrete example is the bomb truck. Different payloads are displayed based on which upgrades have been +//made. When the bomb truck disguises as something else, these subobjects are lost because the vector is +//stored in W3DDrawModule. When we revert back to the original bomb truck, we call this function to +//recalculate those upgraded subobjects. +//------------------------------------------------------------------------------------------------- +void Object::forceRefreshSubObjectUpgradeStatus() +{ + for (BehaviorModule** module = m_behaviors; *module; ++module) + { + UpgradeModuleInterface* upgrade = (*module)->getUpgrade(); + if (!upgrade) + continue; + + if( upgrade->isSubObjectsUpgrade() ) + { + upgrade->forceRefreshUpgrade(); + } + } +} + +//------------------------------------------------------------------------------------------------- +/** Returns whether an object entered or exited an area. */ +//------------------------------------------------------------------------------------------------- +Bool Object::didEnterOrExit() const +{ + if (isKindOf(KINDOF_INERT)) { + return FALSE; + } + // note that this needs to return true if we + // entered or exited on the current frame OR + // the previous frame... since the current execution + // order is ScriptEngine, then ObjectUpdates, + // enter/exits detected in ObjectUpdate on frame N + // won't be noticed by the ScriptEngine till frame N+1. + UnsignedInt now = TheGameLogic->getFrame(); + return m_enteredOrExitedFrame == now || m_enteredOrExitedFrame == now - 1; +} + +//------------------------------------------------------------------------------------------------- +/** Returns whether an object entered an area. */ +//------------------------------------------------------------------------------------------------- +Bool Object::didEnter(const PolygonTrigger *pTrigger) const +{ + if (!didEnterOrExit()) + return false; + + DEBUG_ASSERTCRASH(!isKindOf(KINDOF_INERT), ("Asking whether an inert object entered or exited. This is invalid.\n")); + + for (Int i=0; igetUpdateExitInterface()) != NULL ) + break; + } + + // If you don't have a fancy one, you may have one from your contain module, + // since if you can contain something, they will need to get out. + if( exitInterface == NULL ) + { + ContainModuleInterface *cmod = getContain(); + if( cmod ) + { + exitInterface = cmod->getContainExitInterface(); + } + } + + return exitInterface; + +} // end getObjectExitInterface + +//------------------------------------------------------------------------------------------------- +/** Checks the object against trigger areas when the position changes. */ +//------------------------------------------------------------------------------------------------- +void Object::setTriggerAreaFlagsForChangeInPosition() +{ + // projectiles cannot trigger areas. (jkmcd) + // neither can inert objects, like the radar ping, etc. (jkmcd) + if (isKindOf(KINDOF_PROJECTILE) || isKindOf(KINDOF_INERT)) + return; + + ICoord3D iPos; + Coord3D pos = *getPosition(); + iPos.x = REAL_TO_INT(pos.x); + iPos.y = REAL_TO_INT(pos.y); + iPos.z = 0; // Trigger areas compare on xy only. + if (m_iPos.x == iPos.x && m_iPos.y == iPos.y) + { + return; // didn't move enough to change integer position. + } + + if (!isKindOf(KINDOF_IMMOBILE)) { + if (isKindOf(KINDOF_INFANTRY) || isKindOf(KINDOF_VEHICLE) ) { + TheGameClient->notifyTerrainObjectMoved(this); + } + } + + if (getAIUpdateInterface()) + { + TheAI->pathfinder()->updatePos(this, getPosition()); + } + + UnsignedInt now = TheGameLogic->getFrame(); + if (m_enteredOrExitedFrame != 0 && m_enteredOrExitedFrame != now) + updateTriggerAreaFlags(); + + // Check for exited. + Int i; + for (i=0; ipointInTrigger(m_iPos)) + { + m_triggerInfo[i].isInside = false; + m_triggerInfo[i].exited = true; + m_enteredOrExitedFrame = now; + if (m_team) + m_team->setEnteredExited(); + TheGameLogic->updateObjectsChangedTriggerAreas(); +#ifdef RTS_DEBUG + //TheScriptEngine->AppendDebugMessage("Object exited.", false); +#endif + } + } + + m_iPos = iPos; + + for (const PolygonTrigger *pTrig = PolygonTrigger::getFirstPolygonTrigger(); pTrig; pTrig = pTrig->getNext()) + { + Bool skip = false; + for (i = 0; i < m_numTriggerAreasActive; i++) + { + if (m_triggerInfo[i].pTrigger == pTrig) + { + // Already handled this one in the check for exited above. + skip = true; + break; + } + } + if (skip) + continue; + if (pTrig->pointInTrigger(m_iPos)) + { + if (m_numTriggerAreasActive < MAX_TRIGGER_AREA_INFOS) + { + m_triggerInfo[m_numTriggerAreasActive].isInside = true; + m_triggerInfo[m_numTriggerAreasActive].entered = true; + m_triggerInfo[m_numTriggerAreasActive].exited = false; + m_triggerInfo[m_numTriggerAreasActive].pTrigger = pTrig; + m_enteredOrExitedFrame = now; + if (m_team) + m_team->setEnteredExited(); + TheGameLogic->updateObjectsChangedTriggerAreas(); + ++m_numTriggerAreasActive; +#ifdef RTS_DEBUG + //TheScriptEngine->AppendDebugMessage("Object entered.", false); +#endif + } + else + { + // Shouldn't happen. + static Bool didWarn = false; + if (!didWarn) + { + didWarn = true; + TheScriptEngine->AppendDebugMessage("***WARNING - Too many nested trigger areas. ***", true); + } + } + } + + } + +} + + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool Object::isInList(Object **pListHead) const +{ + Bool result = m_prev || m_next || *pListHead == this; +#ifdef INTENSE_DEBUG + Bool found = false; + for (Object* o = *pListHead; o; o = o->m_next) + { + if (o == this) + { + found = true; + break; + } + } + DEBUG_ASSERTCRASH(found==result,("inconsistent links in Object::isInList")); +#endif + return result; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void Object::prependToList(Object **pListHead) +{ + DEBUG_ASSERTCRASH(!isInList(pListHead), ("obj is already in a list")); + + m_prev = NULL; + m_next = *pListHead; + if (*pListHead) + (*pListHead)->m_prev = this; + *pListHead = this; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void Object::setLayer(PathfindLayerEnum layer) +{ + if (layer!=m_layer) { +#define no_SET_LAYER_INTENSE_DEBUG +#ifdef SET_LAYER_INTENSE_DEBUG + DEBUG_LOG(("Changing layer from %d to %d\n", m_layer, layer)); + if (m_layer != LAYER_GROUND) { + if (TheTerrainLogic->objectInteractsWithBridgeLayer(this, m_layer)) { + DEBUG_CRASH(("Probably shouldn't be chaging layer. jba.")); + } + } +#endif + TheAI->pathfinder()->removePos(this); + m_layer = layer; + TheAI->pathfinder()->updatePos(this, getPosition()); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void Object::setDestinationLayer(PathfindLayerEnum layer) +{ + if (layer!=m_destinationLayer) { + m_destinationLayer = layer; + } +} + +// ------------------------------------------------------------------------------------------------ +/** Set unique ID */ +// ------------------------------------------------------------------------------------------------ +void Object::setID( ObjectID id ) +{ + + // sanity + DEBUG_ASSERTCRASH( id != INVALID_ID, ("Object::setID - Invalid id\n") ); + + // if id hasn't changed do nothing + if( m_id == id ) + return; + + // remove this objects previous id from the lookup table + TheGameLogic->removeObjectFromLookupTable( this ); + + // assign new id + m_id = id; + + // add new id to lookup table + TheGameLogic->addObjectToLookupTable( this ); + +} // end setID + +// ------------------------------------------------------------------------------------------------ +Real Object::calculateHeightAboveTerrain(void) const +{ + const Coord3D* pos = getPosition(); + Real terrainZ = TheTerrainLogic->getLayerHeight( pos->x, pos->y, m_layer ); + Real myZ = pos->z; + return myZ - terrainZ; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void Object::removeFromList(Object **pListHead) +{ + if (m_next) + m_next->m_prev = m_prev; + + if (m_prev) + m_prev->m_next = m_next; + else + *pListHead = m_next; + + m_prev = NULL; + m_next = NULL; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void Object::friend_prepareForMapBoundaryAdjust(void) +{ + // NOTE - DO NOT remove from pathfind map. jba. + // NO NO. jba. TheAI->pathfinder()->removeObjectFromPathfindMap( this ); + + // remove from the radar, remove from the partition manager + TheRadar->removeObject(this); + ThePartitionManager->unRegisterObject(this); + + // The whole PartitionManager and all of the Looker data is about to be blown away, + // so forget what I think I have done + m_partitionLastLook->reset(); + m_partitionRevealAllLastLook->reset(); + m_partitionLastShroud->reset(); + + m_partitionLastThreat->reset(); + m_partitionLastValue->reset(); + +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void Object::friend_notifyOfNewMapBoundary(void) +{ + ThePartitionManager->registerObject(this); + TheRadar->addObject(this); + TheAI->pathfinder()->addObjectToPathfindMap( this ); + + // Now that the PartitionManager has finished its reset, we need to relook + handlePartitionCellMaintenance(); + + Region3D mapExtent; + TheTerrainLogic->getExtent(&mapExtent); + if (mapExtent.isInRegionNoZ(getPosition())) + m_privateStatus &= ~OFF_MAP; + else + m_privateStatus |= OFF_MAP; +} + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void Object::calcNaturalRallyPoint(Coord2D *pt) +{ + const Matrix3D *transform = getTransformMatrix(); + Vector3 v; + + // + // get the natural rally point from the template, this coord is in model space relative + // to the model (0,0,0) + // +/* + const Coord3D *naturalRallyPoint; + naturalRallyPoint = m_template->getNaturalRallyPoint(); + v.X = naturalRallyPoint->x; + v.Y = naturalRallyPoint->y; + v.Z = naturalRallyPoint->z; +*/ + v.Set( 0, 0, 0 ); + + // transform the point into world space + transform->Transform_Vector( *transform, v, &v ); + + // we're only concerned with the 2D elements for now + pt->x = v.X; + pt->y = v.Y; + +} + +//------------------------------------------------------------------------------------------------- +Module* Object::findModule(NameKeyType key) const +{ + Module* m = NULL; + + for (BehaviorModule** b = m_behaviors; *b; ++b) + { + if ((*b)->getModuleNameKey() == key) + { +#ifdef INTENSE_DEBUG + if (m == NULL) + { + m = *b; + } + else + { + DEBUG_CRASH(("Duplicate modules found for name %s!\n",TheNameKeyGenerator->keyToName(key).str())); + } +#else + m = *b; + break; +#endif + } + } + + return m; +} + +//------------------------------------------------------------------------------------------------- +/** + * Returns true if object is currently able to move. + */ +Bool Object::isMobile() const +{ + if (isKindOf(KINDOF_IMMOBILE)) + return false; + + // AW: This excemption is needed, because teleporters still need to listen to AI commands when disabled + if( isDisabled() && !isDisabledByType(DISABLED_TELEPORT) ) + return false; + + return true; +} + +//------------------------------------------------------------------------------------------------- +void Object::scoreTheKill( const Object *victim ) +{ + // Do stuff that has nothing to do with experience points here, like tell our Player we killed something + /// @todo Multiplayer score hook location? + + Player* victimController = victim->getControllingPlayer(); + // if the other player is not a playable side (i.e. they are civilian, observer, whatever) + // we shouldn't count the kill. + if (victimController->isPlayableSide() == FALSE) + { + return; + } + + + if ( victim->isKindOf( KINDOF_IGNORED_IN_GUI ) ) + return; + + + Player* controller = getControllingPlayer(); + + if (victimController) + { + victimController->getScoreKeeper()->addObjectLost(victim); + } + + Relationship r = getRelationship(victim); + if (r != ENEMIES) + return; + + // Don't count kills that I do on my own buildings or units, cause thats just silly. + if (controller == victimController) + { + return; + } + + if (controller) + { + controller->getScoreKeeper()->addObjectDestroyed(victim); + controller->addSkillPointsForKill(this, victim); + controller->doBountyForKill(this, victim); + } + + // Now handle experience, if we can gain any + if (m_experienceTracker && m_experienceTracker->isAcceptingExperiencePoints()) + { + // srj sez: per dustin, no experience (et al) for killing things under construction. + if (!victim->testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION)) + { + Int experienceValue = victim->getExperienceTracker()->getExperienceValue( this ); + getExperienceTracker()->addExperiencePoints( experienceValue ); + } + } +} + +//------------------------------------------------------------------------------------------------- +VeterancyLevel Object::getVeterancyLevel() const +{ + return m_experienceTracker ? m_experienceTracker->getVeterancyLevel() : LEVEL_REGULAR; +} + +//------------------------------------------------------------------------------------------------- +void Object::friend_bindToDrawable( Drawable *draw ) +{ + m_drawable = draw; + if (m_drawable) + { + ModelConditionFlags set; + ModelConditionFlags clr; + for (int i = 0; i < WEAPONSET_COUNT; ++i) + { + ModelConditionFlagType mcs = TheWeaponSetTypeToModelConditionTypeMap[i]; + if( mcs != MODELCONDITION_INVALID ) + { + if (m_curWeaponSetFlags.test(i)) + set.set(mcs); + else + clr.set(mcs); + } + } + if (TheGlobalData) + { + if (TheGlobalData->m_forceModelsToFollowTimeOfDay) + { + set.set(MODELCONDITION_NIGHT, (TheGlobalData->m_timeOfDay == TIME_OF_DAY_NIGHT) ? 1 : 0); + } + + if (TheGlobalData->m_forceModelsToFollowWeather) + { + set.set(MODELCONDITION_SNOW, (TheGlobalData->m_weather == WEATHER_SNOWY) ? 1 : 0); + } + } + m_drawable->clearAndSetModelConditionFlags(clr, set); + } + + for (BehaviorModule** b = m_behaviors; *b; ++b) + { + (*b)->onDrawableBoundToObject(); + } +} + +//------------------------------------------------------------------------------------------------- +void Object::setSelectable(Bool selectable) +{ + m_isSelectable = selectable; + if (m_drawable) + { + m_drawable->setSelectable(selectable); + } +} + +//------------------------------------------------------------------------------------------------- +Bool Object::isSelectable() const +{ +// return getTemplate()->isKindOf(KINDOF_ALWAYS_SELECTABLE) +// || (m_isSelectable +// && !testStatus(OBJECT_STATUS_UNSELECTABLE) +// && !isEffectivelyDead() +// && !getTemplate()->isKindOf(KINDOF_DRONE)//Most drones are unselectable from being slaved, but the SpyDrone needs help +// ); + + + if (getTemplate()->isKindOf(KINDOF_ALWAYS_SELECTABLE)) + return TRUE; + + if ( m_isSelectable ) + if ( !testStatus(OBJECT_STATUS_UNSELECTABLE) ) + if ( !isEffectivelyDead() ) + //if ( !getTemplate()->isKindOf(KINDOF_DRONE) )//Most drones are unselectable from being slaved, but the SpyDrone needs help + return TRUE; + + return FALSE; +} + +//------------------------------------------------------------------------------------------------- +Bool Object::isMassSelectable() const +{ + return isSelectable() && !isKindOf(KINDOF_STRUCTURE); +} + +//------------------------------------------------------------------------------------------------- +void Object::setWeaponSetFlag(WeaponSetType wst) +{ + m_curWeaponSetFlags.set(wst); + m_weaponSet.updateWeaponSet(this); + if (m_drawable) + { + m_drawable->setModelConditionState(TheWeaponSetTypeToModelConditionTypeMap[wst]); + } +} + +//------------------------------------------------------------------------------------------------- +void Object::clearWeaponSetFlag(WeaponSetType wst) +{ + m_curWeaponSetFlags.set(wst, 0); + m_weaponSet.updateWeaponSet(this); + if (m_drawable) + { + m_drawable->clearModelConditionState(TheWeaponSetTypeToModelConditionTypeMap[wst]); + } +} + +//------------------------------------------------------------------------------------------------- +Bool Object::hasSpecialPower( SpecialPowerType type ) const +{ + return TEST_SPECIALPOWERMASK( m_specialPowerBits, type ); +} + +//------------------------------------------------------------------------------------------------- +Bool Object::hasAnySpecialPower() const +{ + return SPECIALPOWERMASK_ANY_SET( m_specialPowerBits ); +} + +//------------------------------------------------------------------------------------------------- +void Object::onVeterancyLevelChanged( VeterancyLevel oldLevel, VeterancyLevel newLevel, Bool provideFeedback ) +{ + updateUpgradeModules(); + + const UpgradeTemplate* up = TheUpgradeCenter->findVeterancyUpgrade(newLevel); + if (up) + giveUpgrade(up); + + BodyModuleInterface* body = getBodyModule(); + if (body) + body->onVeterancyLevelChanged( oldLevel, newLevel, provideFeedback ); + + Bool hideAnimationForStealth = FALSE; + if( !isLocallyControlled() && + testStatus( OBJECT_STATUS_STEALTHED ) && + !testStatus( OBJECT_STATUS_DETECTED ) && + !testStatus( OBJECT_STATUS_DISGUISED ) ) + { + hideAnimationForStealth = TRUE; + } + + Bool doAnimation = ( ! hideAnimationForStealth + && (newLevel > oldLevel) + && ( ! isKindOf(KINDOF_IGNORED_IN_GUI))); //First, we plan to do the animation if the level went up + + switch (newLevel) + { + case LEVEL_REGULAR: + clearWeaponSetFlag(WEAPONSET_VETERAN); + clearWeaponSetFlag(WEAPONSET_ELITE); + clearWeaponSetFlag(WEAPONSET_HERO); + clearWeaponBonusCondition(WEAPONBONUSCONDITION_VETERAN); + clearWeaponBonusCondition(WEAPONBONUSCONDITION_ELITE); + clearWeaponBonusCondition(WEAPONBONUSCONDITION_HERO); + doAnimation = FALSE;//... but not if somehow up to Regular + break; + case LEVEL_VETERAN: + setWeaponSetFlag(WEAPONSET_VETERAN); + clearWeaponSetFlag(WEAPONSET_ELITE); + clearWeaponSetFlag(WEAPONSET_HERO); + setWeaponBonusCondition(WEAPONBONUSCONDITION_VETERAN); + clearWeaponBonusCondition(WEAPONBONUSCONDITION_ELITE); + clearWeaponBonusCondition(WEAPONBONUSCONDITION_HERO); + break; + case LEVEL_ELITE: + clearWeaponSetFlag(WEAPONSET_VETERAN); + setWeaponSetFlag(WEAPONSET_ELITE); + clearWeaponSetFlag(WEAPONSET_HERO); + clearWeaponBonusCondition(WEAPONBONUSCONDITION_VETERAN); + setWeaponBonusCondition(WEAPONBONUSCONDITION_ELITE); + clearWeaponBonusCondition(WEAPONBONUSCONDITION_HERO); + break; + case LEVEL_HEROIC: + clearWeaponSetFlag(WEAPONSET_VETERAN); + clearWeaponSetFlag(WEAPONSET_ELITE); + setWeaponSetFlag(WEAPONSET_HERO); + clearWeaponBonusCondition(WEAPONBONUSCONDITION_VETERAN); + clearWeaponBonusCondition(WEAPONBONUSCONDITION_ELITE); + setWeaponBonusCondition(WEAPONBONUSCONDITION_HERO); + break; + } + + if( doAnimation && TheGameLogic->getDrawIconUI() && provideFeedback ) + { + if( TheAnim2DCollection && TheGlobalData->m_levelGainAnimationName.isEmpty() == FALSE ) + { + Anim2DTemplate *animTemplate = TheAnim2DCollection->findTemplate( TheGlobalData->m_levelGainAnimationName ); + + Coord3D pos = *getPosition(); + pos.add(&m_healthBoxOffset); + + TheInGameUI->addWorldAnimation( animTemplate, + &pos, + WORLD_ANIM_FADE_ON_EXPIRE, + TheGlobalData->m_levelGainAnimationDisplayTimeInSeconds, + TheGlobalData->m_levelGainAnimationZRisePerSecond); + } + + AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_unitPromoted; + soundToPlay.setObjectID( getID() ); + TheAudio->addAudioEvent( &soundToPlay ); + } + +} + +//------------------------------------------------------------------------------------------------- +/** + * Returns true if object currently has some kind of attack capability + */ +Bool Object::isAbleToAttack() const +{ + + //****************************************************** + //********* AUTOMATICALLY FALSE CONDITIONS ************* + //****************************************************** + + // For things that may or may not be able to normally attack, but are under a status condition + if( getStatusBits().test( OBJECT_STATUS_NO_ATTACK ) ) + return false; + + // if we're contained within a transport we cannot attack unless it specifically allows us + const Object *containedBy = getContainedBy(); + DEBUG_ASSERTCRASH( (containedBy == NULL) || (containedBy->getContain() != NULL), ("A %s thinks they are contained by something with no contain module!", getTemplate()->getName().str() ) ); + if( containedBy && containedBy->getContain() && !containedBy->getContain()->isPassengerAllowedToFire( getID() ) ) + return false; + + + // We can't fire if under construction + if( testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) ) + return false; + + // or being sold + if( testStatus(OBJECT_STATUS_SOLD) ) + return false; + + if ( isDisabledByType( DISABLED_SUBDUED ) ) + return FALSE; // A Microwave Tank is cooking me + + //We can't fire if we, as a portable structure, are aptly disabled + if ( isKindOf( KINDOF_PORTABLE_STRUCTURE ) || isKindOf( KINDOF_SPAWNS_ARE_THE_WEAPONS )) + { + if( isDisabledByType( DISABLED_HACKED ) || isDisabledByType( DISABLED_EMP ) ) + return false; + + if ( isKindOf( KINDOF_INFANTRY ) ) // I must be a stinger soldier or similar + { + for (BehaviorModule** update = getBehaviorModules(); *update; ++update)//expensive search, limited only to stinger soldiers + { + SlavedUpdateInterface* sdu = (*update)->getSlavedUpdateInterface(); + if ( sdu ) + { + ObjectID slaverID = sdu->getSlaverID(); + if ( slaverID != INVALID_ID ) + { + Object *slaver = TheGameLogic->findObjectByID( slaverID ); + if ( slaver && slaver->isDisabledByType( DISABLED_SUBDUED )) + return FALSE;// if my stinger site is subdued, so am I + } + + break;//only expect one slavedupdate, so stop searching + } + } + } + + + } + + + + //We can't fire if all our weapons are disabled! + //Currently, only turreted weapons can be disabled. + //ONLY DO THIS CHECK IF OUR UNIT DOESN'T HAVE THE + //KINDOF_CAN_ATTACK flag... nuke cannons have disabled + //turrets when not deployed, and need to be able to attack to deploy! + //Strategy centers can't attack when bombardment isn't active! + Bool anyEnabled = FALSE; + Bool anyWeapon = FALSE; + const AIUpdateInterface *ai = getAI(); + if( ai && !isKindOf( KINDOF_CAN_ATTACK ) ) + { + for( Int i = 0; i < WEAPONSLOT_COUNT; i++ ) + { + //Find the weapon in this slot. + Weapon* weapon = getWeaponInWeaponSlot( (WeaponSlotType)i ); + if( !weapon ) + continue; + + anyWeapon = TRUE; + + //We found a weapon, is it a turret? + Real dummy; + WhichTurretType tur = ai->getWhichTurretForWeaponSlot( (WeaponSlotType)i, &dummy ); + if( tur == TURRET_INVALID ) + { + //Currently impossible to disable a non-turreted weapon, so we + //have a non turreted weapon that is enabled. Quit. + anyEnabled = TRUE; + break; + } + + if( ai->isTurretEnabled( tur ) ) + { + //The turret is enable, meaning we have an enabled weapon. Quit. + anyEnabled = TRUE; + break;; + } + } + if( anyWeapon && !anyEnabled ) + { + //We failed to find any active weapons. + return FALSE; + } + } + + + //*************************************** + //********* TRUE CONDITIONS ************* + //*************************************** + + // for certain buildings + if (isKindOf(KINDOF_CAN_ATTACK)) + return true; + + // for garrisonned buildings that can attack sometimes + if( getStatusBits().test( OBJECT_STATUS_CAN_ATTACK ) ) + return true; + + // for weaponless transports. This will make me think I can, but I will check if I literally can by looking + // at passenger weapons in CanAttack. + const ContainModuleInterface* contain = getContain(); + if( contain && contain->isPassengerAllowedToFire( getID() ) && contain->getContainCount() > 0 ) + return true; + + // if we have AI and a weapon, assume we know how to use it + if (getAIUpdateInterface() != NULL && m_weaponSet.hasAnyWeapon()) + { + +// actually, we don't want to do this; we want the troop crawler to be considered "able to attack" +// even if empty, so sayeth Dustin. (srj) +// // special case: if the only damage we do is DEPLOY, we must have some guys contained. +// if (m_weaponSet.hasSingleDamageType(DAMAGE_DEPLOY)) +// { +// return contain->getContainCount() > 0; +// } +// else + { + return true; + } + } + + SpawnBehaviorInterface *spawnInterface = getSpawnBehaviorInterface(); + if( spawnInterface ) + { + if( spawnInterface->canAnySlavesAttack() ) + { + return TRUE; + } + } + + if (getTemplate()->isEnterGuard()) + return TRUE; + +//Default is no + return false; +} + +//------------------------------------------------------------------------------------------------- +/** + * Mask/Un-Mask an object + */ +void Object::maskObject( Bool mask ) +{ + + // set or clear the mask bit + setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ), mask ); + + // + // when masking objects they become unselected ... we do this in any situation for + // any player cause you aren't allowed to select masked objects, if the object is not + // selected (ie, belongs to another player) it's no big deal cause it won't be selected + // anyway + // + + if (mask) + TheGameLogic->deselectObject(this, ~getControllingPlayer()->getPlayerMask(), TRUE); + +} // end maskObject + +//------------------------------------------------------------------------------------------------- +/* + * returns true if the current locomotor is an airborne one + */ +Bool Object::isUsingAirborneLocomotor( void ) const +{ + return ( m_ai && m_ai->getCurLocomotor() && ((m_ai->getCurLocomotor()->getLegalSurfaces() & LOCOMOTORSURFACE_AIR) != 0) ); +} + +//------------------------------------------------------------------------------------------------- +//THIS FUNCTION BELONGS AT THE OBJECT LEVEL BECAUSE THERE IS AT LEAST ONE SPECIAL UNIT +//(ANGRY MOB) WHICH NEEDS LOGIC-SIDE POSITION CALC'S... +//IT WOULD PROBABLY BE WISE TO MOVE ALL THE HARD-CODED DEFAULTS BELOW +//INTO A NEW Drawable::getHealthBox..() WHICH USES GEOM0INFO, MODEL DATA, INI DATA, ETC. +void Object::getHealthBoxPosition(Coord3D& pos) const +{ + pos = *getPosition(); + pos.z += getGeometryInfo().getMaxHeightAbovePosition() + 10; + pos.add(&m_healthBoxOffset); + + // this needs to get moved to the mobspawnerupdate + if (isKindOf(KINDOF_MOB_NEXUS)) // quicker idiot test + { + pos.z += 20;// dear God, I confess my kluge, and repent. + } +} + +//------------------------------------------------------------------------------------------------- +//THIS FUNCTION BELONGS AT THE OBJECT LEVEL BECAUSE THERE IS AT LEAST ONE SPECIAL UNIT +//(ANGRY MOB) WHICH NEEDS LOGIC-SIDE POSITION CALC'S... +//IT WOULD PROBABLY BE WISE TO MOVE ALL THE HARD-CODED DEFAULTS BELOW +//INTO A NEW Drawable::getHealthBox..() WHICH USES GEOM0INFO, MODEL DATA, INI DATA, ETC. +Bool Object::getHealthBoxDimensions(Real &healthBoxHeight, Real &healthBoxWidth) const +{ + +#ifdef CALC_HEALTHBAR_FROM_HITPOINTS + Real maxHP = getBodyModule()->getMaxHealth(); + + if( isKindOf( KINDOF_STRUCTURE ) ) + { + //enforce healthBoxHeightMinimum/Maximum + healthBoxHeight = min(3.0f, max(5.0f, maxHP/50)); + //enforce healthBoxWidthMinimum/Maximum + healthBoxWidth = min(150.0f, max(100.0f, maxHP/10)); + return true; + } + else if ( isKindOf(KINDOF_MOB_NEXUS) ) + { + //enforce healthBoxHeightMinimum/Maximum + healthBoxHeight = min(3.0f, max(5.0f, maxHP/50)); + //enforce healthBoxWidthMinimum/Maximum + healthBoxWidth = min(100.0f, max(66.0f, maxHP/10)); + return true; + } + else if ( isKindOf( KINDOF_IGNORED_IN_GUI ) ) + { + healthBoxHeight = 0; + healthBoxWidth = 0; + return false; + } + else + { + //enforce healthBoxHeightMinimum/Maximum + healthBoxHeight = min(3.0f, max(5.0f, maxHP/50)); + //enforce healthBoxWidthMinimum/Maximum + healthBoxWidth = min(150.0f, max(35.0f, maxHP/10)); + return true; + } +#else + + if ( isKindOf( KINDOF_IGNORED_IN_GUI ) ) + { + healthBoxHeight = 0; + healthBoxWidth = 0; + return false; + } + + //just add the major and minor axes + Real size = MAX(20.0f, MIN(150.0f, (getGeometryInfo().getMajorRadius() + getGeometryInfo().getMinorRadius())) ); + healthBoxHeight = 3.0f; + healthBoxWidth = MAX(20.0f, size * 2.0f); + return TRUE; + +#endif + +} + + +//------------------------------------------------------------------------------------------------- +/** + * Update this object instance with properties from the map object + * + */ +void Object::updateObjValuesFromMapProperties(Dict* properties) +{ + Bool exists; + + AsciiString valStr; + Bool valBool = false; + Int valInt = 0; + Real valReal = 0.0f; + + valStr = properties->getAsciiString(TheKey_objectName, &exists); + if (exists) { + setName(valStr); + } + + valInt = properties->getInt(TheKey_objectMaxHPs, &exists); + if (exists && valInt >= 0) { + BodyModuleInterface* body = getBodyModule(); + if (body) { + body->setMaxHealth(valInt); + } + } + + valInt = properties->getInt(TheKey_objectInitialHealth, &exists); + if (exists) { + BodyModuleInterface* body = getBodyModule(); + if (body) { + body->setInitialHealth(valInt); + } + } + + // set the veterancy level + valInt = properties->getInt(TheKey_objectVeterancy, &exists); + if (exists) { + if (m_experienceTracker && m_experienceTracker->isTrainable()) + { + m_experienceTracker->setVeterancyLevel((VeterancyLevel)valInt); + } + } + + // set the aggressiveness/mood + valInt = properties->getInt(TheKey_objectAggressiveness, &exists); + if (exists) { + AIUpdateInterface *ai = getAIUpdateInterface(); + if (ai) + { + ai->setAttitude((AttitudeType)valInt); + } + } + + // set recruitable + valBool = properties->getBool(TheKey_objectRecruitableAI, &exists); + if (exists) { + if (getAIUpdateInterface()) + { + getAIUpdateInterface()->setIsRecruitable(valBool); + } + } + + // set selectable + valBool = properties->getBool(TheKey_objectSelectable, &exists); + if (exists) { + if (valBool != isSelectable()) { + setSelectable(valBool); + } + } + + // set the stopping distance + valReal = properties->getReal(TheKey_objectStoppingDistance, &exists); + if (exists && valReal >= 0.5f) + { + if (getAIUpdateInterface() && getAIUpdateInterface()->getCurLocomotor()) + { + Locomotor *loco = getAIUpdateInterface()->getCurLocomotor(); + loco->setCloseEnoughDist(valReal); + } + } + + // set the disabledness of this object + valBool = properties->getBool(TheKey_objectEnabled, &exists); + if (exists) { + setScriptStatus(OBJECT_STATUS_SCRIPT_DISABLED, !valBool); + } + + // set the disabledness of this object + valBool = properties->getBool(TheKey_objectPowered, &exists); + if (exists) { + setScriptStatus(OBJECT_STATUS_SCRIPT_UNPOWERED, !valBool); + } + + // set the invulnerability of the object + valBool = properties->getBool(TheKey_objectIndestructible, &exists); + if (exists) { + BodyModuleInterface* body = getBodyModule(); + if (body) { + body->setIndestructible(valBool); + } + } + + // set the sellability of the object + valBool = properties->getBool(TheKey_objectUnsellable, &exists); + if (exists) { + setScriptStatus(OBJECT_STATUS_SCRIPT_UNSELLABLE, valBool); + } + + //Set the player targetable setting of the object + valBool = properties->getBool( TheKey_objectTargetable, &exists ); + if( exists ) + { + setScriptStatus(OBJECT_STATUS_SCRIPT_TARGETABLE, valBool); + } + + // adjust the vision distance of this object, overriding its default vision distance + valInt = properties->getInt(TheKey_objectVisualRange, &exists); + if (exists) + { + if (valInt < 0) + valInt = 0; + m_visionRange = INT_TO_REAL(valInt); + } + + // adjust the shroud clearing distance of this object, overriding its default distance + valInt = properties->getInt(TheKey_objectShroudClearingDistance, &exists); + if (exists) + { + if (valInt < 0) + valInt = 0.0f; + m_shroudClearingRange = INT_TO_REAL(valInt); + } + + + Int upgradeNum = 0; + do + { + AsciiString keyName; + keyName.format("%s%d", TheNameKeyGenerator->keyToName(TheKey_objectGrantUpgrade).str(), upgradeNum); + valStr = properties->getAsciiString(NAMEKEY(keyName), &exists); + + if (exists) + { + const UpgradeTemplate *ut = TheUpgradeCenter->findUpgrade(valStr); + if (ut) + giveUpgrade(ut); + } + else + { + valStr.clear(); + } + + ++upgradeNum; + } while (!valStr.isEmpty()); + + Drawable *drawable = getDrawable(); + if ( drawable ) + { + valInt = properties->getInt(TheKey_objectTime, &exists); + if (exists) + { + switch (valInt) + { + case 1: + drawable->clearModelConditionState(MODELCONDITION_NIGHT); + break; + case 2: + drawable->setModelConditionState(MODELCONDITION_NIGHT); + break; + default: + break; + } + } + + valInt = properties->getInt(TheKey_objectWeather, &exists); + if (exists) + { + switch (valInt) + { + case 1: + drawable->clearModelConditionState(MODELCONDITION_SNOW); + break; + case 2: + drawable->setModelConditionState(MODELCONDITION_SNOW); + break; + default: + break; + } + } + + // See if we are supposed to playing the ambient sound + Bool soundEnabledExists; + Bool soundEnabled = properties->getBool( TheKey_objectSoundAmbientEnabled, &soundEnabledExists ); + + DynamicAudioEventInfo * audioToModify = NULL; + Bool infoModified = false; + valStr = properties->getAsciiString( TheKey_objectSoundAmbient, &exists ); + if ( exists ) + { + if ( valStr.isEmpty() ) + { + drawable->setCustomSoundAmbientOff(); + soundEnabledExists = true; + soundEnabled = false; // Don't bother trying to enable later + } + else + { + const AudioEventInfo * baseInfo = TheAudio->findAudioEventInfo( valStr ); + DEBUG_ASSERTCRASH( baseInfo != NULL, ("Cannot find customized ambient sound '%s'", valStr.str() ) ); + if ( baseInfo != NULL ) + { + audioToModify = newInstance( DynamicAudioEventInfo )( *baseInfo ); + infoModified = true; + } + } + } + + // Don't do anything more to audio if we forced the ambient sound off + if ( !( exists && valStr.isEmpty() ) ) + { + valBool = properties->getBool( TheKey_objectSoundAmbientCustomized, &exists ); + if ( exists && valBool ) + { + if ( audioToModify == NULL ) + { + const AudioEventInfo * baseInfo = drawable->getBaseSoundAmbientInfo( ); + DEBUG_ASSERTCRASH( baseInfo != NULL, ("getBaseSoundAmbientInfo() return NULL" ) ); + if ( baseInfo != NULL ) + { + audioToModify = newInstance( DynamicAudioEventInfo )( *baseInfo ); + } + } + + if ( audioToModify != NULL ) + { + valBool = properties->getBool( TheKey_objectSoundAmbientLooping, &exists ); + if ( exists ) + { + audioToModify->overrideLoopFlag( valBool ); + infoModified = true; + } + + valInt = properties->getInt( TheKey_objectSoundAmbientLoopCount, &exists ); + if ( exists && BitIsSet( audioToModify->m_control, AC_LOOP ) ) + { + audioToModify->overrideLoopCount( valInt ); + infoModified = true; + } + + valReal = properties->getReal( TheKey_objectSoundAmbientMinVolume, &exists ); + if ( exists ) + { + audioToModify->overrideMinVolume( valReal ); + infoModified = true; + } + + valReal = properties->getReal( TheKey_objectSoundAmbientVolume, &exists ); + if ( exists ) + { + audioToModify->overrideVolume( valReal ); + infoModified = true; + } + + valReal = properties->getReal( TheKey_objectSoundAmbientMinRange, &exists ); + if ( exists ) + { + audioToModify->overrideMinRange( valReal ); + infoModified = true; + } + + valReal = properties->getReal( TheKey_objectSoundAmbientMaxRange, &exists ); + if ( exists ) + { + audioToModify->overrideMaxRange( valReal ); + infoModified = true; + } + + valInt = properties->getInt( TheKey_objectSoundAmbientPriority, &exists ); + if ( exists ) + { + audioToModify->overridePriority ( (AudioPriority)valInt ); + infoModified = true; + } + } + } + } + + if ( !soundEnabledExists ) + { + // Decide if the sound should start enabled or not, since the map maker didn't record + // a preference. Enable permanently looping sounds, disable one-shot sounds by default + // NOTE: This test should match the tests done in MapObjectProps::mapObjectPageSound::dictToEnabled() + // when it decided whether or not to show a customized sound as enabled + if ( audioToModify != NULL ) + { + soundEnabled = audioToModify->isPermanentSound(); + soundEnabledExists = true; // To get into enableAmbientSoundFromScript() call. + } + else + { + // Use default audio + const AudioEventInfo * baseInfo = drawable->getBaseSoundAmbientInfo( ); + if ( baseInfo != NULL ) + { + soundEnabled = baseInfo->isPermanentSound(); + soundEnabledExists = true; // To get into enableAmbientSoundFromScript() call. + } + } + } + + if ( soundEnabledExists && !soundEnabled ) + { + // Make sure sound doesn't start playing when we set it + // ...FromScript because this is also controlled by the map designer not the game logic + drawable->enableAmbientSoundFromScript( false ); + } + + if ( infoModified && audioToModify != NULL ) + { + // Give a custom, level-specific name + drawable->mangleCustomAudioName( audioToModify ); + + // Pass to TheAudio + TheAudio->addAudioEventInfo( audioToModify ); + + drawable->setCustomSoundAmbientInfo( audioToModify ); + audioToModify = NULL; // Belongs to TheAudio now + } + + if ( audioToModify != NULL ) + { + audioToModify->deleteInstance(); + audioToModify = NULL; + } + + if ( soundEnabledExists && soundEnabled ) + { + // Play sound now that it is set up, if needed. Don't call if already enabled because that + // can cause sound to play twice + // ...FromScript because this is also controlled by the map designer not the game logic + if ( !drawable->getAmbientSoundEnabledFromScript() ) + { + drawable->enableAmbientSoundFromScript( true ); + } + } + } +} + +//------------------------------------------------------------------------------------------------- +void Object::friend_adjustPowerForPlayer( Bool incoming ) +{ + if (isDisabled() && getTemplate()->getEnergyProduction() > 0) + { + // Disabledness only affects Producers, not Consumers. + return; + } + + if (incoming) { + getControllingPlayer()->getEnergy()->objectEnteringInfluence(this); + } else { + getControllingPlayer()->getEnergy()->objectLeavingInfluence(this); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//------------------------------------------------------------------------------------------------- +void Object::onDisabledEdge(Bool becomingDisabled) +{ + // rip through the behavior modules and call the onDisabledEdge for any modules that care + for( BehaviorModule **module = m_behaviors; *module; ++module ) + (*module)->onDisabledEdge( becomingDisabled ); + + DozerAIInterface *dozerAI = getAI() ? getAI()->getDozerAIInterface() : NULL; + if( becomingDisabled && dozerAI ) + { + // Have to say goodbye to the thing we might be building or repairing so someone else can do it. + if( dozerAI->getCurrentTask() != DOZER_TASK_INVALID ) + dozerAI->cancelTask( dozerAI->getCurrentTask() ); + } + + Player* controller = getControllingPlayer(); + // can be called during game teardown, thus controller can be null + if (controller) + { + //@todo jkmcd - Colin suggested we rewrite this to use the interface stuff. I agree, but need + // to get some more bugs fixed today. + static NameKeyType radar = NAMEKEY("RadarUpgrade"); + Module *mod = mod = findModule(radar); + if (mod) { + RadarUpgrade *radarMod = (RadarUpgrade*) mod; + if (radarMod->isAlreadyUpgraded()) { + // Need to decrement the count here, because we own a radar upgrade + if (becomingDisabled) { + controller->removeRadar(radarMod->getIsDisableProof()); + } else { + controller->addRadar(radarMod->getIsDisableProof()); + } + } + } + } + + // We will need to adjust power ... somehow ... + Int powerToAdjust = getTemplate()->getEnergyProduction(); + + if( powerToAdjust > 0 ) + { + // We can't affect something that consumes, or else we go low power which removes the consumption + // which makes us not low power so we add the consumption so we go low power... + // This check also guaards the IsDisabled in friend_adjustPower above + static NameKeyType powerPlant = NAMEKEY("PowerPlantUpgrade"); + static NameKeyType overCharge = NAMEKEY("OverchargeBehavior"); + + Module* mod = findModule(powerPlant); + if (mod) { + PowerPlantUpgrade *powerPlantMod = (PowerPlantUpgrade*) mod; + if (powerPlantMod->isAlreadyUpgraded()) { + powerToAdjust += getTemplate()->getEnergyBonus(); + } + } + + mod = findModule(overCharge); + if (mod) { + OverchargeBehavior *overChargeMod = (OverchargeBehavior*) mod; + if (overChargeMod->isOverchargeActive()) { + powerToAdjust += getTemplate()->getEnergyBonus(); + } + } + + // Now, adjust the power for the player. + if (controller) + controller->getEnergy()->adjustPower(powerToAdjust, !becomingDisabled); + } +} + +//------------------------------------------------------------------------------------------------- +/** Object CRC implemtation */ +//------------------------------------------------------------------------------------------------- +void Object::crc( Xfer *xfer ) +{ +#ifdef DEBUG_CRC +// g_logObjectCRCs = TRUE; +// Bool g_logAllObjects = TRUE; + AsciiString logString; + AsciiString tmp; + Bool doLogging = g_logObjectCRCs /* && getControllingPlayer()->getPlayerType() == PLAYER_HUMAN */; + if (doLogging) + { + tmp.format("CRC of Object %d (%s), owned by player %d, team: %d, ", m_id, getTemplate()->getName().str(), getControllingPlayer()->getPlayerIndex(), this->getTeam() ? this->getTeam()->getID() : TEAM_ID_INVALID); + logString.concat(tmp); + } +#endif // DEBUG_CRC + + xfer->xferUnsignedByte(&m_privateStatus); +#ifdef DEBUG_CRC + if (doLogging) + { + tmp.format("m_privateStatus: %X, ", (UnsignedInt)m_privateStatus); + logString.concat(tmp); + } +#endif // DEBUG_CRC + + // This is evil - we cast the const Matrix3D * to a Matrix3D * because the XferCRC class must use + // the same interface as the XferLoad class for save game restore. This only works because + // XferCRC does not modify its data. + xfer->xferUser((Matrix3D *)getTransformMatrix(), sizeof(Matrix3D)); +#ifdef DEBUG_CRC + if (doLogging) + { + XferCRC tmpXfer; + tmpXfer.open("tmp"); + tmpXfer.xferUser((Matrix3D *)getTransformMatrix(), sizeof(Matrix3D)); + tmp.format("getTransformMatrix(): %8.8X, ", tmpXfer.getCRC()); + tmpXfer.close(); + logString.concat(tmp); + } +#endif // DEBUG_CRC + + + xfer->xferUser(&m_id, sizeof(m_id)); +#ifdef DEBUG_CRC + if (doLogging) + { + tmp.format("m_id: %d, ", m_id); + logString.concat(tmp); + } +#endif // DEBUG_CRC + xfer->xferUser(&m_objectUpgradesCompleted, sizeof(Int64)); +#ifdef DEBUG_CRC + if (doLogging) + { + tmp.format("m_objectUpgradesCompleted: %I64X, ", m_objectUpgradesCompleted); + logString.concat(tmp); + } +#endif // DEBUG_CRC + if (m_experienceTracker) + xfer->xferSnapshot( m_experienceTracker ); +#ifdef DEBUG_CRC + if (doLogging) + { + XferCRC tmpXfer; + tmpXfer.open("tmp"); + tmpXfer.xferSnapshot(m_experienceTracker); + tmp.format("m_experienceTracker: %8.8X, ", tmpXfer.getCRC()); + tmpXfer.close(); + logString.concat(tmp); + } +#endif // DEBUG_CRC + + Real health = getBodyModule()->getHealth(); + xfer->xferUser(&health, sizeof(health)); +#ifdef DEBUG_CRC + if (doLogging) + { + tmp.format("health: %g/%8.8X, ", health, AS_INT(health)); + logString.concat(tmp); + } +#endif // DEBUG_CRC + + xfer->xferUnsignedInt(&m_weaponBonusCondition); +#ifdef DEBUG_CRC + if (doLogging) + { + tmp.format("m_weaponBonusCondition: %8.8X, ", m_weaponBonusCondition); + logString.concat(tmp); + } +#endif // DEBUG_CRC + + Real scalar = getBodyModule()->getDamageScalar(); + xfer->xferUser(&scalar, sizeof(scalar)); +#ifdef DEBUG_CRC + if (doLogging) + { + tmp.format("damage scalar: %g/%8.8X\n", scalar, AS_INT(scalar)); + logString.concat(tmp); + + CRCDEBUG_LOG(("%s", logString.str())); + } +#endif // DEBUG_CRC + + for (Int i=0; ixferSnapshot( thisWeapon ); + } + } + +} // end crc + +//------------------------------------------------------------------------------------------------- +/** Object xfer implemtation + * Version Info: + * 1: Initial version + * 2: Xfers m_singleUseCommandUsed... determines if the single use command button has been used or not. + * 3: Xfers the solehealingbenefactor ID and expiration frame + * 4: misc stuff that got missed somehow + * 5: m_isReceivingDifficultyBonus + * 6: We do indeed need to save m_containedBy. The comment misrepresents what the contain module will do. + * 7: save full mtx, not pos+orient. + * 8: Kris: Conversion of object status bits from UnsignedInt to BitFlags<> + * 9: Extra sighting for reveal to all with different range units + */ +//------------------------------------------------------------------------------------------------- +void Object::xfer( Xfer *xfer ) +{ + + // version + const XferVersion currentVersion = 9; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // object ID + ObjectID id = getID(); + xfer->xferObjectID( &id ); + setID( id ); + + DEBUG_LOG(("Xfer Object %s id=%d\n",getTemplate()->getName().str(),id)); + + if (version >= 7) + { + Matrix3D mtx = *getTransformMatrix(); + xfer->xferMatrix3D(&mtx); + setTransformMatrix(&mtx); + } + else + { + // object position + Coord3D pos = *getPosition(); + xfer->xferCoord3D( &pos ); + setPosition( &pos ); + + // orientation + Real orientation = getOrientation(); + xfer->xferReal( &orientation ); + setOrientation( orientation ); + } + + // team + TeamID teamID = m_team ? m_team->getID() : TEAM_ID_INVALID; + xfer->xferUser( &teamID, sizeof( TeamID ) ); + // DON'T set the team yet; must wait till we read our status bits, + // since setTeam can affect the player's power usage, but that could + // be done incorrectly if our status bits aren't accurate yet... (srj) + + // producer id + xfer->xferObjectID( &m_producerID ); + + // builder id + xfer->xferObjectID( &m_builderID ); + + // drawable id + Drawable *draw = getDrawable(); + DrawableID drawableID = draw ? draw->getID() : INVALID_DRAWABLE_ID; + xfer->xferDrawableID( &drawableID ); + if( xfer->getXferMode() == XFER_LOAD ) + { + + // change the ID of the drawable attached to be the same ID as it was when it was saved + draw->setID( drawableID ); + + } // end if + + // internal name + xfer->xferAsciiString( &m_name ); + + // status + if( version >= 8 ) + { + m_status.xfer( xfer ); + } + else + { + //We are loading an old version, so we must convert it from a 32-bit int to a bitflag + UnsignedInt oldStatus; + xfer->xferUnsignedInt( &oldStatus ); + + //Clear our status + m_status.clear(); + + for( int i = 0; i < 32; i++ ) + { + UnsignedInt bit = 1<xferUnsignedByte( &m_scriptStatus ); + + // private status + xfer->xferUnsignedByte( &m_privateStatus ); + + // OK, now that we have xferred our status bits, it's safe to set the team... + if( xfer->getXferMode() == XFER_LOAD ) + { + Team *team = TheTeamFactory->findTeamByID( teamID ); + if( team == NULL ) + { + DEBUG_CRASH(( "Object::xfer - Unable to load team\n" )); + throw SC_INVALID_DATA; + } + const Bool restoring = true; + setOrRestoreTeam( team, restoring ); + } + + // geometry info + xfer->xferSnapshot( &m_geometryInfo ); + + // sighting info, last look - must be saved cause we save PartitionCell::m_shroudLevel + xfer->xferSnapshot( m_partitionLastLook ); + + if( version >= 9 ) + xfer->xferSnapshot( m_partitionRevealAllLastLook ); + + // sighting info, last shroud - must be saved cause we save PartitionCell::m_shroudLevel + xfer->xferSnapshot( m_partitionLastShroud ); + + // vision spied by + xfer->xferUser( m_visionSpiedBy, sizeof( Int ) * MAX_PLAYER_COUNT ); + + // vision spied by mask + xfer->xferUser( &m_visionSpiedMask, sizeof( PlayerMaskType ) ); + + // sighting info, last threat + // John M says we don't need to save this (CBD) +// xfer->xferSnapshot( &m_partitionLastThreat ); + + // sighting info, last value + // John M says we don't need to save this (CBD) +// xfer->xferSnapshot( &m_partitionLastValue ); + + // vision range + xfer->xferReal( &m_visionRange ); + + // shroud clearing range + xfer->xferReal( &m_shroudClearingRange ); + + // shroud range + xfer->xferReal( &m_shroudRange ); + + // disabled mask + m_disabledMask.xfer( xfer ); + + //New var added for version 2. Determines if the single use command button has been used or not. + if( xfer->getXferMode() == XFER_SAVE || version >= 2 ) + { + xfer->xferBool( &m_singleUseCommandUsed ); + } + else + { + m_singleUseCommandUsed = false; + } + + // disabled till frame + xfer->xferUser( m_disabledTillFrame, sizeof( UnsignedInt ) * DISABLED_COUNT ); + + // special model condition until + xfer->xferUnsignedInt( &m_smcUntil ); + + // + // radar data ... when loading, we will remove all objects from the radar and let + // the radar system load itself as a separate chunk of data from the save file + // + if( xfer->getXferMode() == XFER_LOAD && m_radarData ) + TheRadar->removeObject( this ); + + // experience tracker + xfer->xferSnapshot( m_experienceTracker ); + + // + // we do not need to do anything with our m_containedBy pointer, the post process + // of that objects contain module will actually re-do the contain process again + // + // m_containedBy <-- do nothing with this right now + if( version >= 6 ) + { + // No, the contain module is just going to friend_ reach in and set this for us. + // Containers more complicated than Open (like Tunnel) can't do that. Our variable, + // our responsibility. + if( xfer->getXferMode() == XFER_SAVE ) + { + if( m_containedBy != NULL ) + m_xferContainedByID = m_containedBy->getID(); + else + m_xferContainedByID = INVALID_ID; + } + + + xfer->xferObjectID( &m_xferContainedByID ); + } + + // contained by frame + xfer->xferUnsignedInt( &m_containedByFrame ); + + // construction percent + xfer->xferReal( &m_constructionPercent ); + + // upgrades completed + xfer->xferUpgradeMask( &m_objectUpgradesCompleted ); + + // original team name + xfer->xferAsciiString( &m_originalTeamName ); + + // indicator color + xfer->xferColor( &m_indicatorColor ); + + // health box offset + xfer->xferCoord3D( &m_healthBoxOffset ); + + // Entered & exited housekeeping. + Int i; + xfer->xferByte(&m_numTriggerAreasActive); + xfer->xferUnsignedInt(&m_enteredOrExitedFrame); + xfer->xferICoord3D(&m_iPos); + if (m_numTriggerAreasActive<0 || m_numTriggerAreasActive>MAX_TRIGGER_AREA_INFOS) { + DEBUG_CRASH(("Invalid m_numTriggerAreasActive = %d, max is %d", m_numTriggerAreasActive, + MAX_TRIGGER_AREA_INFOS)); + throw SC_INVALID_DATA; + } + for (i=0; igetTriggerName(); + } + xfer->xferAsciiString(&triggerName); + if (xfer->getXferMode() == XFER_LOAD) + { + // + // CBD (11-13-2002) I'm disabling this because it appears there might be some areas with + // empty names, see John A. for more info + // + //if (triggerName.isNotEmpty()) + m_triggerInfo[i].pTrigger = TheTerrainLogic->getTriggerAreaByName(triggerName); + } + xfer->xferByte(&m_triggerInfo[i].entered); + xfer->xferByte(&m_triggerInfo[i].exited); + xfer->xferByte(&m_triggerInfo[i].isInside); + } + // Layer object is pathing on. + xfer->xferUser(&m_layer, sizeof(m_layer)); + + // Layer of current path goal. + xfer->xferUser(&m_destinationLayer, sizeof(m_destinationLayer)); + + // Object selectability. + xfer->xferBool(&m_isSelectable); + + xfer->xferUnsignedInt(&m_safeOcclusionFrame); + + // User formations. + xfer->xferUser(&m_formationID, sizeof(m_formationID)); + if (m_formationID!=NO_FORMATION_ID) { + xfer->xferCoord2D(&m_formationOffset); + } + + // module count + UnsignedShort moduleCount = 0; + for (BehaviorModule** b = m_behaviors; *b; ++b) + ++moduleCount; + + xfer->xferUnsignedShort( &moduleCount ); + AsciiString moduleIdentifier; + BehaviorModule *module; + if( xfer->getXferMode() == XFER_SAVE ) + { + + // go through all modules + for (BehaviorModule** b = m_behaviors; *b; ++b) + { + + // get module + module = *b; + + // write module identifier + moduleIdentifier = TheNameKeyGenerator->keyToName( module->getModuleTagNameKey() ); + DEBUG_ASSERTCRASH( moduleIdentifier != AsciiString::TheEmptyString, + ("Object::xfer - Module tag key does not translate to a string!\n") ); + xfer->xferAsciiString( &moduleIdentifier ); + + // begin a data block + xfer->beginBlock(); + + // xfer data + xfer->xferSnapshot( module ); + + // end data block + xfer->endBlock(); + + } // end for, it + + } // end if, save + else + { + AsciiString otherModuleIdentifier; + + // read all module data + for( UnsignedShort i = 0; i < moduleCount; ++i ) + { + + // read module name + xfer->xferAsciiString( &moduleIdentifier ); + NameKeyType moduleIdentifierKey = TheNameKeyGenerator->nameToKey(moduleIdentifier); + + // find the module with this identifier in the module list + module = NULL; + for (BehaviorModule** b = m_behaviors; b && *b; ++b) + { + + if (moduleIdentifierKey == (*b)->getModuleTagNameKey()) + { + module = *b; + break; + } + + } // end for, moduleIt + + // start of a new block + Int dataSize = xfer->beginBlock(); + + // + // if we didn't find the module, it's quite possible that we have removed + // it from the object definition in a future patch, if that is so, we need to + // skip the module data in the file + // + if( module == NULL ) + { + + // for testing purposes, this module better be found +// DEBUG_CRASH(( "Object::xfer - Module '%s' was indicated in file, but not found on object '%s'(%d)\n", +// moduleIdentifier.str(), getTemplate()->getName().str(), getID() )); + + // skip this data in the file + xfer->skip( dataSize ); + + } // end if + else + { + + // xfer the data into this module + xfer->xferSnapshot( module ); + + } // end else + + // end block + xfer->endBlock(); + + } // end for, i module count recorded in file + + } // end else, load + + + if ( version >= 3 ) + { + xfer->xferObjectID( &m_soleHealingBenefactorID ); + xfer->xferUnsignedInt( &m_soleHealingBenefactorExpirationFrame ); + } + else if ( xfer->getXferMode() == XFER_LOAD ) + { + m_soleHealingBenefactorID = INVALID_ID; + m_soleHealingBenefactorExpirationFrame = 0; + } + + // Doesn't need to be saved. These are created as needed. jba. + //AIGroup* m_group; ///< if non-NULL, we are part of this group of agents + + // don't need to save m_partitionData. + DEBUG_ASSERTCRASH(!(xfer->getXferMode() == XFER_LOAD && m_partitionData == NULL), ("should not be in partitionmgr yet")); + + // don't need to be saved or loaded; are inited & cached for runtime only by our ctor (srj) + //m_repulsorHelper; + //m_smcHelper; + //m_wsHelper; + //m_defectionHelper; + //m_firingTracker; + //m_contain; + //m_body; + //m_ai; + //m_physics; +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + //m_hasDiedAlready; +#endif + + if (version >= 4) + { + // xfer the weaponSetFlags FIRST, since we need 'em to restore the weaponSet properly. (srj) + m_curWeaponSetFlags.xfer( xfer ); + xfer->xferUnsignedInt(&m_weaponBonusCondition); + xfer->xferUser(&m_lastWeaponCondition, sizeof(m_lastWeaponCondition)); + + // do the weaponSet itself after all the weapon-related stuff, just in case + xfer->xferSnapshot(&m_weaponSet); + + m_specialPowerBits.xfer( xfer ); + + xfer->xferAsciiString(&m_commandSetStringOverride); + + xfer->xferBool(&m_modulesReady); + } + + if (version >= 5) + { + xfer->xferBool(&m_isReceivingDifficultyBonus); + } + else + m_isReceivingDifficultyBonus = FALSE; + +} // end xfer + +//------------------------------------------------------------------------------------------------- +/** Object load game post process phase */ +//------------------------------------------------------------------------------------------------- +void Object::loadPostProcess() +{ + if( m_xferContainedByID != INVALID_ID ) + m_containedBy = TheGameLogic->findObjectByID(m_xferContainedByID); + else + m_containedBy = NULL; + +} // end loadPostProcess + +//------------------------------------------------------------------------------------------------- +/** Does this object have this upgrade */ +//------------------------------------------------------------------------------------------------- +Bool Object::hasUpgrade( const UpgradeTemplate *upgradeT ) const +{ + if( m_objectUpgradesCompleted.testForAll( upgradeT->getUpgradeMask() ) ) + { + return TRUE; + } + return FALSE; +} // end hasUpgrade + +//------------------------------------------------------------------------------------------------- +/** Is this object capable of having this upgrade */ +//------------------------------------------------------------------------------------------------- +Bool Object::affectedByUpgrade( const UpgradeTemplate *upgradeT ) const +{ + UpgradeMaskType objectMask = getObjectCompletedUpgradeMask(); + UpgradeMaskType playerMask = getControllingPlayer()->getCompletedUpgradeMask(); + UpgradeMaskType maskToCheck = playerMask; + maskToCheck.set( objectMask ); + maskToCheck.set( upgradeT->getUpgradeMask() ); + + // We need to add in all of the already owned upgrades to handle "AND" requiring upgrades. + // We combine all the masks in case someone has a Object AND Player combination + + for (BehaviorModule** module = m_behaviors; *module; ++module) + { + UpgradeModuleInterface* upgrade = (*module)->getUpgrade(); + if (!upgrade) + continue; + + if( upgrade->wouldUpgrade( maskToCheck ) ) + { + // if any of my many upgrade modules would execute in response to this flag, say yes. + return TRUE; + } + } + return FALSE; + +} // end affectedByUpgrade + +//------------------------------------------------------------------------------------------------- +/** Give this upgrade to this object */ +//------------------------------------------------------------------------------------------------- +void Object::giveUpgrade( const UpgradeTemplate *upgradeT ) +{ + if (upgradeT) + { + m_objectUpgradesCompleted.set( upgradeT->getUpgradeMask() ); + + // + // iterate through all the upgrade modules of this object and call the method to + // grant a new upgrade + // + updateUpgradeModules(); + } +} // end giveUpgrade + +//------------------------------------------------------------------------------------------------- +/** Remove this upgrade from this object */ +//------------------------------------------------------------------------------------------------- +void Object::removeUpgrade( const UpgradeTemplate *upgradeT ) +{ + m_objectUpgradesCompleted.clear( upgradeT->getUpgradeMask() ); + for (BehaviorModule** module = m_behaviors; *module; ++module) + { + UpgradeModuleInterface* upgrade = (*module)->getUpgrade(); + if (!upgrade) + continue; + + // Whoa, please note that while the function is called Object::RemoveUpgrade, it is not removing anything + // in the sense of undoing the effects. It is just resetting the upgrade so it may be run again. + upgrade->resetUpgrade( upgradeT->getUpgradeMask() ); + } +} + +//------------------------------------------------------------------------------------------------- +/** Central point for onCapture logic */ +//------------------------------------------------------------------------------------------------- +void Object::onCapture( Player *oldOwner, Player *newOwner ) +{ + // Everybody dhills when they captured so they don't keep doing something the new player might not want him to be doing + if( getAIUpdateInterface() && (oldOwner != newOwner) ) + getAIUpdateInterface()->aiIdle(CMD_FROM_AI); + + // this gets the new owner some points + newOwner->getScoreKeeper()->addObjectCaptured(this); + + // rip through the behavior modules and call the onCapture for any modules that care + for( BehaviorModule **module = m_behaviors; *module; ++module ) + (*module)->onCapture( oldOwner, newOwner ); + + // + // We have to undo our look for the old team and redo it for the new. + // onCapture is used now, so it better be called after ownership changes and not before. + // + handlePartitionCellMaintenance(); + + // Design needs the player to be able to sell buildings he steals from the AI's build list, and this is the + // easiest fix. The only snafu would be a key building build listed by the AI that the player can capture + // and the AI tries to capture back but needs to not sell. In that case, a Cinematic Unsellable version + // of the building needs to be made. This fix has been okayed as the most non-lethal in November. + clearScriptStatus(OBJECT_STATUS_SCRIPT_UNSELLABLE); + + // mark the command bar to redraw + TheControlBar->markUIDirty(); + + if (oldOwner!=newOwner && newOwner->isSkirmishAIPlayer()) { + // The skirmish ai doesn't know what to do with captured faction buildings except sell them. + if (isFactionStructure()) { + TheBuildAssistant->sellObject( this ); + } + } + +} // end onCapture + +//------------------------------------------------------------------------------------------------- +/// Object level events that need to happen upon game death +void Object::onDie( DamageInfo *damageInfo ) +{ + + checkAndDetonateBoobyTrap(NULL);// Already dying, so no need to handle death case of explosion + +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + DEBUG_ASSERTCRASH(m_hasDiedAlready == false, ("Object::onDie has been called multiple times. This is invalid. jkmcd")); + m_hasDiedAlready = true; +#endif + + Bool selfInflicted = (damageInfo->in.m_sourceID == getID()); + + // FIRST, call our die modules. + for (BehaviorModule** d = m_behaviors; *d; ++d) + { + DieModuleInterface* die = (*d)->getDie(); + if (die) + die->onDie(damageInfo); + } + + // When objects die we remove from the radar as they're really not interesting anymore + if( m_radarData ) + TheRadar->removeObject( this ); + + // Just in case I have been sporting one of thise fancy Terrain Decals, + //I naturally lose it now, because I'm dead. + Drawable *draw = getDrawable(); + if (draw) draw->setTerrainDecalFadeTarget(0.0f, -0.03f);//fade... + //if (draw) draw->setTerrainDecal(TERRAIN_DECAL_NONE);//pop! + + + // objects that were spawned from something, need to tell their spawner that they have died + Object* spawner = TheGameLogic->findObjectByID( getProducerID() ); + if( spawner ) + { + + // get the spawn behavior interface of the spawner + SpawnBehaviorInterface *spawnerBehavior = spawner->getSpawnBehaviorInterface(); + if( spawnerBehavior ) + spawnerBehavior->onSpawnDeath( getID(), damageInfo ); + + } + + handlePartitionCellMaintenance(); + if(m_team) + m_team->notifyTeamOfObjectDeath(); + + if (isLocallyControlled() && !selfInflicted) // wasLocallyControlled? :-) + { + if (isKindOf(KINDOF_STRUCTURE) && isKindOf(KINDOF_MP_COUNT_FOR_VICTORY)) + { + TheEva->setShouldPlay(EVA_BuldingLost); + } + else if (isKindOf(KINDOF_INFANTRY) || isKindOf(KINDOF_VEHICLE)) + { + TheEva->setShouldPlay(EVA_UnitLost); + //Create a fake radar event so the user can use the spacebar to quickly jump to this! + TheRadar->tryEvent( RADAR_EVENT_FAKE, getPosition() ); + } + } + + // This call won't do anything if we aren't actually in the list. + //Kris: Added NULL check to prevent crash with combat bikes & their riders getting deleted on exit. + if( getControllingPlayer() ) + { + TheInGameUI->removeIdleWorker( this, getControllingPlayer()->getPlayerIndex() ); + } + + //When a GLA hole is in the process of rebuilding, and that rebuild is lost, we need to + //tell anyone attacking it to transfer the attack to the hole that still exists. + if( testStatus( OBJECT_STATUS_RECONSTRUCTING ) ) + { + Object *hole = TheGameLogic->findObjectByID( getProducerID() ); + if( hole ) + { + // set the information in the hole about what to build + RebuildHoleBehaviorInterface *rhbi = RebuildHoleBehavior::getRebuildHoleBehaviorInterfaceFromObject( hole ); + + // sanity + DEBUG_ASSERTCRASH( rhbi, ("Object::onDie() - No Rebuild Hole Behavior interface on hole\n") ); + + // start the rebuild process + if( rhbi ) + { + rhbi->startRebuildProcess( getTemplate(), getID() ); + } + + //Transfer any attackers from the destroyed building to the hole. + for ( Object *obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() ) + { + AIUpdateInterface* ai = obj->getAI(); + if (!ai) + continue; + + ai->transferAttack( getID(), hole->getID() ); + } + } + } + +} + +//------------------------------------------------------------------------------------------------- +void Object::setWeaponBonusCondition(WeaponBonusConditionType wst) +{ + WeaponBonusConditionFlags oldCondition = m_weaponBonusCondition; + m_weaponBonusCondition |= (1 << wst); + + if( oldCondition != m_weaponBonusCondition ) + { + // Our weapon bonus just changed, so we need to immediately update our weapons + m_weaponSet.weaponSetOnWeaponBonusChange(this); + } +} + +//------------------------------------------------------------------------------------------------- +void Object::clearWeaponBonusCondition(WeaponBonusConditionType wst) +{ + WeaponBonusConditionFlags oldCondition = m_weaponBonusCondition; + m_weaponBonusCondition &= ~(1 << wst); + + if( oldCondition != m_weaponBonusCondition ) + { + // Our weapon bonus just changed, so we need to immediately update our weapons + m_weaponSet.weaponSetOnWeaponBonusChange(this); + } +} + +//------------------------------------------------------------------------------------------------- +/** + A weapon cannot be in charge of maintaining condition flags as it is all event driven. + I will maintain my ModelCondition myself if it should change. Firing is set by firing logic, + so I don't include it here. It is only the states that expire on timers that noone watches + that I am concerned with. +*/ +//------------------------------------------------------------------------------------------------- +void Object::adjustModelConditionForWeaponStatus() +{ + UnsignedInt now = TheGameLogic->getFrame(); + + for (int i = 0; i < WEAPONSLOT_COUNT; ++i) + { + const Weapon* w = m_weaponSet.getWeaponInWeaponSlot((WeaponSlotType)i); + if (!w) + { + m_lastWeaponCondition[i] = WSF_NONE; + continue; + } + + WeaponSetConditionType conditionToSet = WSF_INVALID; + if (i != m_weaponSet.getCurWeaponSlot()) + { + // if this isn't the current weapon, then we never set ANYTHING for it. + conditionToSet = WSF_NONE; + } + else if (w->getLastShotFrame() == now) + { + // yep, this overrides any weapon-status condition! + conditionToSet = WSF_FIRING; + } + else if (!testStatus( OBJECT_STATUS_IS_ATTACKING )) + { + // srj sez: not 100% sure about this one, but the problem is: say we were attacking, + // then issue a move command. if we didn't do this here, we might still have a 'firing' + // pose, because his weapon might be in 'reloading' mode. since we're not attacking, however, + // we really don't care, so we just force the issue here. (This might still need tweaking for the pursue state.) + conditionToSet = WSF_NONE; + } + else + { + WeaponStatus newStatus = w->getStatus(); + + const static WeaponSetConditionType s_wsfLookup[WEAPON_STATUS_COUNT] = + { + WSF_NONE, // READY_TO_FIRE, + WSF_NONE, // OUT_OF_AMMO, + WSF_BETWEEN, // BETWEEN_FIRING_SHOTS, + WSF_RELOADING, // RELOADING_CLIP, + WSF_PREATTACK // PRE_ATTACK, + }; + conditionToSet = s_wsfLookup[newStatus]; + + // special case this: say we are firing in bursts: pow-pow-pow-pause, etc. + // then we might have a frame where we have reloaded and are ready-to-fire, + // but haven't fired yet this frame. in that case, use 'between' so we still have + // a firing pose, 'cuz if we use 'none' we will 'pop' back to idle for a frame. (srj) + // additional note: only do if aiming or firing, since we could also be in this state if + // we are approaching or pursuing a target! (srj) + if (newStatus == READY_TO_FIRE && conditionToSet == WSF_NONE && testStatus( OBJECT_STATUS_IS_ATTACKING ) && + (testStatus( OBJECT_STATUS_IS_AIMING_WEAPON ) || testStatus( OBJECT_STATUS_IS_FIRING_WEAPON ))) + { + conditionToSet = WSF_BETWEEN; + } + + } + + if (m_drawable) + { + m_drawable->updateDrawableClipStatus( w->getRemainingAmmo(), w->getClipSize(), w->getWeaponSlot() ); + if (conditionToSet != WSF_INVALID && conditionToSet != m_lastWeaponCondition[i]) + { + m_lastWeaponCondition[i] = conditionToSet; + ModelConditionFlags c = m_weaponSet.getModelConditionForWeaponSlot((WeaponSlotType)i, conditionToSet); + m_drawable->clearAndSetModelConditionFlags(s_allWeaponFireFlags[i], c); + if (conditionToSet == WSF_PREATTACK) + { + // in the preattack state, adjust the speed of the preattack anim to match the actual time it will take + UnsignedInt preAttackDone = w->getPreAttackFinishedFrame(); + if (preAttackDone > now) + m_drawable->setAnimationLoopDuration(preAttackDone - now); + } + } + } + } +} + +//------------------------------------------------------------------------------------------------- +/// We have moved a 'significant' amount, so do maintenence that can be considered 'cell-based' +void Object::onPartitionCellChange() +{ + handlePartitionCellMaintenance(); +} + +//------------------------------------------------------------------------------------------------- +void Object::handlePartitionCellMaintenance() +{ + handleShroud(); + handleValueMap(); + handleThreatMap(); +} + +//------------------------------------------------------------------------------------------------- +void Object::handleShroud() +{ + // Undo last looking + unlook(); + // and shrouding + unshroud(); + + // redo shrouding + shroud(); + // Redo looking + look(); +} + +//------------------------------------------------------------------------------------------------- +void Object::handleValueMap() +{ + removeValue(); + addValue(); +} + +//------------------------------------------------------------------------------------------------- +void Object::handleThreatMap() +{ + removeThreat(); + addThreat(); +} + +//------------------------------------------------------------------------------------------------- +void Object::addValue() +{ + if( !m_partitionLastValue->isInvalid() ) + { + DEBUG_CRASH( ("An Object is adding value, but hasn't removed his previous value.") ); + return; + } + + if (!getControllingPlayer()) + return; + + if( getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) || isEffectivelyDead() || getShroudClearingRange() <= 0.0f ) + return; + + + m_partitionLastValue->m_where = *getPosition(); + m_partitionLastValue->m_data = getTemplate()->friend_getBuildCost(); + + m_partitionLastValue->m_forWhom = getControllingPlayer()->getPlayerMask(); + m_partitionLastValue->m_howFar = getVisionRange(); // we are valuable all the way to where we can target. + + ThePartitionManager->doValueAffect(m_partitionLastValue->m_where.x, + m_partitionLastValue->m_where.y, + m_partitionLastValue->m_howFar, + m_partitionLastValue->m_data, + m_partitionLastValue->m_forWhom + ); +} + +//------------------------------------------------------------------------------------------------- +void Object::removeValue() +{ + if( m_partitionLastValue->isInvalid() ) + { + // removing before adding is valid, cause we always remove before adding. (So the first remove + // will occur before the first add) + return; + } + + ThePartitionManager->undoValueAffect(m_partitionLastValue->m_where.x, + m_partitionLastValue->m_where.y, + m_partitionLastValue->m_howFar, + m_partitionLastValue->m_data, + m_partitionLastValue->m_forWhom + ); + + m_partitionLastValue->reset(); +} + +//------------------------------------------------------------------------------------------------- +void Object::addThreat() +{ + if( !m_partitionLastThreat->isInvalid() ) + { + DEBUG_CRASH( ("An Object is adding threat, but hasn't removed his previous threat. (He hasn't finished the threat?)") ); + return; + } + + if (!getControllingPlayer()) + return; + + if( getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) || isEffectivelyDead() || getShroudClearingRange() <= 0.0f ) + return; + + + m_partitionLastThreat->m_where = *getPosition(); + m_partitionLastThreat->m_data = getTemplate()->getThreatValue(); + + m_partitionLastThreat->m_forWhom = getControllingPlayer()->getPlayerMask(); + m_partitionLastThreat->m_howFar = getVisionRange(); // we are threatening all the way to where we can target. + + ThePartitionManager->doThreatAffect(m_partitionLastThreat->m_where.x, + m_partitionLastThreat->m_where.y, + m_partitionLastThreat->m_howFar, + m_partitionLastThreat->m_data, + m_partitionLastThreat->m_forWhom + ); +} + +//------------------------------------------------------------------------------------------------- +void Object::removeThreat() +{ + if( m_partitionLastThreat->isInvalid() ) + { + // removing before adding is valid, cause we always remove before adding. (So the first remove + // will occur before the first add) + return; + } + + ThePartitionManager->undoThreatAffect(m_partitionLastThreat->m_where.x, + m_partitionLastThreat->m_where.y, + m_partitionLastThreat->m_howFar, + m_partitionLastThreat->m_data, + m_partitionLastThreat->m_forWhom + ); + + m_partitionLastThreat->reset(); +} + + + +//------------------------------------------------------------------------------------------------- +void Object::look() +{ + if( ! m_partitionLastLook->isInvalid() ) + { + DEBUG_CRASH( ("An Object is looking, but hasn't unlooked the last one.") ); + return; + } + + Player* controller = getControllingPlayer(); + if ( controller ) + { + // I removed the check for objects under construction by request of designers since + // they want constructing objects to have a reduced sight range now. -MW + // dead or blind things don't reveal shroud + + + + // Some things get Destroyed directly without hitting Death. + if( !isDestroyed() && !isEffectivelyDead() ) + { + + ContainModuleInterface * contain = (getContainedBy() ? getContainedBy()->getContain() : NULL); + if ( contain && !contain->isGarrisonable() ) + return;// dont look, 'cause you are in a tunnel, now + // GS 10-20 Need to expand that exception to all transports or else you get a perma reveal where + // you entered the transport. Remember, this hackiness is caused by the fact that we never realized that + // garrisoned buildings weren't looking, we were just seeing the leftover last look of the guy inside. + // Otherwise we'd just have enclosingContainer control looking which is the 'correct' answer. + + Real shroudClearingRange = getShroudClearingRange(); + if( shroudClearingRange > 0.0f ) + { + PlayerMaskType lookingMask = 0; + + if ( isKindOf(KINDOF_REVEAL_TO_ALL) ) + { + lookingMask = PLAYERMASK_ALL; + } + else + { + for( Int currentIndex = ThePlayerList->getPlayerCount() - 1; currentIndex >=0; currentIndex-- ) + { + const Player *currentPlayer = ThePlayerList->getNthPlayer( currentIndex ); + + // Build mask of of allies who can see me. + // This is the Object-centric game level that cares + if( getControllingPlayer()->getRelationship( currentPlayer->getDefaultTeam() ) == ALLIES ) + { + lookingMask |= currentPlayer->getPlayerMask(); + } + } + + // Other players can also be looking through our eyes. + lookingMask |= m_visionSpiedMask; + } + + Coord3D pos = *getPosition(); + ThePartitionManager->doShroudReveal( pos.x, pos.y, shroudClearingRange, lookingMask ); + + m_partitionLastLook->m_where = pos; + m_partitionLastLook->m_forWhom = lookingMask; + m_partitionLastLook->m_howFar = getShroudClearingRange(); + + // DEBUG_LOG(( "A %s looks at %f, %f for %x at range %f\n", + // getTemplate()->getName().str(), + // pos.x, + // pos.y, + // lookingMask, + // getShroudClearingRange() + // )); + } + + //Now reveal to everyone if we're special. Note this works differently than KINDOF_REVEAL_TO_ALL because + //the kindof uses the same range as allies, spies, and owners would see. This template based shroud + //reveal to all range can specify a different value so we can get a much smaller reveal distance. + // And don't reveal while under construction. When finished, a refresh occurs, so don't worry. + Real shroudRevealToAllRange = getTemplate()->getShroudRevealToAllRange(); + if( shroudRevealToAllRange > 0.0f && !testStatus( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) + { + //Kris: August 20, 2003 + //Seeing I added this concept, I'm changing it now to only reveal to all when the unit is visible. If it's stealthed, + //we won't reveal it anymore (stealth general scudstorm). + Bool stealthedAndNotDetected = testStatus( OBJECT_STATUS_STEALTHED ) && !testStatus( OBJECT_STATUS_DETECTED ) && !testStatus( OBJECT_STATUS_DISGUISED ); + if( !stealthedAndNotDetected ) + { + Coord3D pos = *getPosition(); + PlayerMaskType thePlayersMask = ThePlayerList->getPlayersWithRelationship( getControllingPlayer()->getPlayerIndex(), ALLOW_ENEMIES | ALLOW_NEUTRAL ); + ThePartitionManager->doShroudReveal( pos.x, pos.y, shroudRevealToAllRange, thePlayersMask ); + m_partitionRevealAllLastLook->m_where = pos; + m_partitionRevealAllLastLook->m_forWhom = thePlayersMask; + m_partitionRevealAllLastLook->m_howFar = shroudRevealToAllRange; + } + } + } + } +} + +//------------------------------------------------------------------------------------------------- +void Object::unlook() +{ + if( m_partitionLastLook->isInvalid() ) + { + // Your very first action will be an unlook, so of course you haven't looked yet. This is not an error + // This early return prevents an extra unlook if you never looked. Like you have 0 vision. + return; + } + + ThePartitionManager->queueUndoShroudReveal(m_partitionLastLook->m_where.x, + m_partitionLastLook->m_where.y, + m_partitionLastLook->m_howFar, + m_partitionLastLook->m_forWhom + ); + +// DEBUG_LOG(( "A %s queues an unlook at %f, %f for %x at range %f\n", +// getTemplate()->getName().str(), +// m_partitionLastLook.m_where.x, +// m_partitionLastLook.m_where.y, +// m_partitionLastLook.m_forWhom, +// m_partitionLastLook.m_howFar +// )); + + m_partitionLastLook->reset(); + + if( !m_partitionRevealAllLastLook->isInvalid() ) + { + ThePartitionManager->queueUndoShroudReveal(m_partitionRevealAllLastLook->m_where.x, + m_partitionRevealAllLastLook->m_where.y, + m_partitionRevealAllLastLook->m_howFar, + m_partitionRevealAllLastLook->m_forWhom + ); + + m_partitionRevealAllLastLook->reset(); + } +} + +//------------------------------------------------------------------------------------------------- +void Object::shroud() +{ + if( ! m_partitionLastShroud->isInvalid() ) + { + DEBUG_CRASH( ("An Object is shrouding, but hasn't unshrouded the last one.") ); + return; + } + + Player* controller = getControllingPlayer(); + if ( controller ) + { + // things under construction don't shroud. (srj), nor do dead or blind things + if( !getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) && !isEffectivelyDead() && getShroudRange() > 0.0f ) + { + PlayerMaskType shroudingMask = 0; + for( Int currentIndex = ThePlayerList->getPlayerCount() - 1; currentIndex >=0; currentIndex-- ) + { + const Player *currentPlayer = ThePlayerList->getNthPlayer( currentIndex ); + //Build mask of NON-allies. This is the Object-centric game level that cares + if( getControllingPlayer()->getRelationship( currentPlayer->getDefaultTeam() ) != ALLIES ) + { + shroudingMask |= currentPlayer->getPlayerMask(); + } + } + + Coord3D pos = *getPosition(); + ThePartitionManager->doShroudCover(pos.x, pos.y, + getShroudRange(), + shroudingMask); + + m_partitionLastShroud->m_where = pos; + m_partitionLastShroud->m_forWhom = shroudingMask; + m_partitionLastShroud->m_howFar = getShroudRange(); + } + } +} + +//------------------------------------------------------------------------------------------------- +void Object::unshroud() +{ + if( m_partitionLastShroud->isInvalid() ) + { + // Your very first action will be an unlook, so of course you haven't looked yet. This is not an error + // This early return prevents an extra unlook if you never looked. Like you have 0 shroud generation. + return; + } + + ThePartitionManager->undoShroudCover(m_partitionLastShroud->m_where.x, + m_partitionLastShroud->m_where.y, + m_partitionLastShroud->m_howFar, + m_partitionLastShroud->m_forWhom); + + m_partitionLastShroud->reset(); +} + +//------------------------------------------------------------------------------------------------- +Real Object::getVisionRange() const +{ +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + if (TheGlobalData->m_debugVisibility) + { + Vector3 pos(m_visionRange, 0, 0); + for (int i = 0; i < TheGlobalData->m_debugVisibilityTileCount; ++i) + { + pos.Rotate_Z(1.0f * i / TheGlobalData->m_debugVisibilityTileCount * 2 * PI); + Coord3D coord = { pos.X + getPosition()->x, pos.Y + getPosition()->y, pos.Z + getPosition()->z }; + + addIcon(&coord, TheGlobalData->m_debugVisibilityTileWidth, + TheGlobalData->m_debugVisibilityTileDuration, + TheGlobalData->m_debugVisibilityTargettableColor); + } + } +#endif + return m_visionRange; +} + +//------------------------------------------------------------------------------------------------- +void Object::setVisionRange( Real newVisionRange ) +{ + m_visionRange = newVisionRange; +} + +//------------------------------------------------------------------------------------------------- +Real Object::getShroudClearingRange() const +{ + Real shroudClearingRange=m_shroudClearingRange; + + if( getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) ) + { + //structures under construction have limited vision range. For now, base it + //on the geometry extents so the structure can only see itself. + shroudClearingRange = getGeometryInfo().getBoundingCircleRadius(); + } + +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + if (TheGlobalData->m_debugVisibility) + { + Vector3 pos(shroudClearingRange, 0, 0); + for (int i = 0; i < TheGlobalData->m_debugVisibilityTileCount; ++i) + { + pos.Rotate_Z(1.0f * i / TheGlobalData->m_debugVisibilityTileCount * 2 * PI); + Coord3D coord = { pos.X + getPosition()->x, pos.Y + getPosition()->y, pos.Z + getPosition()->z }; + + addIcon(&coord, TheGlobalData->m_debugVisibilityTileWidth, + TheGlobalData->m_debugVisibilityTileDuration, + TheGlobalData->m_debugVisibilityDeshroudColor); + } + } +#endif + + return shroudClearingRange; +} + +//------------------------------------------------------------------------------------------------- +void Object::setShroudClearingRange( Real newShroudClearingRange ) +{ + if( newShroudClearingRange != m_shroudClearingRange ) + { + // The partition cell refresh is a slow operation, so only do it if you really have to. + // Range change is a valid reason to relook. + m_shroudClearingRange = newShroudClearingRange; + + /* + Complete and total monkey hack fix. + + The problem: newObject doesn't get an initial pos, so all objects start at 0,0,0. + Most code paths instantly move 'em to a good pos, but in some cases, that is too late: + If we have search-and-destroy battle plan, we will apply it at that point, and clear out + a vision range based on our current (wrong) location. Doh! + + So, this just sez: if you are at 0,0,0, don't call handlePartitionCellMaintenance()... since + you will either (1) be moved elsewhere immediately, thus forcing it to be called via + another route anyway, or (2) not be moved, which means you are a very naughty and worthless + object anyway and we should just ignore you. + + Proper fix for next version is to require initial pos to be passed in to newObject so that + all objects can start at their proper initial position from the start of the ctor. + + (srj) + */ + const Coord3D* pos = getPosition(); + if (pos->x || pos->y || pos->z) + { + handlePartitionCellMaintenance(); + } + } +} + +//------------------------------------------------------------------------------------------------- +Real Object::getShroudRange() const +{ +#if defined(RTS_DEBUG) || defined(RTS_INTERNAL) + if (TheGlobalData->m_debugVisibility) + { + Vector3 pos(m_shroudRange, 0, 0); + for (int i = 0; i < TheGlobalData->m_debugVisibilityTileCount; ++i) + { + pos.Rotate_Z(1.0f * i / TheGlobalData->m_debugVisibilityTileCount * 2 * PI); + Coord3D coord = { pos.X + getPosition()->x, pos.Y + getPosition()->y, pos.Z + getPosition()->z }; + + addIcon(&coord, TheGlobalData->m_debugVisibilityTileWidth, + TheGlobalData->m_debugVisibilityTileDuration, + TheGlobalData->m_debugVisibilityGapColor); + } + } +#endif + + return m_shroudRange; +} + +//------------------------------------------------------------------------------------------------- +void Object::setShroudRange( Real newShroudRange ) +{ + m_shroudRange = newShroudRange; +} + +//------------------------------------------------------------------------------------------------- +void Object::setVisionSpied(Bool setting, Int byWhom) +{ + Bool needRefresh = FALSE; // If this setting is an edge trigger on the reference count, I need to refresh + + if( setting ) + { + m_visionSpiedBy[ byWhom ] = m_visionSpiedBy[ byWhom ] + 1; + if( m_visionSpiedBy[ byWhom ] == 1 ) + needRefresh = TRUE; + } + else + { + m_visionSpiedBy[ byWhom ] = m_visionSpiedBy[ byWhom ] - 1; + if( m_visionSpiedBy[ byWhom ] == 0 ) + needRefresh = TRUE; + } + + if( needRefresh ) + { + PlayerMaskType workingMask = 0; + for (Int i = 0; i < MAX_PLAYER_COUNT; ++i) + { + if( m_visionSpiedBy[i] > 0 ) + BitSet( workingMask, ( 1 << i ) ); + else + BitClear( workingMask, ( 1 << i ) ); + } + + m_visionSpiedMask = workingMask; + + handlePartitionCellMaintenance(); + } +} + +//------------------------------------------------------------------------------------------------- +void Object::doStatusDamage( ObjectStatusTypes status, Real duration ) +{ + if(m_statusDamageHelper) + m_statusDamageHelper->doStatusDamage(status, duration); +} + +//------------------------------------------------------------------------------------------------- +void Object::doTempWeaponBonus( WeaponBonusConditionType status, UnsignedInt duration, TintStatus tintStatus) +{ + if(m_tempWeaponBonusHelper) + m_tempWeaponBonusHelper->doTempWeaponBonus(status, duration, tintStatus); +} + +//------------------------------------------------------------------------------------------------- +void Object::notifySubdualDamage( Real amount ) +{ + if(m_subdualDamageHelper) + m_subdualDamageHelper->notifySubdualDamage( amount ); + + // If we are gaining subdual damage, we are slowly tinting + if( getDrawable() ) + { + if( amount > 0 ) + getDrawable()->setTintStatus(TINT_STATUS_GAINING_SUBDUAL_DAMAGE); + else + getDrawable()->clearTintStatus(TINT_STATUS_GAINING_SUBDUAL_DAMAGE); + } +} + +//------------------------------------------------------------------------------------------------- +void Object::notifyChronoDamage(Real amount) +{ + if (m_chronoDamageHelper) + m_chronoDamageHelper->notifyChronoDamage(amount); + + //Real progress = INT_TO_REAL(now - m_dieFrame) / INT_TO_REAL(m_destructionFrame - m_dieFrame); + + BodyModuleInterface* body = getBodyModule(); + Drawable* draw = getDrawable(); + if (body != NULL && draw != NULL) { + + Real chronoTh = TheGlobalData->m_chronoDamageDisableThreshold * body->getMaxHealth(); + Real chronoDmg = body->getCurrentChronoDamageAmount(); + if (chronoDmg > chronoTh) { + Real progress = (chronoDmg - chronoTh) / (body->getMaxHealth() - chronoTh); + progress = min(1.0f, max(0.0f, progress)); + + Real alpha0 = TheGlobalData->m_chronoDisableAlphaStart; + Real alpha1 = TheGlobalData->m_chronoDisableAlphaEnd; + Real opacity = (1.0 - progress) * alpha0 + progress * alpha1; + + // DEBUG_LOG(("Object::notifyChronoDamage - progress = %f, alpha = %f\n", progress, opacity)); + + draw->setDrawableOpacity(opacity); + //draw->setEffectiveOpacity(opacity); + //draw->setSecondMaterialPassOpacity(opacity); + + } + else if (amount < 0) { + draw->setDrawableOpacity(1.0); + // DEBUG_LOG(("Object::notifyChronoDamage - reset opacity\n")); + } + } + + //If we are gaining chrono damage, we are slowly tinting + if (getDrawable()) + { + if (amount > 0) + getDrawable()->setTintStatus(TINT_STATUS_GAINING_CHRONO_DAMAGE); + else + getDrawable()->clearTintStatus(TINT_STATUS_GAINING_CHRONO_DAMAGE); + } +} + +//------------------------------------------------------------------------------------------------- +/** Given a special power template, find the module in the object that can implement it. + * There can be at most one */ +//------------------------------------------------------------------------------------------------- +SpecialPowerModuleInterface *Object::getSpecialPowerModule( const SpecialPowerTemplate *specialPowerTemplate ) const +{ + + // sanity + if( specialPowerTemplate == NULL ) + return NULL; + + // search the modules for the one with the matching template + for( BehaviorModule** m = m_behaviors; *m; ++m ) + { + SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); + if (!sp) + continue; + + if( sp->isModuleForPower( specialPowerTemplate ) ) + return sp; + } + + return NULL; + +} + +//------------------------------------------------------------------------------------------------- +/** Execute special power */ +//------------------------------------------------------------------------------------------------- +void Object::doSpecialPower( const SpecialPowerTemplate *specialPowerTemplate, UnsignedInt commandOptions, Bool forced ) +{ + + if (isDisabled()) + return; + + // sanity + if( !forced && TheSpecialPowerStore->canUseSpecialPower( this, specialPowerTemplate ) == FALSE ) + return; + + // get the module and execute + SpecialPowerModuleInterface *mod = getSpecialPowerModule( specialPowerTemplate ); + if( mod ) + mod->doSpecialPower( commandOptions ); + +} + +//------------------------------------------------------------------------------------------------- +/** Execute special power */ +//------------------------------------------------------------------------------------------------- +void Object::doSpecialPowerAtObject( const SpecialPowerTemplate *specialPowerTemplate, Object *obj, UnsignedInt commandOptions, Bool forced ) +{ + + if (isDisabled()) + return; + + // sanity + if( !forced && TheSpecialPowerStore->canUseSpecialPower( this, specialPowerTemplate ) == FALSE ) + return; + + // get the module and execute + SpecialPowerModuleInterface *mod = getSpecialPowerModule( specialPowerTemplate ); + if( mod ) + mod->doSpecialPowerAtObject( obj, commandOptions ); +} + +//------------------------------------------------------------------------------------------------- +/** Execute special power */ +//------------------------------------------------------------------------------------------------- +void Object::doSpecialPowerAtLocation( const SpecialPowerTemplate *specialPowerTemplate, + const Coord3D *loc, Real angle, UnsignedInt commandOptions, Bool forced ) +{ + + if (isDisabled()) + return; + + // sanity + if( !forced && TheSpecialPowerStore->canUseSpecialPower( this, specialPowerTemplate ) == FALSE ) + return; + + // get the module and execute + SpecialPowerModuleInterface *mod = getSpecialPowerModule( specialPowerTemplate ); + if( mod ) + mod->doSpecialPowerAtLocation( loc, angle, commandOptions ); + +} + +//------------------------------------------------------------------------------------------------- +/** Execute special power */ +//------------------------------------------------------------------------------------------------- +void Object::doSpecialPowerUsingWaypoints( const SpecialPowerTemplate *specialPowerTemplate, const Waypoint *way, UnsignedInt commandOptions, Bool forced ) +{ + + if (isDisabled()) + return; + + // sanity + if( !forced && TheSpecialPowerStore->canUseSpecialPower( this, specialPowerTemplate ) == FALSE ) + return; + + // get the module and execute + SpecialPowerModuleInterface *mod = getSpecialPowerModule( specialPowerTemplate ); + if( mod ) + mod->doSpecialPowerUsingWaypoints( way, commandOptions ); + +} + +//------------------------------------------------------------------------------------------------- +/** Execute command button ability */ +//------------------------------------------------------------------------------------------------- +void Object::doCommandButton( const CommandButton *commandButton, CommandSourceType cmdSource ) +{ + if (isDisabled()) + return; + + AIUpdateInterface *ai = getAIUpdateInterface(); + if( commandButton ) + { + switch( commandButton->getCommandType() ) + { + case GUI_COMMAND_SPECIAL_POWER: + if( commandButton->getSpecialPowerTemplate() ) + { + CommandOption commandOptions = (CommandOption)(commandButton->getOptions() | COMMAND_FIRED_BY_SCRIPT); + doSpecialPower( commandButton->getSpecialPowerTemplate(), commandOptions, cmdSource == CMD_FROM_SCRIPT ); + return; + } + break; + case GUI_COMMAND_STOP: + if( ai ) + { + ai->aiIdle( cmdSource ); + return; + } + break; + + case GUI_COMMAND_SWITCH_WEAPON: + { + WeaponSlotType weaponSlot = commandButton->getWeaponSlot(); + // GUI_COMMAND_SWITCH_WEAPON switches until un-switched, or switched to something else. + setWeaponLock( weaponSlot, LOCKED_PERMANENTLY ); + return; + } + + case GUI_COMMAND_FIRE_WEAPON: + if( ai ) + { + if( !BitIsSet( commandButton->getOptions(), COMMAND_OPTION_NEED_OBJECT_TARGET ) && !BitIsSet( commandButton->getOptions(), NEED_TARGET_POS ) ) + { + setWeaponLock( commandButton->getWeaponSlot(), LOCKED_TEMPORARILY ); + //LOCATION BASED FIRE WEAPON + ai->aiAttackPosition( NULL, commandButton->getMaxShotsToFire(), cmdSource ); + } + else + { + DEBUG_CRASH( ("WARNING: Script doCommandButton for button %s cannot fire weapon with NO POSITION. Skipping.", commandButton->getName().str()) ); + } + return; + } + break; + + case GUI_COMMAND_OBJECT_UPGRADE: + case GUI_COMMAND_PLAYER_UPGRADE: + { + const UpgradeTemplate *upgradeT = commandButton->getUpgradeTemplate(); + DEBUG_ASSERTCRASH( upgradeT, ("Undefined upgrade '%s' in player upgrade command\n", "UNKNOWN") ); + // sanity + if( upgradeT == NULL ) + break; + if( upgradeT->getUpgradeType() == UPGRADE_TYPE_OBJECT ) + { + if( hasUpgrade( upgradeT ) || !affectedByUpgrade( upgradeT ) ) + break; + } + // producer must have a production update + ProductionUpdateInterface *pu = getProductionUpdateInterface(); + if( pu == NULL ) + break; + // queue the upgrade "research" + pu->queueUpgrade( upgradeT ); + } + return; + case GUI_COMMAND_UNIT_BUILD: + case GUI_COMMAND_DOZER_CONSTRUCT: { + const ThingTemplate *tt = commandButton->getThingTemplate(); + ProductionUpdateInterface *pu = this->getProductionUpdateInterface(); + if (pu && tt) { + pu->queueCreateUnit( tt, pu->requestUniqueUnitID()); + return; + } + break; + } + case GUI_COMMAND_HACK_INTERNET:{ + if( ai ) + { + ai->aiHackInternet( cmdSource ); + return; + } + break; + } + + case GUI_COMMAND_SELL: + TheBuildAssistant->sellObject( this ); + return; + + //Feel free to implement object based command buttons. + case GUI_COMMAND_COMBATDROP: + case GUI_COMMAND_DOZER_CONSTRUCT_CANCEL: + case GUI_COMMAND_CANCEL_UNIT_BUILD: + case GUI_COMMAND_CANCEL_UPGRADE: + case GUI_COMMAND_ATTACK_MOVE: + case GUI_COMMAND_GUARD: + case GUI_COMMAND_GUARD_WITHOUT_PURSUIT: + case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY: + case GUI_COMMAND_WAYPOINTS: + case GUI_COMMAND_EXIT_CONTAINER: + case GUI_COMMAND_EVACUATE: + case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT: + case GUI_COMMAND_BEACON_DELETE: + case GUI_COMMAND_SET_RALLY_POINT: + case GUI_COMMAND_TOGGLE_OVERCHARGE: +#ifdef ALLOW_SURRENDER + case GUI_COMMAND_POW_RETURN_TO_PRISON: +#endif + case GUICOMMANDMODE_HIJACK_VEHICLE: + case GUICOMMANDMODE_CONVERT_TO_CARBOMB: +#ifdef ALLOW_SURRENDER + case GUICOMMANDMODE_PICK_UP_PRISONER: +#endif + default: + break; + } + DEBUG_CRASH( ("WARNING: Script doCommandButton for button %s not implemented. Doing nothing.", commandButton->getName().str()) ); + } +} + +//------------------------------------------------------------------------------------------------- +/** Execute command button ability directed at an object target */ +//------------------------------------------------------------------------------------------------- +void Object::doCommandButtonAtObject( const CommandButton *commandButton, Object *obj, CommandSourceType cmdSource ) +{ + if (isDisabled()) + return; + + AIUpdateInterface *ai = getAIUpdateInterface(); + if( commandButton ) + { + switch( commandButton->getCommandType() ) + { + case GUI_COMMAND_COMBATDROP: + if( ai ) + { + ai->aiCombatDrop( obj, *(obj->getPosition()), cmdSource ); + } + return; + case GUI_COMMAND_SPECIAL_POWER: + { + if( commandButton->getSpecialPowerTemplate() ) + { + CommandOption commandOptions = (CommandOption)(commandButton->getOptions() | COMMAND_FIRED_BY_SCRIPT); + doSpecialPowerAtObject( commandButton->getSpecialPowerTemplate(), obj, commandOptions, cmdSource == CMD_FROM_SCRIPT ); + } + return; + } + + case GUI_COMMAND_STOP: + if( ai ) + { + ai->aiIdle( cmdSource ); + } + return; + + case GUI_COMMAND_FIRE_WEAPON: + if( ai ) + { + if( BitIsSet( commandButton->getOptions(), COMMAND_OPTION_NEED_OBJECT_TARGET ) ) + { + //OBJECT BASED FIRE WEAPON + if( !obj ) + { + break; + } + + if( !commandButton->isValidObjectTarget( this, obj ) ) + { + break; + } + + setWeaponLock( commandButton->getWeaponSlot(), LOCKED_TEMPORARILY ); + + if( BitIsSet( commandButton->getOptions(), ATTACK_OBJECTS_POSITION ) ) + { + //Actually, you know what.... we want to attack the object's location instead. + ai->aiAttackPosition( obj->getPosition(), commandButton->getMaxShotsToFire(), cmdSource ); + } + else + { + ai->aiAttackObject( obj, commandButton->getMaxShotsToFire(), cmdSource ); + } + } + else + { + DEBUG_CRASH( ("WARNING: Script doCommandButtonAtObject for button %s cannot fire weapon at AN OBJECT. Skipping.", commandButton->getName().str()) ); + } + return; + } + break; + + case GUICOMMANDMODE_HIJACK_VEHICLE: + case GUICOMMANDMODE_CONVERT_TO_CARBOMB: + case GUICOMMANDMODE_SABOTAGE_BUILDING: + if( ai ) + { + ai->aiEnter( obj, cmdSource ); + } + return; + + //Feel free to implement object based command buttons. + case GUI_COMMAND_DOZER_CONSTRUCT: + case GUI_COMMAND_DOZER_CONSTRUCT_CANCEL: + case GUI_COMMAND_UNIT_BUILD: + case GUI_COMMAND_CANCEL_UNIT_BUILD: + case GUI_COMMAND_PLAYER_UPGRADE: + case GUI_COMMAND_OBJECT_UPGRADE: + case GUI_COMMAND_CANCEL_UPGRADE: + case GUI_COMMAND_ATTACK_MOVE: + case GUI_COMMAND_GUARD: + case GUI_COMMAND_GUARD_WITHOUT_PURSUIT: + case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY: + case GUI_COMMAND_WAYPOINTS: + case GUI_COMMAND_EXIT_CONTAINER: + case GUI_COMMAND_EVACUATE: + case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT: + case GUI_COMMAND_BEACON_DELETE: + case GUI_COMMAND_SET_RALLY_POINT: + case GUI_COMMAND_SELL: + case GUI_COMMAND_HACK_INTERNET: + case GUI_COMMAND_TOGGLE_OVERCHARGE: + case GUI_COMMAND_SWITCH_WEAPON: + +#ifdef ALLOW_SURRENDER + case GUI_COMMAND_POW_RETURN_TO_PRISON: + case GUICOMMANDMODE_PICK_UP_PRISONER: +#endif + default: + break; + } + DEBUG_CRASH( ("WARNING: Script doCommandButtonAtObject for button %s not implemented. Doing nothing.", commandButton->getName().str()) ); + } +} + +//------------------------------------------------------------------------------------------------- +/** Execute command button ability directed at a location */ +//------------------------------------------------------------------------------------------------- +void Object::doCommandButtonAtPosition( const CommandButton *commandButton, const Coord3D *pos, CommandSourceType cmdSource ) +{ + if (isDisabled()) + return; + + AIUpdateInterface *ai = getAIUpdateInterface(); + if( commandButton ) + { + switch( commandButton->getCommandType() ) + { + case GUI_COMMAND_SPECIAL_POWER: + { + if( commandButton->getSpecialPowerTemplate() ) + { + CommandOption commandOptions = (CommandOption)(commandButton->getOptions() | COMMAND_FIRED_BY_SCRIPT); + doSpecialPowerAtLocation( commandButton->getSpecialPowerTemplate(), pos, INVALID_ANGLE, commandOptions, cmdSource == CMD_FROM_SCRIPT ); + return; + } + break; + } + case GUI_COMMAND_ATTACK_MOVE: + if( ai ) + { + ai->aiAttackMoveToPosition( pos, commandButton->getMaxShotsToFire(), cmdSource ); + return; + } + break; + case GUI_COMMAND_STOP: + if( ai ) + { + ai->aiIdle( cmdSource ); + return; + } + break; + + case GUI_COMMAND_DOZER_CONSTRUCT: + TheBuildAssistant->buildObjectNow( this, commandButton->getThingTemplate(), pos, 0.0f, getControllingPlayer() ); + return; + + case GUI_COMMAND_FIRE_WEAPON: + if( ai ) + { + if( BitIsSet( commandButton->getOptions(), NEED_TARGET_POS ) ) + { + //LOCATION BASED FIRE WEAPON + if( !pos ) + { + break; + } + setWeaponLock( commandButton->getWeaponSlot(), LOCKED_TEMPORARILY ); + ai->aiAttackPosition( pos, commandButton->getMaxShotsToFire(), cmdSource ); + } + else + { + DEBUG_CRASH( ("WARNING: Script doCommandButtonAtPosition for button %s cannot fire weapon at A POSITION. Skipping.", commandButton->getName().str()) ); + } + return; + } + break; + + case GUI_COMMAND_DOZER_CONSTRUCT_CANCEL: + case GUI_COMMAND_UNIT_BUILD: + case GUI_COMMAND_CANCEL_UNIT_BUILD: + case GUI_COMMAND_PLAYER_UPGRADE: + case GUI_COMMAND_OBJECT_UPGRADE: + case GUI_COMMAND_CANCEL_UPGRADE: + case GUI_COMMAND_GUARD: + case GUI_COMMAND_GUARD_WITHOUT_PURSUIT: + case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY: + case GUI_COMMAND_WAYPOINTS: + case GUI_COMMAND_EXIT_CONTAINER: + case GUI_COMMAND_EVACUATE: + case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT: + case GUI_COMMAND_BEACON_DELETE: + case GUI_COMMAND_SET_RALLY_POINT: + case GUI_COMMAND_SELL: + case GUI_COMMAND_HACK_INTERNET: + case GUI_COMMAND_TOGGLE_OVERCHARGE: +#ifdef ALLOW_SURRENDER + case GUI_COMMAND_POW_RETURN_TO_PRISON: +#endif + case GUI_COMMAND_COMBATDROP: + case GUI_COMMAND_SWITCH_WEAPON: + case GUICOMMANDMODE_HIJACK_VEHICLE: + case GUICOMMANDMODE_CONVERT_TO_CARBOMB: +#ifdef ALLOW_SURRENDER + case GUICOMMANDMODE_PICK_UP_PRISONER: +#endif + default: + break; + } + DEBUG_CRASH( ("WARNING: Script doCommandButtonAtPosition for button %s not implemented. Doing nothing.", commandButton->getName().str()) ); + } +} + +//------------------------------------------------------------------------------------------------- +/** Execute command button ability directed at a location */ +//------------------------------------------------------------------------------------------------- +void Object::doCommandButtonUsingWaypoints( const CommandButton *commandButton, const Waypoint *way, CommandSourceType cmdSource ) +{ + if (isDisabled()) + return; + + if( commandButton ) + { + if( !BitIsSet( commandButton->getOptions(), CAN_USE_WAYPOINTS ) ) + { + //Our button doesn't support waypoints. + DEBUG_CRASH( ("WARNING: Script doCommandButtonUsingWaypoints for button %s lacks CAN_USE_WAYPOINTS option. Doing nothing.", commandButton->getName().str()) ); + return; + } + switch( commandButton->getCommandType() ) + { + case GUI_COMMAND_SPECIAL_POWER: + { + if( commandButton->getSpecialPowerTemplate() ) + { + CommandOption commandOptions = (CommandOption)(commandButton->getOptions() | COMMAND_FIRED_BY_SCRIPT); + doSpecialPowerUsingWaypoints( commandButton->getSpecialPowerTemplate(), way, commandOptions, cmdSource == CMD_FROM_SCRIPT ); + return; + } + break; + } + case GUI_COMMAND_ATTACK_MOVE: + case GUI_COMMAND_STOP: + case GUI_COMMAND_DOZER_CONSTRUCT: + case GUI_COMMAND_DOZER_CONSTRUCT_CANCEL: + case GUI_COMMAND_UNIT_BUILD: + case GUI_COMMAND_CANCEL_UNIT_BUILD: + case GUI_COMMAND_PLAYER_UPGRADE: + case GUI_COMMAND_OBJECT_UPGRADE: + case GUI_COMMAND_CANCEL_UPGRADE: + case GUI_COMMAND_GUARD: + case GUI_COMMAND_GUARD_WITHOUT_PURSUIT: + case GUI_COMMAND_GUARD_FLYING_UNITS_ONLY: + case GUI_COMMAND_WAYPOINTS: + case GUI_COMMAND_EXIT_CONTAINER: + case GUI_COMMAND_EVACUATE: + case GUI_COMMAND_EXECUTE_RAILED_TRANSPORT: + case GUI_COMMAND_BEACON_DELETE: + case GUI_COMMAND_SET_RALLY_POINT: + case GUI_COMMAND_SELL: + case GUI_COMMAND_FIRE_WEAPON: + case GUI_COMMAND_HACK_INTERNET: + case GUI_COMMAND_TOGGLE_OVERCHARGE: +#ifdef ALLOW_SURRENDER + case GUI_COMMAND_POW_RETURN_TO_PRISON: +#endif + case GUI_COMMAND_COMBATDROP: + case GUI_COMMAND_SWITCH_WEAPON: + case GUICOMMANDMODE_HIJACK_VEHICLE: + case GUICOMMANDMODE_CONVERT_TO_CARBOMB: +#ifdef ALLOW_SURRENDER + case GUICOMMANDMODE_PICK_UP_PRISONER: +#endif + default: + break; + } + DEBUG_CRASH( ("WARNING: Script doCommandButtonUsingWaypoints for button %s not implemented. Doing nothing.", commandButton->getName().str()) ); + } +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void Object::clearLeechRangeModeForAllWeapons() +{ + m_weaponSet.clearLeechRangeModeForAllWeapons(); +} + +// ------------------------------------------------------------------------------------------------ +/** Search our update modules for a production update interface and return it if one is found */ +// ------------------------------------------------------------------------------------------------ +ProductionUpdateInterface* Object::getProductionUpdateInterface( void ) +{ + ProductionUpdateInterface *pui; + + // tell our update modules that we intend to do this special power. + for( BehaviorModule** u = m_behaviors; *u; ++u ) + { + + pui = (*u)->getProductionUpdateInterface(); + if( pui ) + return pui; + + } // end for + + return NULL; + +} // end getProductionUpdateInterface + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +DockUpdateInterface *Object::getDockUpdateInterface( void ) +{ + DockUpdateInterface *dock = NULL; + + for( BehaviorModule **u = m_behaviors; *u; ++u ) + { + if( (dock = (*u)->getDockUpdateInterface()) != NULL ) + return dock; + } + + return NULL; + +} // end getDockUpdateInterface + +// ------------------------------------------------------------------------------------------------ +// Search our special power modules for a specific one. +// ------------------------------------------------------------------------------------------------ +SpecialPowerModuleInterface* Object::findSpecialPowerModuleInterface( SpecialPowerType type ) const +{ + for (BehaviorModule** m = m_behaviors; *m; ++m) + { + SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); + if (!sp) + continue; + + const SpecialPowerTemplate *spTemplate = sp->getSpecialPowerTemplate(); + if (spTemplate && spTemplate->getSpecialPowerType() == type || type == SPECIAL_INVALID ) + { + return sp; + } + } + return NULL; +} + +// ------------------------------------------------------------------------------------------------ +// Search our special power modules for the first occurrence of a shortcut special. +// ------------------------------------------------------------------------------------------------ +SpecialPowerModuleInterface* Object::findAnyShortcutSpecialPowerModuleInterface() const +{ + for( BehaviorModule** m = m_behaviors; *m; ++m ) + { + SpecialPowerModuleInterface* sp = (*m)->getSpecialPower(); + if (!sp) + continue; + + const SpecialPowerTemplate *spTemplate = sp->getSpecialPowerTemplate(); + if( spTemplate && spTemplate->isShortcutPower() ) + { + return sp; + } + } + return NULL; +} + +// ------------------------------------------------------------------------------------------------ +/** Get spawn behavior interface from object */ +// ------------------------------------------------------------------------------------------------ +SpawnBehaviorInterface* Object::getSpawnBehaviorInterface() const +{ + for (BehaviorModule** m = m_behaviors; *m; ++m) + { + SpawnBehaviorInterface *sbi = (*m)->getSpawnBehaviorInterface(); + if( sbi ) + { + return sbi; + } + } + return NULL; +} // end getSpawnBehaviorInterfaceFromObject + +// ------------------------------------------------------------------------------------------------ +ProjectileUpdateInterface* Object::getProjectileUpdateInterface() const +{ + for (BehaviorModule** m = m_behaviors; *m; ++m) + { + ProjectileUpdateInterface *pui = (*m)->getProjectileUpdateInterface(); + if( pui ) + { + return pui; + } + } + return NULL; +} + +// ------------------------------------------------------------------------------------------------ +// Simply find the special power module that is currently allowing plotting of positions to target. +// ------------------------------------------------------------------------------------------------ +SpecialPowerUpdateInterface* Object::findSpecialPowerWithOverridableDestinationActive( SpecialPowerType type ) const +{ + for( BehaviorModule** u = m_behaviors; *u; ++u ) + { + SpecialPowerUpdateInterface *spInterface = (*u)->getSpecialPowerUpdateInterface(); + if( spInterface ) + { + if( spInterface->doesSpecialPowerHaveOverridableDestinationActive() ) + { + return spInterface; + } + } + } // end for + return NULL; +} + +// ------------------------------------------------------------------------------------------------ +// Simply find the special power module that is potentially allowed to plot positions to target. +// ------------------------------------------------------------------------------------------------ +SpecialPowerUpdateInterface* Object::findSpecialPowerWithOverridableDestination( SpecialPowerType type ) const +{ + for( BehaviorModule** u = m_behaviors; *u; ++u ) + { + SpecialPowerUpdateInterface *spInterface = (*u)->getSpecialPowerUpdateInterface(); + if( spInterface ) + { + if( spInterface->doesSpecialPowerHaveOverridableDestination() ) + { + return spInterface; + } + } + } // end for + return NULL; +} + + +// ------------------------------------------------------------------------------------------------ +// Search our special ability updates for a specific one. +// ------------------------------------------------------------------------------------------------ +SpecialAbilityUpdate* Object::findSpecialAbilityUpdate( SpecialPowerType type ) const +{ + for( BehaviorModule** u = m_behaviors; *u; ++u ) + { + SpecialPowerUpdateInterface *spInterface = (*u)->getSpecialPowerUpdateInterface(); + if( spInterface && spInterface->isSpecialAbility() ) + { + SpecialAbilityUpdate *spUpdate = (SpecialAbilityUpdate*)spInterface; + if( spUpdate->getSpecialPowerType() == type ) + { + return spUpdate; + } + } + } // end for + + return NULL; +} + +// ------------------------------------------------------------------------------------------------ +SpecialPowerCompletionDie* Object::findSpecialPowerCompletionDie() const +{ + static NameKeyType key_SpecialPowerCompletionDie = NAMEKEY("SpecialPowerCompletionDie"); + return (SpecialPowerCompletionDie*)findModule(key_SpecialPowerCompletionDie); +} + +// ------------------------------------------------------------------------------------------------ +Int Object::getNumConsecutiveShotsFiredAtTarget( const Object *victim ) const +{ + return m_firingTracker ? m_firingTracker->getNumConsecutiveShotsAtVictim( victim ) : 0; +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +Bool Object::getSingleLogicalBonePosition(const char* boneName, Coord3D* position, Matrix3D* transform) const +{ + if (m_drawable && m_drawable->getPristineBonePositions( boneName, 0, position, transform, 1 ) == 1 ) + { + m_drawable->convertBonePosToWorldPos( position, transform, position, transform ); + return true; + } + else + { + if (position) + *position = *getPosition(); + if (transform) + *transform = *getTransformMatrix(); + return false; + } +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +Bool Object::getSingleLogicalBonePositionOnTurret( WhichTurretType whichTurret, const char* boneName, Coord3D* position, Matrix3D* transform ) const +{ + Coord3D turretPosition; + Coord3D bonePosition; + if( getDrawable() == NULL || getAI() == NULL ) + return FALSE; + + // We need to find the TurretBone's pristine position. + getDrawable()->getProjectileLaunchOffset( PRIMARY_WEAPON, 1, NULL, whichTurret, &turretPosition, NULL ); + // And the required bone's pristine position + if( getDrawable()->getPristineBonePositions(boneName, 0, &bonePosition, NULL, 1) != 1 ) + return FALSE; + //Then we mojo the Logic position of the required bone like Missile firing does. Using the logic twist of the turret + Real turretRotation; + getAI()->getTurretRotAndPitch( whichTurret, &turretRotation, NULL ); + + Matrix3D boneOffset(TRUE);// This will be from the turret to the requested bone + +// Vector3 bonePositionVector( bonePosition.x - turretPosition.x, +// bonePosition.y - turretPosition.y, +// bonePosition.z - turretPosition.z ); + Vector3 bonePositionVector( bonePosition.x, + bonePosition.y, + bonePosition.z ); + boneOffset.Translate(bonePositionVector); + + Matrix3D turnAdjustment(TRUE);// this is the turret twist to be applied to the final answer + + turnAdjustment.Translate( turretPosition.x, turretPosition.y, turretPosition.z ); + turnAdjustment.In_Place_Pre_Rotate_Z(turretRotation); + turnAdjustment.Translate( -turretPosition.x, -turretPosition.y, -turretPosition.z ); + + Matrix3D boneLogicTransform; + boneLogicTransform.mul( turnAdjustment, boneOffset ); + + Matrix3D worldTransform; + convertBonePosToWorldPos(NULL, &boneLogicTransform, NULL, &worldTransform); + + Vector3 tmp = worldTransform.Get_Translation(); + Coord3D worldPos; + worldPos.x = tmp.X; + worldPos.y = tmp.Y; + worldPos.z = tmp.Z; + + if( position ) + *position = worldPos; + if( transform ) + *transform = worldTransform; + + return TRUE; +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +Int Object::getMultiLogicalBonePosition(const char* boneNamePrefix, Int maxBones, + Coord3D* positions, Matrix3D* transforms, + Bool convertToWorld ) const +{ + Int count; + if (m_drawable && (count = m_drawable->getPristineBonePositions( boneNamePrefix, 1, positions, transforms, maxBones )) > 0 ) + { + if( convertToWorld ) + { + for (Int i = 0; i < count; ++i) + m_drawable->convertBonePosToWorldPos( positions ? &positions[i] : NULL, transforms ? &transforms[i] : NULL, positions ? &positions[i] : NULL, transforms ? &transforms[i] : NULL ); + } + return count; + } + else + { + return 0; + } +} + +//============================================================================= +const AsciiString& Object::getCommandSetString() const +{ + if (m_commandSetStringOverride.isNotEmpty()) + return m_commandSetStringOverride; + + return getTemplate()->friend_getCommandSetString(); +} + +//============================================================================= +Bool Object::canProduceUpgrade( const UpgradeTemplate *upgrade ) +{ + // We need to have the button to make the upgrade. CommandSets are a weird Logic/Client hybrid. + const CommandSet *set = TheControlBar->findCommandSet(getCommandSetString()); + + for( Int buttonIndex = 0; buttonIndex < MAX_COMMANDS_PER_SET; buttonIndex++ ) + { + const CommandButton *button = set->getCommandButton(buttonIndex); + if( button && button->getUpgradeTemplate() && (button->getUpgradeTemplate() == upgrade) ) + return TRUE; // getUpgradeTemplate only returns something if it is actually an upgrade + } + + return FALSE;// Cheatin' punk. +} + +//============================================================================= +// Object::defect, and related methods = +//============================================================================= +void Object::defect( Team* newTeam, UnsignedInt detectionTime ) +{ + if ( isContained() ) //@todo (KRIS?) make contained units unselectable, until then... lorenzen + { + return; + } + + Player *player = getControllingPlayer(); + if ( !player ) + return; + + Team* myTeam = player->getDefaultTeam(); + if ( myTeam == newTeam ) // can't defect from my own team, that would be silly + return; + + // things that are under construction, or sold, cannot defect. + if (testStatus(OBJECT_STATUS_UNDER_CONSTRUCTION) || + testStatus(OBJECT_STATUS_SOLD)) + { + return; + } + + // Before switch //////////////////////////////////////// + + //Design says: + ProductionUpdateInterface *production = getProductionUpdateInterface(); + if ( production ) + { + production->cancelAndRefundAllProduction(); + } + + // pop it up on the radar, so as to warn those who care + // do this first, since after setTeam() the infiltrator + // becomes the controllingplayer, not me + + // But don't do this is if the new team is not a real team. "'Enemy' infiltration" wouldn't make + // sense, and we are probably just reverting a cave or something. + if( friend_getRadarData() && newTeam->getControllingPlayer()->isPlayableSide() && myTeam->getControllingPlayer()->isPlayableSide()) + { + TheRadar->tryInfiltrationEvent( this ); + } + + friend_setUndetectedDefector( detectionTime > 0 ); + + if (m_defectionHelper) + m_defectionHelper->startDefectionTimer(detectionTime); + + // Switch //////////////////////////////////////// + setTeam( newTeam ); + + // After switch //////////////////////////////////////// + + AIUpdateInterface *ai = getAI(); + + handlePartitionCellMaintenance();// to clear the shoud for my new master + + if ( ai ) + { + ai->aiIdle( CMD_FROM_AI ); + } + + // Play our sound indicating we've been defected. (weird verbage, but true.) + AudioEventRTS voiceDefect = *getTemplate()->getVoiceDefect(); + voiceDefect.setObjectID(getID()); + TheAudio->addAudioEvent(&voiceDefect); + + //make the new recruit the only selected thing, awaiting new command to move, attack, etc... + Drawable *dr = getDrawable(); + if (dr) + { + dr->flashAsSelected(); //This is the first of several flashes which get cue'd by doDefectorUpdateStuff() + AudioEventRTS defectorTimerSound = TheAudio->getMiscAudio()->m_defectorTimerTickSound; + defectorTimerSound.setObjectID( getID() ); + TheAudio->addAudioEvent(&defectorTimerSound); + } + + ContainModuleInterface *ct = getContain(); + if( ct && ct->isKickOutOnCapture() ) + { + // Caves really really don't want to do this. + ct->removeAllContained( TRUE ); + } + + // if it has parking places, defect anything parked there. + for (BehaviorModule** i = getBehaviorModules(); *i; ++i) + { + ParkingPlaceBehaviorInterface* pp = (*i)->getParkingPlaceBehaviorInterface(); + if (pp) + { + pp->defectAllParkedUnits(newTeam, detectionTime); + break; + } + } + + // defect any mines that are owned by this structure, right now. + // unfortunately, structures don't keep list of mines they own, so we must do + // this the hard way :-( [fortunately, this doens't happen very often, so this + // is probably an acceptable, if icky, solution.] (srj) + for (Object* mine = TheGameLogic->getFirstObject(); mine; mine = mine->getNextObject()) + { + if (mine->isKindOf(KINDOF_MINE)) + { + if (mine->getProducerID() == this->getID()) + { + mine->setTeam(newTeam); + } + } + } + +} + +//============================================================================= +// Object::goInvulnerable +//============================================================================= +void Object::goInvulnerable( UnsignedInt time ) +{ + const Bool WITHOUT_DEFECTOR_FX = FALSE; + + + friend_setUndetectedDefector( time > 0 ); + + if (m_defectionHelper) + m_defectionHelper->startDefectionTimer(time, WITHOUT_DEFECTOR_FX); + +} + +// ------------------------------------------------------------------------------------------------ +/** Return the radar priority for this object type */ +// ------------------------------------------------------------------------------------------------ +RadarPriorityType Object::getRadarPriority( void ) const +{ + RadarPriorityType priority = RADAR_PRIORITY_INVALID; + + // first, get the priority at the thing template level + priority = getTemplate()->getDefaultRadarPriority(); + + // + // there are some objects that we want to show up on the radar when they have + // certain properties ... here we will check for those properties unless the INI + // setting of "not on radar" has been manually entered which explicitly forbids an + // object from being on the radar ... by default objects get an "invalid" priority + // on the radar and this means that we are free to decide one here if we want + // + if( priority == RADAR_PRIORITY_INVALID ) + { + + // objects that are "garrisonable" show up on the radar + ContainModuleInterface *cmi = getContain(); + if( cmi && cmi->isGarrisonable() ) + priority = RADAR_PRIORITY_STRUCTURE; + + // objects that are "capturable" show up on the radar + if( isKindOf( KINDOF_CAPTURABLE ) ) + priority = RADAR_PRIORITY_STRUCTURE; + + + } // end if + + // Carbombs will show up as units regardless of their default priority + if ( testStatus( OBJECT_STATUS_IS_CARBOMB ) ) + priority = RADAR_PRIORITY_UNIT; + + + // return the priority we're going to use + return priority; + +} // end getRadarPriority + +// ------------------------------------------------------------------------------------------------ +AIGroup *Object::getGroup(void) +{ + return m_group; +} + +//------------------------------------------------------------------------------------------------- +void Object::enterGroup( AIGroup *group ) +{ +// DEBUG_LOG(("***AIGROUP %x involved in enterGroup on %x\n", group, this)); + // if we are in another group, remove ourselves from it first + leaveGroup(); + + m_group = group; +} + +//------------------------------------------------------------------------------------------------- +void Object::leaveGroup( void ) +{ +// DEBUG_LOG(("***AIGROUP %x involved in leaveGroup on %x\n", m_group, this)); + // if we are in a group, remove ourselves from it + if (m_group) + { + // to avoid recursion, set m_group to NULL before removing + AIGroup *group = m_group; + m_group = NULL; + group->remove( this ); + } +} + +//------------------------------------------------------------------------------------------------- +Real Object::getCarrierDeckHeight() const +{ + Object *producer = TheGameLogic->findObjectByID( getProducerID() ); + if( producer ) + { + // Find a parking place behavior. + for( BehaviorModule** i = producer->getBehaviorModules(); *i; ++i ) + { + ParkingPlaceBehaviorInterface* pp = (*i)->getParkingPlaceBehaviorInterface(); + if( pp ) + { + return pp->getLandingDeckHeightOffset(); + } + } + } + return 0.0f; +} + +//------------------------------------------------------------------------------------------------- +CountermeasuresBehaviorInterface* Object::getCountermeasuresBehaviorInterface() +{ + for( BehaviorModule** i = getBehaviorModules(); *i; ++i ) + { + CountermeasuresBehaviorInterface* cbi = (*i)->getCountermeasuresBehaviorInterface(); + if( cbi ) + { + return cbi; + } + } + return NULL; +} + +//------------------------------------------------------------------------------------------------- +const CountermeasuresBehaviorInterface* Object::getCountermeasuresBehaviorInterface() const +{ + for( BehaviorModule** i = getBehaviorModules(); *i; ++i ) + { + const CountermeasuresBehaviorInterface* cbi = (*i)->getCountermeasuresBehaviorInterface(); + if( cbi ) + { + return cbi; + } + } + return NULL; +} + +//------------------------------------------------------------------------------------------------- +Bool Object::hasCountermeasures() const +{ + const CountermeasuresBehaviorInterface* cbi = getCountermeasuresBehaviorInterface(); + if( cbi && cbi->isActive() ) + { + return TRUE; + } + return FALSE; +} + +//------------------------------------------------------------------------------------------------- +void Object::reportMissileForCountermeasures( Object *missile ) +{ + for( BehaviorModule** i = getBehaviorModules(); *i; ++i ) + { + CountermeasuresBehaviorInterface* cbi = (*i)->getCountermeasuresBehaviorInterface(); + if( cbi ) + { + cbi->reportMissileForCountermeasures( missile ); + } + } +} + +//------------------------------------------------------------------------------------------------- +ObjectID Object::calculateCountermeasureToDivertTo( const Object& victim ) +{ + AIUpdateInterface *ai = getAI(); + if( ai ) + { + for( BehaviorModule** i = victim.getBehaviorModules(); *i; ++i ) + { + CountermeasuresBehaviorInterface* cbi = (*i)->getCountermeasuresBehaviorInterface(); + if( cbi ) + { + ObjectID decoyID = cbi->calculateCountermeasureToDivertTo( victim ); + return decoyID; + } + } + } + return INVALID_ID; +} diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/UpgradeSpecialPower.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/UpgradeSpecialPower.cpp index c8e76cda709..7f839c16def 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/UpgradeSpecialPower.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/SpecialPower/UpgradeSpecialPower.cpp @@ -1,175 +1,175 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: UpgradeSpecialPower.cpp ///////////////////////////////////////////////////////////////// -// Author: Andreas W, July 25 -// Desc: Special Power will grant an upgrade to the object -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -#include "Common/Xfer.h" -#include "Common/Player.h" -#include "Common/Upgrade.h" -#include "GameLogic/Object.h" -#include "GameLogic/Module/UpgradeSpecialPower.h" - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -UpgradeSpecialPowerModuleData::UpgradeSpecialPowerModuleData(void) -{ - m_upgradeName = ""; -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void UpgradeSpecialPowerModuleData::buildFieldParse(MultiIniFieldParse& p) -{ - SpecialPowerModuleData::buildFieldParse(p); - - static const FieldParse dataFieldParse[] = - { - { "UpgradeToGrant", INI::parseAsciiString, NULL, offsetof(UpgradeSpecialPowerModuleData, m_upgradeName) }, - { 0, 0, 0, 0 } - }; - p.add(dataFieldParse); - -} // end buildFieldParse - -/////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -UpgradeSpecialPower::UpgradeSpecialPower(Thing* thing, const ModuleData* moduleData) - : SpecialPowerModule(thing, moduleData) -{ - -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -UpgradeSpecialPower::~UpgradeSpecialPower(void) -{ - -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void UpgradeSpecialPower::grantUpgrade(Object* object) { - - // get module data - const UpgradeSpecialPowerModuleData* modData = getUpgradeSpecialPowerModuleData(); - - const UpgradeTemplate* upgradeTemplate = TheUpgradeCenter->findUpgrade(modData->m_upgradeName); - if (!upgradeTemplate) - { - DEBUG_ASSERTCRASH(0, ("UpgradeSpecialPower for %s can't find upgrade template %s.", getObject()->getName(), modData->m_upgradeName)); - return; - } - - Player* player = object->getControllingPlayer(); - if (upgradeTemplate->getUpgradeType() == UPGRADE_TYPE_PLAYER) - { - // get the player - player->addUpgrade(upgradeTemplate, UPGRADE_STATUS_COMPLETE); - } - else - { - object->giveUpgrade(upgradeTemplate); - } - - player->getAcademyStats()->recordUpgrade(upgradeTemplate, TRUE); -} - - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void UpgradeSpecialPower::doSpecialPower(UnsignedInt commandOptions) -{ - if (getObject()->isDisabled()) - return; - - // call the base class action cause we are *EXTENDING* functionality - SpecialPowerModule::doSpecialPower(commandOptions); - - // Grant the upgrade - grantUpgrade(getObject()); -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void UpgradeSpecialPower::doSpecialPowerAtObject(Object* obj, UnsignedInt commandOptions) -{ - if (getObject()->isDisabled()) - return; - - // call the base class action cause we are *EXTENDING* functionality - SpecialPowerModule::doSpecialPowerAtObject(obj, commandOptions); - - // Grant the upgrade - grantUpgrade(obj); -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void UpgradeSpecialPower::crc(Xfer* xfer) -{ - - // extend base class - SpecialPowerModule::crc(xfer); - -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ - // ------------------------------------------------------------------------------------------------ -void UpgradeSpecialPower::xfer(Xfer* xfer) -{ - - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion(&version, currentVersion); - - // extend base class - SpecialPowerModule::xfer(xfer); - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void UpgradeSpecialPower::loadPostProcess(void) -{ - - // extend base class - SpecialPowerModule::loadPostProcess(); - -} // end loadPostProcess +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: UpgradeSpecialPower.cpp ///////////////////////////////////////////////////////////////// +// Author: Andreas W, July 25 +// Desc: Special Power will grant an upgrade to the object +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#include "Common/Xfer.h" +#include "Common/Player.h" +#include "Common/Upgrade.h" +#include "GameLogic/Object.h" +#include "GameLogic/Module/UpgradeSpecialPower.h" + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +UpgradeSpecialPowerModuleData::UpgradeSpecialPowerModuleData(void) +{ + m_upgradeName = ""; +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPowerModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + SpecialPowerModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + { "UpgradeToGrant", INI::parseAsciiString, NULL, offsetof(UpgradeSpecialPowerModuleData, m_upgradeName) }, + { 0, 0, 0, 0 } + }; + p.add(dataFieldParse); + +} // end buildFieldParse + +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +UpgradeSpecialPower::UpgradeSpecialPower(Thing* thing, const ModuleData* moduleData) + : SpecialPowerModule(thing, moduleData) +{ + +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +UpgradeSpecialPower::~UpgradeSpecialPower(void) +{ + +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPower::grantUpgrade(Object* object) { + + // get module data + const UpgradeSpecialPowerModuleData* modData = getUpgradeSpecialPowerModuleData(); + + const UpgradeTemplate* upgradeTemplate = TheUpgradeCenter->findUpgrade(modData->m_upgradeName); + if (!upgradeTemplate) + { + DEBUG_ASSERTCRASH(0, ("UpgradeSpecialPower for %s can't find upgrade template %s.", getObject()->getName(), modData->m_upgradeName)); + return; + } + + Player* player = object->getControllingPlayer(); + if (upgradeTemplate->getUpgradeType() == UPGRADE_TYPE_PLAYER) + { + // get the player + player->addUpgrade(upgradeTemplate, UPGRADE_STATUS_COMPLETE); + } + else + { + object->giveUpgrade(upgradeTemplate); + } + + player->getAcademyStats()->recordUpgrade(upgradeTemplate, TRUE); +} + + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPower::doSpecialPower(UnsignedInt commandOptions) +{ + if (getObject()->isDisabled()) + return; + + // call the base class action cause we are *EXTENDING* functionality + SpecialPowerModule::doSpecialPower(commandOptions); + + // Grant the upgrade + grantUpgrade(getObject()); +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPower::doSpecialPowerAtObject(Object* obj, UnsignedInt commandOptions) +{ + if (getObject()->isDisabled()) + return; + + // call the base class action cause we are *EXTENDING* functionality + SpecialPowerModule::doSpecialPowerAtObject(obj, commandOptions); + + // Grant the upgrade + grantUpgrade(obj); +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPower::crc(Xfer* xfer) +{ + + // extend base class + SpecialPowerModule::crc(xfer); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ + // ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPower::xfer(Xfer* xfer) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion(&version, currentVersion); + + // extend base class + SpecialPowerModule::xfer(xfer); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void UpgradeSpecialPower::loadPostProcess(void) +{ + + // extend base class + SpecialPowerModule::loadPostProcess(); + +} // end loadPostProcess 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 22cf98b94da..746dca4d41e 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp @@ -1,625 +1,625 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// TeleporterAIUpdate.cpp ////////// -// Will give self random move commands -// Author: Graham Smallwood, April 2002 - -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -#include "Common/GameAudio.h" -#include "Common/RandomValue.h" -#include "GameLogic/Module/TeleporterAIUpdate.h" -#include "GameLogic/Object.h" -#include "Common/Xfer.h" -#include "Common/DisabledTypes.h" -#include "Common/ModelState.h" -#include "GameClient/Drawable.h" -#include "GameClient/FXList.h" -#include "GameLogic/AI.h" -#include "GameLogic/AIGuard.h" -#include "GameLogic/AIPathfind.h" -#include "GameLogic/Module/AIUpdate.h" -#include "GameLogic/Damage.h" -#include "GameLogic/GameLogic.h" -#include "GameLogic/Weapon.h" -#include "GameLogic/PartitionManager.h" -#include "GameLogic/TerrainLogic.h" -#include "GameClient/TintStatus.h" - - -//------------------------------------------------------------------------------------------------- -TeleporterAIUpdateModuleData::TeleporterAIUpdateModuleData( void ) -{ - m_sourceFX = NULL; - m_targetFX = NULL; - m_recoverEndFX = NULL; - m_tintStatus = TINT_STATUS_INVALID; - m_opacityStart = 1.0; - m_opacityEnd = 1.0; -} - -//------------------------------------------------------------------------------------------------- -/*static*/ void TeleporterAIUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) -{ - AIUpdateModuleData::buildFieldParse(p); - - static const FieldParse dataFieldParse[] = - { - { "MinDistanceForTeleport", INI::parseReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_minDistance) }, - { "DisabledDurationPerDistance", INI::parseDurationReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_disabledDuration) }, - { "TeleportStartFX", INI::parseFXList, NULL, offsetof(TeleporterAIUpdateModuleData, m_sourceFX) }, - { "TeleportTargetFX", INI::parseFXList, NULL, offsetof(TeleporterAIUpdateModuleData, m_targetFX) }, - { "TeleportRecoverEndFX", INI::parseFXList, NULL, offsetof(TeleporterAIUpdateModuleData, m_recoverEndFX) }, - { "TeleportRecoverSoundAmbient", INI::parseAudioEventRTS, NULL, offsetof(TeleporterAIUpdateModuleData, m_recoverSoundLoop) }, - { "TeleportRecoverTint", TintStatusFlags::parseSingleBitFromINI, NULL, offsetof(TeleporterAIUpdateModuleData, m_tintStatus) }, - { "TeleportRecoverOpacityStart", INI::parsePercentToReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_opacityStart) }, - { "TeleportRecoverOpacityEnd", INI::parsePercentToReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_opacityEnd) }, - { 0, 0, 0, 0 } - }; - p.add(dataFieldParse); -} - - -//------------------------------------------------------------------------------------------------- -AIStateMachine* TeleporterAIUpdate::makeStateMachine() -{ - return newInstance(AIStateMachine)( getObject(), "TeleporterAIUpdateMachine"); -} - -//------------------------------------------------------------------------------------------------- -TeleporterAIUpdate::TeleporterAIUpdate( Thing *thing, const ModuleData* moduleData ) : AIUpdateInterface( thing, moduleData ) -{ - m_disabledUntil = 0; - m_disabledStart = 0; - m_isDisabled = false; -} - -//------------------------------------------------------------------------------------------------- -TeleporterAIUpdate::~TeleporterAIUpdate( void ) -{ - -} - -//------------------------------------------------------------------------------------------------- -UpdateSleepTime TeleporterAIUpdate::update(void) -{ - const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); - Object* obj = getObject(); - - //UpdateSleepTime ret = UPDATE_SLEEP_FOREVER; - - UnsignedInt now = TheGameLogic->getFrame(); - - if (m_isDisabled) { - if (m_disabledUntil > now) { - // We are currently disabled - Real progress = __max(__min(INT_TO_REAL(now - m_disabledStart) / INT_TO_REAL(m_disabledUntil - m_disabledStart), 1.0), 0.0); - - Drawable* draw = obj->getDrawable(); - if (draw) - { - // - set opacity - if (d->m_opacityStart < 1.0f || d->m_opacityEnd < 1.0f) { - Real opacity = (1.0 - progress) * d->m_opacityStart + progress * d->m_opacityEnd; - // DEBUG_LOG((">>> TPAI Update: opacity = %f\n", curOpacity)); - draw->setDrawableOpacity(opacity); - //draw->setEffectiveOpacity(opacity); - //draw->setSecondMaterialPassOpacity(opacity); - } - } - // We actually need to stop here, because the default update would allow us to attack while disabled - return UPDATE_SLEEP_NONE; - //ret = UPDATE_SLEEP_NONE; - } - else { - // We are done - removeRecoverEffects(); - m_isDisabled = false; - } - } - - // extend - // UpdateSleepTime ret2 = AIUpdateInterface::update(); - // return (ret < ret2) ? ret : ret2; - - return AIUpdateInterface::update(); - -} // end update - - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -void TeleporterAIUpdate::applyRecoverEffects(Real dist) -{ - const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); - Object* obj = getObject(); - - // - set conditionstate - obj->setModelConditionState(MODELCONDITION_TELEPORT_RECOVER); - - // - add ambient sound - m_recoverSoundLoop = d->m_recoverSoundLoop; - m_recoverSoundLoop.setObjectID(obj->getID()); - m_recoverSoundLoop.setPlayingHandle(TheAudio->addAudioEvent(&m_recoverSoundLoop)); - - Drawable* draw = obj->getDrawable(); - if (draw) - { - // - set color tint - if (d->m_tintStatus > TINT_STATUS_INVALID && d->m_tintStatus < TINT_STATUS_COUNT) - { - draw->setTintStatus(d->m_tintStatus); - } - - // - set opacity - if (d->m_opacityStart < 1.0 || d->m_opacityEnd < 1.0) { - //draw->setEffectiveOpacity(1.0); - //draw->setSecondMaterialPassOpacity(1.0); - draw->setDrawableOpacity(1.0); - } - } - -} - -// ------------------------------------------------------------------------------------------------ -void TeleporterAIUpdate::removeRecoverEffects() -{ - const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); - Object* obj = getObject(); - - obj->clearModelConditionState(MODELCONDITION_TELEPORT_RECOVER); - - TheAudio->removeAudioEvent(m_recoverSoundLoop.getPlayingHandle()); - - Drawable* drw = obj->getDrawable(); - if (drw) - { - // - clear color tint - if (d->m_tintStatus > TINT_STATUS_INVALID && d->m_tintStatus < TINT_STATUS_COUNT) - { - drw->clearTintStatus(d->m_tintStatus); - } - } - - FXList::doFXObj(d->m_recoverEndFX, getObject()); -} -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ - -UpdateSleepTime TeleporterAIUpdate::doTeleport(Coord3D targetPos, Real angle, Real dist) -{ - const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); - Object* obj = getObject(); - - FXList::doFXObj(d->m_sourceFX, getObject()); - - obj->setPosition(&targetPos); - obj->setOrientation(angle); - - FXList::doFXObj(d->m_targetFX, getObject()); - - destroyPath(); - - TheAI->pathfinder()->updateGoal(obj, &targetPos, TheTerrainLogic->getLayerForDestination(&targetPos)); - setLocomotorGoalOrientation(angle); - - UnsignedInt disabledFrames = REAL_TO_UNSIGNEDINT(dist * d->m_disabledDuration); - - m_disabledStart = TheGameLogic->getFrame(); - m_disabledUntil = m_disabledStart + disabledFrames; - - m_isDisabled = true; - obj->setDisabledUntil(DISABLED_TELEPORT, m_disabledUntil); - - applyRecoverEffects(dist); - - // return UPDATE_SLEEP(disabledFrames); - return UPDATE_SLEEP_NONE; // We can't actually sleep since we need to adjust some things dynamically - -} - -//------------------------------------------------------------------------------------------------- -Bool TeleporterAIUpdate::isLocationValid(Object* obj, const Coord3D* targetPos, Object* victim, const Coord3D* victimPos, Weapon* weap) -{ - 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); - - return !viewBlocked && inRange && posValid; -} - -//------------------------------------------------------------------------------------------------- -Bool TeleporterAIUpdate::findAttackLocation(Object* victim, const Coord3D* victimPos, Coord3D* targetPos, Real* targetAngle) -{ - Object* obj = getObject(); - Weapon* weap = obj->getCurrentWeapon(); - if (!weap) - return false; - - Coord3D newPos; - newPos.x = targetPos->x; - newPos.y = targetPos->y; - newPos.z = targetPos->z; - - // Check if the current location is valid. - // This needs to be rechecked after the disabled timer. - if (isLocationValid(obj, targetPos, victim, victimPos, weap)) { - if (!TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &newPos)) { - DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination failed!\n")); - } - //else { - // DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); - //} - - if (isLocationValid(obj, &newPos, victim, victimPos, weap)) { - // DEBUG_LOG((">>> TPAI - findAttackLocation: done with initial pos\n")); - *targetPos = newPos; - return true; - } - } - - newPos.x = targetPos->x; - newPos.y = targetPos->y; - newPos.z = targetPos->z; - - - Real RANGE_MARGIN = 10.0f; - - // If the unit's current distance is lower than the attack range, we try to keep this distance - - - Real maxRange = weap->getAttackRange(obj) - RANGE_MARGIN; - Real range = maxRange - weap->getTemplate()->getMinimumAttackRange(); - - - // Calculate direction vector from victim to candidate position - Coord3D dir; - Real distSq = ThePartitionManager->getGoalDistanceSquared(obj, targetPos, victimPos, FROM_CENTER_2D, &dir); - Real dist = sqrt(distSq); - if (dist < maxRange) { - maxRange = dist; - } - - Coord2D direction; - direction.x = -dir.x; - direction.y = -dir.y; - Real initAngle = atan2(direction.y, direction.x); // angle from victim to target - - direction.normalize(); - - const Real maxAngle = deg2rad(180.0f); - const Real step_size_angle = deg2rad(10.0f); - const Real step_size_length = 15.0f; - // const int max_steps = 500; - - const int max_rings = REAL_TO_INT(range / step_size_length); - const int max_steps = REAL_TO_INT(maxAngle / step_size_angle); - // DEBUG_LOG((">>> TPAI - findAttackLocation: range = %f, max_rings = %d\n", range, max_rings)); - for (int ring = 0; ring < max_rings; ++ring) { - - Real radius = maxRange - (ring * step_size_length); - - for (int step = 0; step < max_steps; ++step) { - int sign = (step % 2) ? 1 : -1; - Real angle = initAngle + (step * sign * step_size_angle); - - //polar offset - newPos.x = victimPos->x + radius * cos(angle); - newPos.y = victimPos->y + radius * sin(angle); - newPos.z = TheTerrainLogic->getGroundHeight(newPos.x, newPos.y); - - //viewBlocked = TheAI->pathfinder()->isAttackViewBlockedByObstacle(obj, newPos, victim, *victimPos); - //inRange = weap->isSourceObjectWithGoalPositionWithinAttackRange(obj, &newPos, victim, victimPos); - //destinationLayer = TheTerrainLogic->getLayerForDestination(&newPos); - //posValid = TheAI->pathfinder()->validMovementPosition(getObject()->getCrusherLevel() > 0, destinationLayer, getLocomotorSet(), &newPos); - - // DEBUG_LOG((">>> TPAI - findAttackLocation: candidate Pos: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); - - // TheAI->pathfinder()->adjustTargetDestination(obj, victim, victimPos, weap, &newPos); - if (!TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &newPos)) { - DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination failed!\n")); - } - //else { - // DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); - //} - - /*if (sign == 1) - FXList::doFXPos(debug_fx1, &newPos); - else - FXList::doFXPos(debug_fx2, &newPos);*/ - - if (isLocationValid(obj, &newPos, victim, victimPos, weap)) { - *targetPos = newPos; - *targetAngle = angle + PI; - //DEBUG_LOG((">>> TPAI - findAttackLocation: done after ring=%d, step=%d\n", ring, step)); - - return true; - } - } - } - - DEBUG_LOG((">>> TPAI - findAttackLocation: failed to find attack position\n")); - - return false; -} - -//------------------------------------------------------------------------------------------------- -UpdateSleepTime TeleporterAIUpdate::doLocomotor(void) -{ - if (!isMoving()) { - return AIUpdateInterface::doLocomotor(); - } - - const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); - - Object* obj = getObject(); - - Object* goalObj = getGoalObject(); - const Coord3D* goalPos = getGoalPosition(); - - Real requiredRange = 0; - - Coord3D targetPos; - Coord3D dir; - Real distSq; - - // TODO: Check states - // - (generic) Moving - // - Attacking - // - Guard - // -- GuardAttack - // -- Move to Object - // - Enter - - //Path* path = getPath(); - - // Get TargetPos - - if (goalObj != NULL) { - targetPos = *goalObj->getPosition(); - //goalPos = targetPos; //This should be the same anyways - //DEBUG_LOG((">>> TPAI - doLoc: goalOBJPos (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); - //if (isAttacking()) - distSq = ThePartitionManager->getDistanceSquared(obj, goalObj, FROM_BOUNDINGSPHERE_2D, &dir); - //else - // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - } - else if (goalPos != NULL && !(goalPos->x == 0 && goalPos->y == 0 && goalPos->z == 0)) { - targetPos = *goalPos; - //DEBUG_LOG((">>> TPAI - doLoc: goalPOS (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); - distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - } - else if (getStateMachine()->getCurrentStateID() == AI_GUARD) { - if (isAttacking()) { - AIGuardMachine* guardMachine = getStateMachine()->getGuardMachine(); - if (guardMachine != NULL) { - ObjectID nemID = guardMachine->getNemesisID(); - if (nemID != INVALID_ID) { - Object* nemesis = TheGameLogic->findObjectByID(nemID); - if (nemesis != NULL) { - goalObj = nemesis; - goalPos = goalObj->getPosition(); - targetPos = *goalPos; - - //DEBUG_LOG((">>> TPAI - doLoc: goalPos GUARD NEMESIS (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); - //distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_BOUNDINGSPHERE_2D, &dir); - distSq = ThePartitionManager->getDistanceSquared(obj, goalObj, FROM_BOUNDINGSPHERE_2D, &dir); - } - } - } - } - else if (getGuardLocation() != NULL && !(getGuardLocation()->x == 0 && getGuardLocation()->y == 0 && getGuardLocation()->z == 0)) { // getStateMachine()->isInGuardIdleState() - targetPos = *getGuardLocation(); - //TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &targetPos); - distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - //DEBUG_LOG((">>> TPAI - doLoc: goalPos GUARD (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); - if (getStateMachine()->isInGuardIdleState()) { - requiredRange = 25.0f; // Allow extra range to give some room for large groups guarding - } - } - } - else { - DEBUG_LOG((">>> TPAI - doLoc: GOAL POS AND OBJ ARE NULL??\n")); - return UPDATE_SLEEP_FOREVER; - } - - if (getStateMachine()->getCurrentStateID() == AI_ENTER) { - // If we want to enter and got this close, we just move normally - requiredRange = 15.0f; - //} else if (getStateMachine()->getCurrentStateID() == AI_DOCK) { - // // Get the dock's approach position. - // // If we are at least X distance away, teleport, otherwise, do normal movement - // DockUpdateInterface* dock = goalObj->getDockUpdateInterface(); - // if (dock != NULL) { - // int dockIndex; // we don't really need this - // Bool reserved = dock->reserveApproachPosition(obj, &targetPos, &dockIndex); - // if (reserved) { - // // Get dist to goal obj center - // Real distSqObj = ThePartitionManager->getDistanceSquared(obj, goalObj, FROM_CENTER_2D, &dir); - // // Get dist to approach pos - // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - - // DEBUG_LOG((">>> TPAI: DOCK distSq = %f, distSqObj = %f\n", distSq, distSqObj)); - - // // If we are close to both the approach pos and the center pos, move normally - // Real minDistSq = 25.0f * 25.0f; - // if (distSqObj < minDistSq && distSqObj < minDistSq) { - // return AIUpdateInterface::doLocomotor(); - // } - // // otherwise teleport - // } - // } - } - - DEBUG_LOG((">>> TPAI - doLoc: LocomotorGoalType = %d, AI STATE = %s (%d)\n", getLocomotorGoalType(), getStateMachine()->getCurrentStateName(), getStateMachine()->getCurrentStateID())); - - Real RANGE_MARGIN = 5.0f; // We calculate distance this much shorter than weapon range - Real TELEPORT_DIST_MARGIN = 5.0f; // We teleport this much closer than needed - - // Get initial dist and dir - // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - Real dist = sqrt(distSq); - Real targetAngle = atan2(dir.y, dir.x); - dir.normalize(); - - // We are within min range - if (dist <= d->m_minDistance || dist <= requiredRange) { - return AIUpdateInterface::doLocomotor(); - } - - //When we attack, we attempt to teleport into range - if (isAttacking()) { - // requiredRange = obj->getLargestWeaponRange(); - Weapon* weap = obj->getCurrentWeapon(); - if (!weap) - return AIUpdateInterface::doLocomotor(); - - // Check if current position is valid for attack - if (isLocationValid(obj, obj->getPosition(), goalObj, goalPos, weap)) { - return AIUpdateInterface::doLocomotor(); - } - - requiredRange = weap->getAttackRange(obj) - RANGE_MARGIN; - - //Adjust target to required distance - if (requiredRange > 0) { - dir.scale(min(dist, requiredRange - TELEPORT_DIST_MARGIN)); - targetPos.sub(&dir); - targetPos.z = TheTerrainLogic->getGroundHeight(targetPos.x, targetPos.y); - } - - // Find proper attack position for adjusted target - if (!findAttackLocation(goalObj, goalPos, &targetPos, &targetAngle)) { - DEBUG_LOG((">>> TPAI - doLoc: isAttacking. FAILED TO FIND VALID LOCATION!\n")); - - // This might happen if we try to attack e.g. a boat in water - // TODO: Should we move as close as we can? - - return AIUpdateInterface::doLocomotor(); - } - //DEBUG_LOG((">>> TPAI - doLoc: findAttackLocation targetPos (AFTER) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); - - //recompute distance and angle - distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - ////targetAngle = atan2(dir.y, dir.x); - dist = sqrt(distSq); - - //DEBUG_LOG((">>> TPAI - doLoc: isAttacking, dist = %f, reqRange = %f\n", dist, requiredRange)); - //m_inAttackPos = TRUE; - } - //else if( /*use special power?*/) { - // //same as with attacks, try to get into range - //} - // else if (getStateMachine()->getCurrentStateID() == AI_ENTER || getStateMachine()->getCurrentStateID() == AI_ENTER) { - else if (goalObj != NULL) { - // We need to correct the position to the outer bounding box of the structure - // TODO: Respect actual geometry, not just radius - requiredRange = goalObj->getGeometryInfo().getBoundingCircleRadius(); - if (requiredRange > 0) { - dir.scale(min(dist, requiredRange)); - targetPos.sub(&dir); - targetPos.z = TheTerrainLogic->getGroundHeight(targetPos.x, targetPos.y); - } - TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &targetPos); - - //recompute distance and angle - distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - - // targetAngle = atan2(dir.y, dir.x); - targetAngle = atan2(goalPos->y - targetPos.y, goalPos->x - targetPos.x); - dist = sqrt(distSq); - } - else { - // TODO: if this doesn't find a location, - TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &targetPos); - - //recompute distance and angle - distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); - targetAngle = atan2(dir.y, dir.x); - dist = sqrt(distSq); - } - - // DEBUG_LOG((">>> TPAI - doLoc: teleport with dist = %f\n", dist)); - doTeleport(targetPos, targetAngle, dist); - - return AIUpdateInterface::doLocomotor(); - -} - -//------------------------------------------------------------------------------------------------- -/** - * See if we can do a quick path without pathfinding. - */ -Bool TeleporterAIUpdate::canComputeQuickPath(void) -{ - return true; -} - -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -Bool TeleporterAIUpdate::computeQuickPath(const Coord3D* destination) -{ - return AIUpdateInterface::computeQuickPath(destination); -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void TeleporterAIUpdate::crc( Xfer *xfer ) -{ - // extend base class - AIUpdateInterface::crc(xfer); -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void TeleporterAIUpdate::xfer( Xfer *xfer ) -{ - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // extend base class - AIUpdateInterface::xfer(xfer); - - xfer->xferBool(&m_isDisabled); - - xfer->xferUnsignedInt(&m_disabledUntil); - xfer->xferUnsignedInt(&m_disabledStart); - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void TeleporterAIUpdate::loadPostProcess( void ) -{ - // extend base class - AIUpdateInterface::loadPostProcess(); -} // end loadPostProcess +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// TeleporterAIUpdate.cpp ////////// +// Will give self random move commands +// Author: Graham Smallwood, April 2002 + +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#include "Common/GameAudio.h" +#include "Common/RandomValue.h" +#include "GameLogic/Module/TeleporterAIUpdate.h" +#include "GameLogic/Object.h" +#include "Common/Xfer.h" +#include "Common/DisabledTypes.h" +#include "Common/ModelState.h" +#include "GameClient/Drawable.h" +#include "GameClient/FXList.h" +#include "GameLogic/AI.h" +#include "GameLogic/AIGuard.h" +#include "GameLogic/AIPathfind.h" +#include "GameLogic/Module/AIUpdate.h" +#include "GameLogic/Damage.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/Weapon.h" +#include "GameLogic/PartitionManager.h" +#include "GameLogic/TerrainLogic.h" +#include "GameClient/TintStatus.h" + + +//------------------------------------------------------------------------------------------------- +TeleporterAIUpdateModuleData::TeleporterAIUpdateModuleData( void ) +{ + m_sourceFX = NULL; + m_targetFX = NULL; + m_recoverEndFX = NULL; + m_tintStatus = TINT_STATUS_INVALID; + m_opacityStart = 1.0; + m_opacityEnd = 1.0; +} + +//------------------------------------------------------------------------------------------------- +/*static*/ void TeleporterAIUpdateModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + AIUpdateModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + { "MinDistanceForTeleport", INI::parseReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_minDistance) }, + { "DisabledDurationPerDistance", INI::parseDurationReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_disabledDuration) }, + { "TeleportStartFX", INI::parseFXList, NULL, offsetof(TeleporterAIUpdateModuleData, m_sourceFX) }, + { "TeleportTargetFX", INI::parseFXList, NULL, offsetof(TeleporterAIUpdateModuleData, m_targetFX) }, + { "TeleportRecoverEndFX", INI::parseFXList, NULL, offsetof(TeleporterAIUpdateModuleData, m_recoverEndFX) }, + { "TeleportRecoverSoundAmbient", INI::parseAudioEventRTS, NULL, offsetof(TeleporterAIUpdateModuleData, m_recoverSoundLoop) }, + { "TeleportRecoverTint", TintStatusFlags::parseSingleBitFromINI, NULL, offsetof(TeleporterAIUpdateModuleData, m_tintStatus) }, + { "TeleportRecoverOpacityStart", INI::parsePercentToReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_opacityStart) }, + { "TeleportRecoverOpacityEnd", INI::parsePercentToReal, NULL, offsetof(TeleporterAIUpdateModuleData, m_opacityEnd) }, + { 0, 0, 0, 0 } + }; + p.add(dataFieldParse); +} + + +//------------------------------------------------------------------------------------------------- +AIStateMachine* TeleporterAIUpdate::makeStateMachine() +{ + return newInstance(AIStateMachine)( getObject(), "TeleporterAIUpdateMachine"); +} + +//------------------------------------------------------------------------------------------------- +TeleporterAIUpdate::TeleporterAIUpdate( Thing *thing, const ModuleData* moduleData ) : AIUpdateInterface( thing, moduleData ) +{ + m_disabledUntil = 0; + m_disabledStart = 0; + m_isDisabled = false; +} + +//------------------------------------------------------------------------------------------------- +TeleporterAIUpdate::~TeleporterAIUpdate( void ) +{ + +} + +//------------------------------------------------------------------------------------------------- +UpdateSleepTime TeleporterAIUpdate::update(void) +{ + const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); + Object* obj = getObject(); + + //UpdateSleepTime ret = UPDATE_SLEEP_FOREVER; + + UnsignedInt now = TheGameLogic->getFrame(); + + if (m_isDisabled) { + if (m_disabledUntil > now) { + // We are currently disabled + Real progress = __max(__min(INT_TO_REAL(now - m_disabledStart) / INT_TO_REAL(m_disabledUntil - m_disabledStart), 1.0), 0.0); + + Drawable* draw = obj->getDrawable(); + if (draw) + { + // - set opacity + if (d->m_opacityStart < 1.0f || d->m_opacityEnd < 1.0f) { + Real opacity = (1.0 - progress) * d->m_opacityStart + progress * d->m_opacityEnd; + // DEBUG_LOG((">>> TPAI Update: opacity = %f\n", curOpacity)); + draw->setDrawableOpacity(opacity); + //draw->setEffectiveOpacity(opacity); + //draw->setSecondMaterialPassOpacity(opacity); + } + } + // We actually need to stop here, because the default update would allow us to attack while disabled + return UPDATE_SLEEP_NONE; + //ret = UPDATE_SLEEP_NONE; + } + else { + // We are done + removeRecoverEffects(); + m_isDisabled = false; + } + } + + // extend + // UpdateSleepTime ret2 = AIUpdateInterface::update(); + // return (ret < ret2) ? ret : ret2; + + return AIUpdateInterface::update(); + +} // end update + + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +void TeleporterAIUpdate::applyRecoverEffects(Real dist) +{ + const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); + Object* obj = getObject(); + + // - set conditionstate + obj->setModelConditionState(MODELCONDITION_TELEPORT_RECOVER); + + // - add ambient sound + m_recoverSoundLoop = d->m_recoverSoundLoop; + m_recoverSoundLoop.setObjectID(obj->getID()); + m_recoverSoundLoop.setPlayingHandle(TheAudio->addAudioEvent(&m_recoverSoundLoop)); + + Drawable* draw = obj->getDrawable(); + if (draw) + { + // - set color tint + if (d->m_tintStatus > TINT_STATUS_INVALID && d->m_tintStatus < TINT_STATUS_COUNT) + { + draw->setTintStatus(d->m_tintStatus); + } + + // - set opacity + if (d->m_opacityStart < 1.0 || d->m_opacityEnd < 1.0) { + //draw->setEffectiveOpacity(1.0); + //draw->setSecondMaterialPassOpacity(1.0); + draw->setDrawableOpacity(1.0); + } + } + +} + +// ------------------------------------------------------------------------------------------------ +void TeleporterAIUpdate::removeRecoverEffects() +{ + const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); + Object* obj = getObject(); + + obj->clearModelConditionState(MODELCONDITION_TELEPORT_RECOVER); + + TheAudio->removeAudioEvent(m_recoverSoundLoop.getPlayingHandle()); + + Drawable* drw = obj->getDrawable(); + if (drw) + { + // - clear color tint + if (d->m_tintStatus > TINT_STATUS_INVALID && d->m_tintStatus < TINT_STATUS_COUNT) + { + drw->clearTintStatus(d->m_tintStatus); + } + } + + FXList::doFXObj(d->m_recoverEndFX, getObject()); +} +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ + +UpdateSleepTime TeleporterAIUpdate::doTeleport(Coord3D targetPos, Real angle, Real dist) +{ + const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); + Object* obj = getObject(); + + FXList::doFXObj(d->m_sourceFX, getObject()); + + obj->setPosition(&targetPos); + obj->setOrientation(angle); + + FXList::doFXObj(d->m_targetFX, getObject()); + + destroyPath(); + + TheAI->pathfinder()->updateGoal(obj, &targetPos, TheTerrainLogic->getLayerForDestination(&targetPos)); + setLocomotorGoalOrientation(angle); + + UnsignedInt disabledFrames = REAL_TO_UNSIGNEDINT(dist * d->m_disabledDuration); + + m_disabledStart = TheGameLogic->getFrame(); + m_disabledUntil = m_disabledStart + disabledFrames; + + m_isDisabled = true; + obj->setDisabledUntil(DISABLED_TELEPORT, m_disabledUntil); + + applyRecoverEffects(dist); + + // return UPDATE_SLEEP(disabledFrames); + return UPDATE_SLEEP_NONE; // We can't actually sleep since we need to adjust some things dynamically + +} + +//------------------------------------------------------------------------------------------------- +Bool TeleporterAIUpdate::isLocationValid(Object* obj, const Coord3D* targetPos, Object* victim, const Coord3D* victimPos, Weapon* weap) +{ + 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); + + return !viewBlocked && inRange && posValid; +} + +//------------------------------------------------------------------------------------------------- +Bool TeleporterAIUpdate::findAttackLocation(Object* victim, const Coord3D* victimPos, Coord3D* targetPos, Real* targetAngle) +{ + Object* obj = getObject(); + Weapon* weap = obj->getCurrentWeapon(); + if (!weap) + return false; + + Coord3D newPos; + newPos.x = targetPos->x; + newPos.y = targetPos->y; + newPos.z = targetPos->z; + + // Check if the current location is valid. + // This needs to be rechecked after the disabled timer. + if (isLocationValid(obj, targetPos, victim, victimPos, weap)) { + if (!TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &newPos)) { + DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination failed!\n")); + } + //else { + // DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); + //} + + if (isLocationValid(obj, &newPos, victim, victimPos, weap)) { + // DEBUG_LOG((">>> TPAI - findAttackLocation: done with initial pos\n")); + *targetPos = newPos; + return true; + } + } + + newPos.x = targetPos->x; + newPos.y = targetPos->y; + newPos.z = targetPos->z; + + + Real RANGE_MARGIN = 10.0f; + + // If the unit's current distance is lower than the attack range, we try to keep this distance + + + Real maxRange = weap->getAttackRange(obj) - RANGE_MARGIN; + Real range = maxRange - weap->getTemplate()->getMinimumAttackRange(); + + + // Calculate direction vector from victim to candidate position + Coord3D dir; + Real distSq = ThePartitionManager->getGoalDistanceSquared(obj, targetPos, victimPos, FROM_CENTER_2D, &dir); + Real dist = sqrt(distSq); + if (dist < maxRange) { + maxRange = dist; + } + + Coord2D direction; + direction.x = -dir.x; + direction.y = -dir.y; + Real initAngle = atan2(direction.y, direction.x); // angle from victim to target + + direction.normalize(); + + const Real maxAngle = deg2rad(180.0f); + const Real step_size_angle = deg2rad(10.0f); + const Real step_size_length = 15.0f; + // const int max_steps = 500; + + const int max_rings = REAL_TO_INT(range / step_size_length); + const int max_steps = REAL_TO_INT(maxAngle / step_size_angle); + // DEBUG_LOG((">>> TPAI - findAttackLocation: range = %f, max_rings = %d\n", range, max_rings)); + for (int ring = 0; ring < max_rings; ++ring) { + + Real radius = maxRange - (ring * step_size_length); + + for (int step = 0; step < max_steps; ++step) { + int sign = (step % 2) ? 1 : -1; + Real angle = initAngle + (step * sign * step_size_angle); + + //polar offset + newPos.x = victimPos->x + radius * cos(angle); + newPos.y = victimPos->y + radius * sin(angle); + newPos.z = TheTerrainLogic->getGroundHeight(newPos.x, newPos.y); + + //viewBlocked = TheAI->pathfinder()->isAttackViewBlockedByObstacle(obj, newPos, victim, *victimPos); + //inRange = weap->isSourceObjectWithGoalPositionWithinAttackRange(obj, &newPos, victim, victimPos); + //destinationLayer = TheTerrainLogic->getLayerForDestination(&newPos); + //posValid = TheAI->pathfinder()->validMovementPosition(getObject()->getCrusherLevel() > 0, destinationLayer, getLocomotorSet(), &newPos); + + // DEBUG_LOG((">>> TPAI - findAttackLocation: candidate Pos: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); + + // TheAI->pathfinder()->adjustTargetDestination(obj, victim, victimPos, weap, &newPos); + if (!TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &newPos)) { + DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination failed!\n")); + } + //else { + // DEBUG_LOG((">>> TPAI - findAttackLocation: AdjustDestination: %f, %f, %f\n", newPos.x, newPos.y, newPos.z)); + //} + + /*if (sign == 1) + FXList::doFXPos(debug_fx1, &newPos); + else + FXList::doFXPos(debug_fx2, &newPos);*/ + + if (isLocationValid(obj, &newPos, victim, victimPos, weap)) { + *targetPos = newPos; + *targetAngle = angle + PI; + //DEBUG_LOG((">>> TPAI - findAttackLocation: done after ring=%d, step=%d\n", ring, step)); + + return true; + } + } + } + + DEBUG_LOG((">>> TPAI - findAttackLocation: failed to find attack position\n")); + + return false; +} + +//------------------------------------------------------------------------------------------------- +UpdateSleepTime TeleporterAIUpdate::doLocomotor(void) +{ + if (!isMoving()) { + return AIUpdateInterface::doLocomotor(); + } + + const TeleporterAIUpdateModuleData* d = getTeleporterAIUpdateModuleData(); + + Object* obj = getObject(); + + Object* goalObj = getGoalObject(); + const Coord3D* goalPos = getGoalPosition(); + + Real requiredRange = 0; + + Coord3D targetPos; + Coord3D dir; + Real distSq; + + // TODO: Check states + // - (generic) Moving + // - Attacking + // - Guard + // -- GuardAttack + // -- Move to Object + // - Enter + + //Path* path = getPath(); + + // Get TargetPos + + if (goalObj != NULL) { + targetPos = *goalObj->getPosition(); + //goalPos = targetPos; //This should be the same anyways + //DEBUG_LOG((">>> TPAI - doLoc: goalOBJPos (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + //if (isAttacking()) + distSq = ThePartitionManager->getDistanceSquared(obj, goalObj, FROM_BOUNDINGSPHERE_2D, &dir); + //else + // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + } + else if (goalPos != NULL && !(goalPos->x == 0 && goalPos->y == 0 && goalPos->z == 0)) { + targetPos = *goalPos; + //DEBUG_LOG((">>> TPAI - doLoc: goalPOS (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + } + else if (getStateMachine()->getCurrentStateID() == AI_GUARD) { + if (isAttacking()) { + AIGuardMachine* guardMachine = getStateMachine()->getGuardMachine(); + if (guardMachine != NULL) { + ObjectID nemID = guardMachine->getNemesisID(); + if (nemID != INVALID_ID) { + Object* nemesis = TheGameLogic->findObjectByID(nemID); + if (nemesis != NULL) { + goalObj = nemesis; + goalPos = goalObj->getPosition(); + targetPos = *goalPos; + + //DEBUG_LOG((">>> TPAI - doLoc: goalPos GUARD NEMESIS (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + //distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_BOUNDINGSPHERE_2D, &dir); + distSq = ThePartitionManager->getDistanceSquared(obj, goalObj, FROM_BOUNDINGSPHERE_2D, &dir); + } + } + } + } + else if (getGuardLocation() != NULL && !(getGuardLocation()->x == 0 && getGuardLocation()->y == 0 && getGuardLocation()->z == 0)) { // getStateMachine()->isInGuardIdleState() + targetPos = *getGuardLocation(); + //TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &targetPos); + distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + //DEBUG_LOG((">>> TPAI - doLoc: goalPos GUARD (0) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + if (getStateMachine()->isInGuardIdleState()) { + requiredRange = 25.0f; // Allow extra range to give some room for large groups guarding + } + } + } + else { + DEBUG_LOG((">>> TPAI - doLoc: GOAL POS AND OBJ ARE NULL??\n")); + return UPDATE_SLEEP_FOREVER; + } + + if (getStateMachine()->getCurrentStateID() == AI_ENTER) { + // If we want to enter and got this close, we just move normally + requiredRange = 15.0f; + //} else if (getStateMachine()->getCurrentStateID() == AI_DOCK) { + // // Get the dock's approach position. + // // If we are at least X distance away, teleport, otherwise, do normal movement + // DockUpdateInterface* dock = goalObj->getDockUpdateInterface(); + // if (dock != NULL) { + // int dockIndex; // we don't really need this + // Bool reserved = dock->reserveApproachPosition(obj, &targetPos, &dockIndex); + // if (reserved) { + // // Get dist to goal obj center + // Real distSqObj = ThePartitionManager->getDistanceSquared(obj, goalObj, FROM_CENTER_2D, &dir); + // // Get dist to approach pos + // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + + // DEBUG_LOG((">>> TPAI: DOCK distSq = %f, distSqObj = %f\n", distSq, distSqObj)); + + // // If we are close to both the approach pos and the center pos, move normally + // Real minDistSq = 25.0f * 25.0f; + // if (distSqObj < minDistSq && distSqObj < minDistSq) { + // return AIUpdateInterface::doLocomotor(); + // } + // // otherwise teleport + // } + // } + } + + DEBUG_LOG((">>> TPAI - doLoc: LocomotorGoalType = %d, AI STATE = %s (%d)\n", getLocomotorGoalType(), getStateMachine()->getCurrentStateName(), getStateMachine()->getCurrentStateID())); + + Real RANGE_MARGIN = 5.0f; // We calculate distance this much shorter than weapon range + Real TELEPORT_DIST_MARGIN = 5.0f; // We teleport this much closer than needed + + // Get initial dist and dir + // distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + Real dist = sqrt(distSq); + Real targetAngle = atan2(dir.y, dir.x); + dir.normalize(); + + // We are within min range + if (dist <= d->m_minDistance || dist <= requiredRange) { + return AIUpdateInterface::doLocomotor(); + } + + //When we attack, we attempt to teleport into range + if (isAttacking()) { + // requiredRange = obj->getLargestWeaponRange(); + Weapon* weap = obj->getCurrentWeapon(); + if (!weap) + return AIUpdateInterface::doLocomotor(); + + // Check if current position is valid for attack + if (isLocationValid(obj, obj->getPosition(), goalObj, goalPos, weap)) { + return AIUpdateInterface::doLocomotor(); + } + + requiredRange = weap->getAttackRange(obj) - RANGE_MARGIN; + + //Adjust target to required distance + if (requiredRange > 0) { + dir.scale(min(dist, requiredRange - TELEPORT_DIST_MARGIN)); + targetPos.sub(&dir); + targetPos.z = TheTerrainLogic->getGroundHeight(targetPos.x, targetPos.y); + } + + // Find proper attack position for adjusted target + if (!findAttackLocation(goalObj, goalPos, &targetPos, &targetAngle)) { + DEBUG_LOG((">>> TPAI - doLoc: isAttacking. FAILED TO FIND VALID LOCATION!\n")); + + // This might happen if we try to attack e.g. a boat in water + // TODO: Should we move as close as we can? + + return AIUpdateInterface::doLocomotor(); + } + //DEBUG_LOG((">>> TPAI - doLoc: findAttackLocation targetPos (AFTER) = %f, %f, %f\n", targetPos.x, targetPos.y, targetPos.z)); + + //recompute distance and angle + distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + ////targetAngle = atan2(dir.y, dir.x); + dist = sqrt(distSq); + + //DEBUG_LOG((">>> TPAI - doLoc: isAttacking, dist = %f, reqRange = %f\n", dist, requiredRange)); + //m_inAttackPos = TRUE; + } + //else if( /*use special power?*/) { + // //same as with attacks, try to get into range + //} + // else if (getStateMachine()->getCurrentStateID() == AI_ENTER || getStateMachine()->getCurrentStateID() == AI_ENTER) { + else if (goalObj != NULL) { + // We need to correct the position to the outer bounding box of the structure + // TODO: Respect actual geometry, not just radius + requiredRange = goalObj->getGeometryInfo().getBoundingCircleRadius(); + if (requiredRange > 0) { + dir.scale(min(dist, requiredRange)); + targetPos.sub(&dir); + targetPos.z = TheTerrainLogic->getGroundHeight(targetPos.x, targetPos.y); + } + TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &targetPos); + + //recompute distance and angle + distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + + // targetAngle = atan2(dir.y, dir.x); + targetAngle = atan2(goalPos->y - targetPos.y, goalPos->x - targetPos.x); + dist = sqrt(distSq); + } + else { + // TODO: if this doesn't find a location, + TheAI->pathfinder()->adjustDestination(obj, getLocomotorSet(), &targetPos); + + //recompute distance and angle + distSq = ThePartitionManager->getDistanceSquared(obj, &targetPos, FROM_CENTER_2D, &dir); + targetAngle = atan2(dir.y, dir.x); + dist = sqrt(distSq); + } + + // DEBUG_LOG((">>> TPAI - doLoc: teleport with dist = %f\n", dist)); + doTeleport(targetPos, targetAngle, dist); + + return AIUpdateInterface::doLocomotor(); + +} + +//------------------------------------------------------------------------------------------------- +/** + * See if we can do a quick path without pathfinding. + */ +Bool TeleporterAIUpdate::canComputeQuickPath(void) +{ + return true; +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +Bool TeleporterAIUpdate::computeQuickPath(const Coord3D* destination) +{ + return AIUpdateInterface::computeQuickPath(destination); +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void TeleporterAIUpdate::crc( Xfer *xfer ) +{ + // extend base class + AIUpdateInterface::crc(xfer); +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void TeleporterAIUpdate::xfer( Xfer *xfer ) +{ + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // extend base class + AIUpdateInterface::xfer(xfer); + + xfer->xferBool(&m_isDisabled); + + xfer->xferUnsignedInt(&m_disabledUntil); + xfer->xferUnsignedInt(&m_disabledStart); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void TeleporterAIUpdate::loadPostProcess( void ) +{ + // extend base class + AIUpdateInterface::loadPostProcess(); +} // end loadPostProcess diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp index aa5c036c8b6..7b3fbd13a56 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.cpp @@ -1,198 +1,198 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: RadiusDecalBehavior.cpp /////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -#include "Common/RandomValue.h" -#include "Common/Xfer.h" -#include "GameLogic/GameLogic.h" -#include "GameLogic/Module/RadiusDecalBehavior.h" -#include "GameLogic/Object.h" - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- - -RadiusDecalBehaviorModuleData::RadiusDecalBehaviorModuleData() -{ - m_initiallyActive = false; - m_decalRadius = 0.0f; -} -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -/*static*/ void RadiusDecalBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p) -{ - UpdateModuleData::buildFieldParse(p); - static const FieldParse dataFieldParse[] = - { - { "StartsActive", INI::parseBool, NULL, offsetof(RadiusDecalBehaviorModuleData, m_initiallyActive) }, - { "RadiusDecal", RadiusDecalTemplate::parseRadiusDecalTemplate, NULL, offsetof( RadiusDecalBehaviorModuleData, m_decalTemplate) }, - { "Radius", INI::parseReal, NULL, offsetof( RadiusDecalBehaviorModuleData, m_decalRadius) }, - { 0, 0, 0, 0 } - }; - - BehaviorModuleData::buildFieldParse(p); - p.add(dataFieldParse); - p.add(UpgradeMuxData::getFieldParse(), offsetof(RadiusDecalBehaviorModuleData, m_upgradeMuxData)); -} - - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -RadiusDecalBehavior::RadiusDecalBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData ) -{ - if (getRadiusDecalBehaviorModuleData()->m_initiallyActive) - { - giveSelfUpgrade(); - } - else { - clearDecal(); - setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -RadiusDecalBehavior::~RadiusDecalBehavior( void ) -{ - clearDecal(); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void RadiusDecalBehavior::createRadiusDecal( void ) -{ - const RadiusDecalBehaviorModuleData* data = getRadiusDecalBehaviorModuleData(); - const RadiusDecalTemplate& tmpl = data->m_decalTemplate; - m_radiusDecal.clear(); - if (tmpl.valid()) { - // DEBUG_LOG(("RadiusDecalBehavior::createRadiusDecal: \n")); - // tmpl.debugPrint(); - tmpl.createRadiusDecal(*(getObject()->getPosition()), data->m_decalRadius, getObject()->getControllingPlayer(), m_radiusDecal); - setWakeFrame(getObject(), m_radiusDecal.isEmpty() ? UPDATE_SLEEP_FOREVER : UPDATE_SLEEP_NONE); - } - else { - // We don't have a decal defined. Do we need this? - setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); - } -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void RadiusDecalBehavior::killRadiusDecal() -{ - clearDecal(); - setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); -} - -// ----------------------------------------------------------------------------------------------- -void RadiusDecalBehavior::clearDecal() -{ - m_radiusDecal.clear(); -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -UpdateSleepTime RadiusDecalBehavior::update( void ) -{ - if (getObject()->isDisabledByType(DISABLED_HELD)) { - if (!m_radiusDecal.isEmpty()) - clearDecal(); - return UPDATE_SLEEP_NONE; // We wait to be re-enabled - } - - // Upgrade has not been triggered, or it might have been removed. - if (!isUpgradeActive()) { - clearDecal(); - return UPDATE_SLEEP_FOREVER; - } - - // The object is dead - if (getObject()->isEffectivelyDead()) { - clearDecal(); - return UPDATE_SLEEP_FOREVER; - } - - // This should be our usual case - if (!m_radiusDecal.isEmpty()) { - m_radiusDecal.update(); - m_radiusDecal.setPosition(*(getObject()->getPosition())); - return UPDATE_SLEEP_NONE; - } - - // We get here if we were disabled - createRadiusDecal(); - return UPDATE_SLEEP_NONE; - - // Something probably went wrong if we reach this point - //return UPDATE_SLEEP_FOREVER; -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void RadiusDecalBehavior::crc( Xfer *xfer ) -{ - - // extend base class - UpdateModule::crc( xfer ); - -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void RadiusDecalBehavior::xfer( Xfer *xfer ) -{ - - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // extend base class - UpdateModule::xfer( xfer ); - - // decal, if any - m_radiusDecal.xferRadiusDecal(xfer); - - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void RadiusDecalBehavior::loadPostProcess( void ) -{ - - // extend base class - UpdateModule::loadPostProcess(); - -} // end loadPostProcess +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: RadiusDecalBehavior.cpp /////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#include "Common/RandomValue.h" +#include "Common/Xfer.h" +#include "GameLogic/GameLogic.h" +#include "GameLogic/Module/RadiusDecalBehavior.h" +#include "GameLogic/Object.h" + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +RadiusDecalBehaviorModuleData::RadiusDecalBehaviorModuleData() +{ + m_initiallyActive = false; + m_decalRadius = 0.0f; +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +/*static*/ void RadiusDecalBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + UpdateModuleData::buildFieldParse(p); + static const FieldParse dataFieldParse[] = + { + { "StartsActive", INI::parseBool, NULL, offsetof(RadiusDecalBehaviorModuleData, m_initiallyActive) }, + { "RadiusDecal", RadiusDecalTemplate::parseRadiusDecalTemplate, NULL, offsetof( RadiusDecalBehaviorModuleData, m_decalTemplate) }, + { "Radius", INI::parseReal, NULL, offsetof( RadiusDecalBehaviorModuleData, m_decalRadius) }, + { 0, 0, 0, 0 } + }; + + BehaviorModuleData::buildFieldParse(p); + p.add(dataFieldParse); + p.add(UpgradeMuxData::getFieldParse(), offsetof(RadiusDecalBehaviorModuleData, m_upgradeMuxData)); +} + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +RadiusDecalBehavior::RadiusDecalBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData ) +{ + if (getRadiusDecalBehaviorModuleData()->m_initiallyActive) + { + giveSelfUpgrade(); + } + else { + clearDecal(); + setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +RadiusDecalBehavior::~RadiusDecalBehavior( void ) +{ + clearDecal(); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void RadiusDecalBehavior::createRadiusDecal( void ) +{ + const RadiusDecalBehaviorModuleData* data = getRadiusDecalBehaviorModuleData(); + const RadiusDecalTemplate& tmpl = data->m_decalTemplate; + m_radiusDecal.clear(); + if (tmpl.valid()) { + // DEBUG_LOG(("RadiusDecalBehavior::createRadiusDecal: \n")); + // tmpl.debugPrint(); + tmpl.createRadiusDecal(*(getObject()->getPosition()), data->m_decalRadius, getObject()->getControllingPlayer(), m_radiusDecal); + setWakeFrame(getObject(), m_radiusDecal.isEmpty() ? UPDATE_SLEEP_FOREVER : UPDATE_SLEEP_NONE); + } + else { + // We don't have a decal defined. Do we need this? + setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); + } +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void RadiusDecalBehavior::killRadiusDecal() +{ + clearDecal(); + setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER); +} + +// ----------------------------------------------------------------------------------------------- +void RadiusDecalBehavior::clearDecal() +{ + m_radiusDecal.clear(); +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +UpdateSleepTime RadiusDecalBehavior::update( void ) +{ + if (getObject()->isDisabledByType(DISABLED_HELD)) { + if (!m_radiusDecal.isEmpty()) + clearDecal(); + return UPDATE_SLEEP_NONE; // We wait to be re-enabled + } + + // Upgrade has not been triggered, or it might have been removed. + if (!isUpgradeActive()) { + clearDecal(); + return UPDATE_SLEEP_FOREVER; + } + + // The object is dead + if (getObject()->isEffectivelyDead()) { + clearDecal(); + return UPDATE_SLEEP_FOREVER; + } + + // This should be our usual case + if (!m_radiusDecal.isEmpty()) { + m_radiusDecal.update(); + m_radiusDecal.setPosition(*(getObject()->getPosition())); + return UPDATE_SLEEP_NONE; + } + + // We get here if we were disabled + createRadiusDecal(); + return UPDATE_SLEEP_NONE; + + // Something probably went wrong if we reach this point + //return UPDATE_SLEEP_FOREVER; +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void RadiusDecalBehavior::crc( Xfer *xfer ) +{ + + // extend base class + UpdateModule::crc( xfer ); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void RadiusDecalBehavior::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // extend base class + UpdateModule::xfer( xfer ); + + // decal, if any + m_radiusDecal.xferRadiusDecal(xfer); + + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void RadiusDecalBehavior::loadPostProcess( void ) +{ + + // extend base class + UpdateModule::loadPostProcess(); + +} // end loadPostProcess diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/LocomotorSetUpgrade.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/LocomotorSetUpgrade.cpp index 8899f65ab6e..c5550ff49ef 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/LocomotorSetUpgrade.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/LocomotorSetUpgrade.cpp @@ -1,146 +1,146 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: LocomotorSetUpgrade.cpp ///////////////////////////////////////////////////////////////////////////// -// Author: Graham Smallwood, March 2002 -// Desc: UpgradeModule that sets a weapon set bit for the Best Fit weapon set chooser to discover -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine - -#define DEFINE_LOCOMOTORSET_NAMES //Gain access to TheLocomotorSetNames[] - -#include "Common/Xfer.h" -#include "GameLogic/Object.h" -#include "GameLogic/Module/LocomotorSetUpgrade.h" -#include "GameLogic/Module/AIUpdate.h" - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -LocomotorSetUpgradeModuleData::LocomotorSetUpgradeModuleData(void) -{ - m_setUpgraded = TRUE; - m_useLocomotorType = FALSE; - m_LocomotorType = LOCOMOTORSET_INVALID; - // m_needsParkedAircraft = FALSE; -} -// ------------------------------------------------------------------------------------------------ -// ------------------------------------------------------------------------------------------------ -/*static*/ void LocomotorSetUpgradeModuleData::parseLocomotorType(INI* ini, void* instance, void* store, const void* /*userData*/) -{ - const char* token = ini->getNextToken(); - if (stricmp(token, "None") != 0) { - LocomotorSetUpgradeModuleData* self = (LocomotorSetUpgradeModuleData*)instance; - self->m_useLocomotorType = true; - *(LocomotorSetType*)store = (LocomotorSetType)INI::scanIndexList(token, TheLocomotorSetNames); - } -} -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void LocomotorSetUpgradeModuleData::buildFieldParse(MultiIniFieldParse& p) -{ - - UpgradeModuleData::buildFieldParse(p); - - static const FieldParse dataFieldParse[] = - { - { "EnableUpgrade", INI::parseBool, NULL, offsetof(LocomotorSetUpgradeModuleData, m_setUpgraded) }, - { "ExplicitLocomotorType", LocomotorSetUpgradeModuleData::parseLocomotorType, NULL, offsetof(LocomotorSetUpgradeModuleData, m_LocomotorType)}, - //{ "NeedsParkedAircraft", INI::parseBool, NULL, offsetof(WeaponSetUpgradeModuleData, m_needsParkedAircraft) }, - { 0, 0, 0, 0 } - }; - - p.add(dataFieldParse); - -} // end buildFieldParse - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -LocomotorSetUpgrade::LocomotorSetUpgrade( Thing *thing, const ModuleData* moduleData ) : UpgradeModule( thing, moduleData ) -{ -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -LocomotorSetUpgrade::~LocomotorSetUpgrade( void ) -{ -} - -//------------------------------------------------------------------------------------------------- -//------------------------------------------------------------------------------------------------- -void LocomotorSetUpgrade::upgradeImplementation( ) -{ - const LocomotorSetUpgradeModuleData* data = getLocomotorSetUpgradeModuleData(); - AIUpdateInterface* ai = getObject()->getAIUpdateInterface(); - if (ai) { - if (data->m_useLocomotorType && data->m_LocomotorType != LOCOMOTORSET_NORMAL_UPGRADED) { - ai->chooseLocomotorSet(data->m_LocomotorType); - } - else { - ai->setLocomotorUpgrade(data->m_setUpgraded); - } - } - -} - -// ------------------------------------------------------------------------------------------------ -/** CRC */ -// ------------------------------------------------------------------------------------------------ -void LocomotorSetUpgrade::crc( Xfer *xfer ) -{ - - // extend base class - UpgradeModule::crc( xfer ); - -} // end crc - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void LocomotorSetUpgrade::xfer( Xfer *xfer ) -{ - - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // extend base class - UpgradeModule::xfer( xfer ); - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Load post process */ -// ------------------------------------------------------------------------------------------------ -void LocomotorSetUpgrade::loadPostProcess( void ) -{ - - // extend base class - UpgradeModule::loadPostProcess(); - -} // end loadPostProcess +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: LocomotorSetUpgrade.cpp ///////////////////////////////////////////////////////////////////////////// +// Author: Graham Smallwood, March 2002 +// Desc: UpgradeModule that sets a weapon set bit for the Best Fit weapon set chooser to discover +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +#define DEFINE_LOCOMOTORSET_NAMES //Gain access to TheLocomotorSetNames[] + +#include "Common/Xfer.h" +#include "GameLogic/Object.h" +#include "GameLogic/Module/LocomotorSetUpgrade.h" +#include "GameLogic/Module/AIUpdate.h" + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +LocomotorSetUpgradeModuleData::LocomotorSetUpgradeModuleData(void) +{ + m_setUpgraded = TRUE; + m_useLocomotorType = FALSE; + m_LocomotorType = LOCOMOTORSET_INVALID; + // m_needsParkedAircraft = FALSE; +} +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +/*static*/ void LocomotorSetUpgradeModuleData::parseLocomotorType(INI* ini, void* instance, void* store, const void* /*userData*/) +{ + const char* token = ini->getNextToken(); + if (stricmp(token, "None") != 0) { + LocomotorSetUpgradeModuleData* self = (LocomotorSetUpgradeModuleData*)instance; + self->m_useLocomotorType = true; + *(LocomotorSetType*)store = (LocomotorSetType)INI::scanIndexList(token, TheLocomotorSetNames); + } +} +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void LocomotorSetUpgradeModuleData::buildFieldParse(MultiIniFieldParse& p) +{ + + UpgradeModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + { "EnableUpgrade", INI::parseBool, NULL, offsetof(LocomotorSetUpgradeModuleData, m_setUpgraded) }, + { "ExplicitLocomotorType", LocomotorSetUpgradeModuleData::parseLocomotorType, NULL, offsetof(LocomotorSetUpgradeModuleData, m_LocomotorType)}, + //{ "NeedsParkedAircraft", INI::parseBool, NULL, offsetof(WeaponSetUpgradeModuleData, m_needsParkedAircraft) }, + { 0, 0, 0, 0 } + }; + + p.add(dataFieldParse); + +} // end buildFieldParse + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +LocomotorSetUpgrade::LocomotorSetUpgrade( Thing *thing, const ModuleData* moduleData ) : UpgradeModule( thing, moduleData ) +{ +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +LocomotorSetUpgrade::~LocomotorSetUpgrade( void ) +{ +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +void LocomotorSetUpgrade::upgradeImplementation( ) +{ + const LocomotorSetUpgradeModuleData* data = getLocomotorSetUpgradeModuleData(); + AIUpdateInterface* ai = getObject()->getAIUpdateInterface(); + if (ai) { + if (data->m_useLocomotorType && data->m_LocomotorType != LOCOMOTORSET_NORMAL_UPGRADED) { + ai->chooseLocomotorSet(data->m_LocomotorType); + } + else { + ai->setLocomotorUpgrade(data->m_setUpgraded); + } + } + +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void LocomotorSetUpgrade::crc( Xfer *xfer ) +{ + + // extend base class + UpgradeModule::crc( xfer ); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void LocomotorSetUpgrade::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // extend base class + UpgradeModule::xfer( xfer ); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void LocomotorSetUpgrade::loadPostProcess( void ) +{ + + // extend base class + UpgradeModule::loadPostProcess(); + +} // end loadPostProcess diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp index 66d9fb090c1..bb18ec31cbf 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp @@ -1,212 +1,212 @@ -/* -** Command & Conquer Generals Zero Hour(tm) -** Copyright 2025 Electronic Arts Inc. -** -** This program is free software: you can redistribute it and/or modify -** it under the terms of the GNU General Public License as published by -** the Free Software Foundation, either version 3 of the License, or -** (at your option) any later version. -** -** This program is distributed in the hope that it will be useful, -** but WITHOUT ANY WARRANTY; without even the implied warranty of -** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -** GNU General Public License for more details. -** -** You should have received a copy of the GNU General Public License -** along with this program. If not, see . -*/ - -//////////////////////////////////////////////////////////////////////////////// -// // -// (c) 2001-2003 Electronic Arts Inc. // -// // -//////////////////////////////////////////////////////////////////////////////// - -// FILE: Damage.cpp /////////////////////////////////////////////////////////////////////////////// -// Author: Colin Day, September 2002 -// Desc: Basic structures for the damage process -/////////////////////////////////////////////////////////////////////////////////////////////////// - -// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// -#include "PreRTS.h" -#include "Common/Xfer.h" -#include "GameLogic/Damage.h" -#include "Common/BitFlagsIO.h" -#include "Common/ThingFactory.h" -#include "Common/ThingTemplate.h" - -/////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////////////////////////// - -const char* DamageTypeFlags::s_bitNameList[] = -{ - "EXPLOSION", - "CRUSH", - "ARMOR_PIERCING", - "SMALL_ARMS", - "GATTLING", - "RADIATION", - "FLAME", - "LASER", - "SNIPER", - "POISON", - "HEALING", - "UNRESISTABLE", - "WATER", - "DEPLOY", - "SURRENDER", - "HACK", - "KILL_PILOT", - "PENALTY", - "FALLING", - "MELEE", - "DISARM", - "HAZARD_CLEANUP", - "PARTICLE_BEAM", - "TOPPLING", - "INFANTRY_MISSILE", - "AURORA_BOMB", - "LAND_MINE", - "JET_MISSILES", - "STEALTHJET_MISSILES", - "MOLOTOV_COCKTAIL", - "COMANCHE_VULCAN", - "SUBDUAL_MISSILE", - "SUBDUAL_VEHICLE", - "SUBDUAL_BUILDING", - "SUBDUAL_UNRESISTABLE", - "MICROWAVE", - "KILL_GARRISONED", - "STATUS", - // Generic additional damage types (no special logic) - "SONIC", - "ACID", - "JET_BOMB", - "ANTI_TANK_GUN", - "ANTI_TANK_MISSILE", - "ANTI_AIR_GUN", - "ANTI_AIR_MISSILE", - "ARTILLERY", - "SEISMIC", - "RAD_BEAM", - "TESLA", - // Specific damage types with special logic attached - "CHRONO_GUN", - //"ZOMBIE_VIRUS", // TODO - //"MIND_CONTROL", // TODO - NULL -}; - -DamageTypeFlags DAMAGE_TYPE_FLAGS_NONE; // inits to all zeroes -DamageTypeFlags DAMAGE_TYPE_FLAGS_ALL; - -void initDamageTypeFlags() -{ - SET_ALL_DAMAGE_TYPE_BITS( DAMAGE_TYPE_FLAGS_ALL ); -} - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void DamageInfo::xfer( Xfer *xfer ) -{ - - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // xfer input - xfer->xferSnapshot( &in ); - - // xfer output - xfer->xferSnapshot( &out ); - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version - * 2: Damage FX override -*/ -// ------------------------------------------------------------------------------------------------ -void DamageInfoInput::xfer( Xfer *xfer ) -{ - - // version - XferVersion currentVersion = 3; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // source id - xfer->xferObjectID( &m_sourceID ); - - // source player mask - xfer->xferUser( &m_sourcePlayerMask, sizeof( PlayerMaskType ) ); - - // damage type - xfer->xferUser( &m_damageType, sizeof( DamageType ) ); - - // damage FX Override - if( version >= 2 ) - xfer->xferUser( &m_damageFXOverride, sizeof( DamageType ) ); - - // death type - xfer->xferUser( &m_deathType, sizeof( DeathType ) ); - - // amount - xfer->xferReal( &m_amount ); - - // kill no matter what (old versions default to FALSE). - if( currentVersion >= 2 ) - { - xfer->xferBool( &m_kill ); - } - - xfer->xferUser( &m_damageStatusType, sizeof(ObjectStatusTypes) );//It's an enum - - xfer->xferCoord3D(&m_shockWaveVector); - xfer->xferReal( &m_shockWaveAmount ); - xfer->xferReal( &m_shockWaveRadius ); - xfer->xferReal( &m_shockWaveTaperOff ); - - if( version >= 3 ) - { - AsciiString thingString = m_sourceTemplate ? m_sourceTemplate->getName() : AsciiString::TheEmptyString; - xfer->xferAsciiString( &thingString ); - if( xfer->getXferMode() == XFER_LOAD ) - { - m_sourceTemplate = TheThingFactory->findTemplate( thingString ); - } - } - -} // end xfer - -// ------------------------------------------------------------------------------------------------ -/** Xfer method - * Version Info: - * 1: Initial version */ -// ------------------------------------------------------------------------------------------------ -void DamageInfoOutput::xfer( Xfer *xfer ) -{ - - // version - XferVersion currentVersion = 1; - XferVersion version = currentVersion; - xfer->xferVersion( &version, currentVersion ); - - // actual damage - xfer->xferReal( &m_actualDamageDealt ); - - // damage clipped - xfer->xferReal( &m_actualDamageClipped ); - - // no effect - xfer->xferBool( &m_noEffect ); - -} // end xfer - +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 Electronic Arts Inc. +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +//////////////////////////////////////////////////////////////////////////////// +// // +// (c) 2001-2003 Electronic Arts Inc. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// FILE: Damage.cpp /////////////////////////////////////////////////////////////////////////////// +// Author: Colin Day, September 2002 +// Desc: Basic structures for the damage process +/////////////////////////////////////////////////////////////////////////////////////////////////// + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" +#include "Common/Xfer.h" +#include "GameLogic/Damage.h" +#include "Common/BitFlagsIO.h" +#include "Common/ThingFactory.h" +#include "Common/ThingTemplate.h" + +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +const char* DamageTypeFlags::s_bitNameList[] = +{ + "EXPLOSION", + "CRUSH", + "ARMOR_PIERCING", + "SMALL_ARMS", + "GATTLING", + "RADIATION", + "FLAME", + "LASER", + "SNIPER", + "POISON", + "HEALING", + "UNRESISTABLE", + "WATER", + "DEPLOY", + "SURRENDER", + "HACK", + "KILL_PILOT", + "PENALTY", + "FALLING", + "MELEE", + "DISARM", + "HAZARD_CLEANUP", + "PARTICLE_BEAM", + "TOPPLING", + "INFANTRY_MISSILE", + "AURORA_BOMB", + "LAND_MINE", + "JET_MISSILES", + "STEALTHJET_MISSILES", + "MOLOTOV_COCKTAIL", + "COMANCHE_VULCAN", + "SUBDUAL_MISSILE", + "SUBDUAL_VEHICLE", + "SUBDUAL_BUILDING", + "SUBDUAL_UNRESISTABLE", + "MICROWAVE", + "KILL_GARRISONED", + "STATUS", + // Generic additional damage types (no special logic) + "SONIC", + "ACID", + "JET_BOMB", + "ANTI_TANK_GUN", + "ANTI_TANK_MISSILE", + "ANTI_AIR_GUN", + "ANTI_AIR_MISSILE", + "ARTILLERY", + "SEISMIC", + "RAD_BEAM", + "TESLA", + // Specific damage types with special logic attached + "CHRONO_GUN", + //"ZOMBIE_VIRUS", // TODO + //"MIND_CONTROL", // TODO + NULL +}; + +DamageTypeFlags DAMAGE_TYPE_FLAGS_NONE; // inits to all zeroes +DamageTypeFlags DAMAGE_TYPE_FLAGS_ALL; + +void initDamageTypeFlags() +{ + SET_ALL_DAMAGE_TYPE_BITS( DAMAGE_TYPE_FLAGS_ALL ); +} + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void DamageInfo::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // xfer input + xfer->xferSnapshot( &in ); + + // xfer output + xfer->xferSnapshot( &out ); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version + * 2: Damage FX override +*/ +// ------------------------------------------------------------------------------------------------ +void DamageInfoInput::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 3; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // source id + xfer->xferObjectID( &m_sourceID ); + + // source player mask + xfer->xferUser( &m_sourcePlayerMask, sizeof( PlayerMaskType ) ); + + // damage type + xfer->xferUser( &m_damageType, sizeof( DamageType ) ); + + // damage FX Override + if( version >= 2 ) + xfer->xferUser( &m_damageFXOverride, sizeof( DamageType ) ); + + // death type + xfer->xferUser( &m_deathType, sizeof( DeathType ) ); + + // amount + xfer->xferReal( &m_amount ); + + // kill no matter what (old versions default to FALSE). + if( currentVersion >= 2 ) + { + xfer->xferBool( &m_kill ); + } + + xfer->xferUser( &m_damageStatusType, sizeof(ObjectStatusTypes) );//It's an enum + + xfer->xferCoord3D(&m_shockWaveVector); + xfer->xferReal( &m_shockWaveAmount ); + xfer->xferReal( &m_shockWaveRadius ); + xfer->xferReal( &m_shockWaveTaperOff ); + + if( version >= 3 ) + { + AsciiString thingString = m_sourceTemplate ? m_sourceTemplate->getName() : AsciiString::TheEmptyString; + xfer->xferAsciiString( &thingString ); + if( xfer->getXferMode() == XFER_LOAD ) + { + m_sourceTemplate = TheThingFactory->findTemplate( thingString ); + } + } + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void DamageInfoOutput::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // actual damage + xfer->xferReal( &m_actualDamageDealt ); + + // damage clipped + xfer->xferReal( &m_actualDamageClipped ); + + // no effect + xfer->xferBool( &m_noEffect ); + +} // end xfer +