diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index 425eba687a..6fbd280bfb 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 @@ -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 @@ -309,6 +310,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 @@ -323,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 @@ -345,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 @@ -399,6 +403,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 @@ -432,6 +437,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 @@ -445,6 +451,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 @@ -470,6 +477,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 @@ -668,10 +676,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 +689,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 @@ -852,12 +860,15 @@ 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/DelayedUpgradeBehavior.cpp Source/GameLogic/Object/Behavior/FlightDeckBehavior.cpp 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 @@ -948,6 +959,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 @@ -965,6 +977,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 @@ -981,6 +994,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 @@ -1035,6 +1049,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 @@ -1059,6 +1074,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 @@ -1167,12 +1183,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/Common/DisabledTypes.h b/GeneralsMD/Code/GameEngine/Include/Common/DisabledTypes.h index 840a967851..26c1b28814 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/DisabledTypes.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/DisabledTypes.h @@ -59,6 +59,9 @@ enum DisabledType CPP_11(: Int) 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) diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GameCommon.h b/GeneralsMD/Code/GameEngine/Include/Common/GameCommon.h index dbbad0424c..0937e76d41 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 c969afaaa3..f11ee558a0 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GameType.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GameType.h @@ -175,6 +175,11 @@ 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 }; diff --git a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h index 2256d749b9..2866f604b1 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/GlobalData.h @@ -51,6 +51,7 @@ class INI; class WeaponBonusSet; enum BodyDamageType CPP_11(: Int); enum AIDebugOptions CPP_11(: Int); +typedef UnsignedInt DeathTypeFlags; //enum DrawableColorTint CPP_11(: Int); // PUBLIC ///////////////////////////////////////////////////////////////////////////////////////// @@ -527,6 +528,22 @@ class GlobalData : public SubsystemInterface Bool m_useOldMoveSpeed; + Real m_chronoDamageDisableThreshold; + UnsignedInt m_chronoDamageHealRate; + Real m_chronoDamageHealAmount; + + Real m_chronoDisableAlphaStart; + Real m_chronoDisableAlphaEnd; + + // TintStatus m_chronoTintStatusType; + AsciiString m_chronoDisableParticleSystemLarge; + AsciiString m_chronoDisableParticleSystemMedium; + AsciiString m_chronoDisableParticleSystemSmall; + + //AudioEventRTS m_chronoDisableSoundLoop; + + DeathTypeFlags m_defaultExcludedDeathTypes; + // the trailing '\' is included! const AsciiString &getPath_UserData() const { return m_userDataDir; } diff --git a/GeneralsMD/Code/GameEngine/Include/Common/INI.h b/GeneralsMD/Code/GameEngine/Include/Common/INI.h index 8d5c22b4bf..f8495c99ef 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 ); @@ -319,6 +320,7 @@ class INI 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*/ ); diff --git a/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h b/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h index 15de5d358a..757bdf97f2 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/KindOf.h @@ -171,6 +171,40 @@ enum KindOfType CPP_11(: Int) 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 }; diff --git a/GeneralsMD/Code/GameEngine/Include/Common/MiscAudio.h b/GeneralsMD/Code/GameEngine/Include/Common/MiscAudio.h index 5c032f736d..7cbc338d5e 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/Common/ModelState.h b/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h index 6749326ed7..9d55321ae9 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/ModelState.h @@ -248,6 +248,44 @@ 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, + + // 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! diff --git a/GeneralsMD/Code/GameEngine/Include/Common/Player.h b/GeneralsMD/Code/GameEngine/Include/Common/Player.h index 05179cd2a3..e578e19c4a 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,16 +383,24 @@ 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; /// 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; @@ -406,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/Common/ThingTemplate.h b/GeneralsMD/Code/GameEngine/Include/Common/ThingTemplate.h index b918059d7f..73ce1542b4 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/ThingTemplate.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/ThingTemplate.h @@ -1,810 +1,812 @@ -/* -** 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_THIN, ///< like default, but half width + + AMMO_PIPS_NUM_TYPES // leave this last +}; +#ifdef DEFINE_AMMO_PIPS_STYLE_NAMES +static const char* AmmoPipsStyleNames[] = +{ + "DEFAULT", + "PERCENTAGE_BAR", + "SINGLE", + "THIN", + + 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/GameClient/Drawable.h b/GeneralsMD/Code/GameEngine/Include/GameClient/Drawable.h index d115447b69..fd63e91b4f 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/Include/GameClient/RadiusDecal.h b/GeneralsMD/Code/GameEngine/Include/GameClient/RadiusDecal.h index 5e720d3f54..f7ea1e8cb0 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/RadiusDecal.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/RadiusDecal.h @@ -90,6 +90,17 @@ class RadiusDecalTemplate 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/GameClient/TintStatus.h b/GeneralsMD/Code/GameEngine/Include/GameClient/TintStatus.h index 7d84c4b578..6ed01a5574 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/TintStatus.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/TintStatus.h @@ -23,6 +23,9 @@ 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_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, diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/AI.h index d16d6b0019..82fae990e8 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/AIStateMachine.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/AIStateMachine.h index 1a5cf974f8..d99b8560b2 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/Include/GameLogic/Damage.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h index f1a0a7e96f..fad83d9ea6 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Damage.h @@ -86,7 +86,26 @@ enum DamageType CPP_11(: Int) 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 + 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[] @@ -194,6 +213,9 @@ enum DeathType CPP_11(: Int) DEATH_EXTRA_7 = 18, DEATH_EXTRA_8 = 19, DEATH_POISONED_GAMMA = 20, + + //New Death Types + DEATH_CHRONO, DEATH_NUM_TYPES // keep this last }; @@ -221,7 +243,9 @@ static const char *TheDeathNames[] = "EXTRA_6", "EXTRA_7", "EXTRA_8", - "POISONED_GAMMA", + "POISONED_GAMMA", + //New: + "CHRONO", NULL }; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h index af8c7f1a97..d694becb90 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Locomotor.h @@ -361,6 +361,12 @@ class Locomotor : public MemoryPoolObject, public Snapshot 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); @@ -389,7 +395,6 @@ class Locomotor : public MemoryPoolObject, public Snapshot 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); @@ -455,6 +460,7 @@ class Locomotor : public MemoryPoolObject, public Snapshot Real m_offsetIncrement; UnsignedInt m_donutTimer; ///< Frame time to keep units from doing the donut. jba. + Real m_speedMultiplier; ///< scalar to max speed and acceleration }; diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/AIUpdate.h index 791bece902..a35e35b273 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 }; @@ -238,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; } @@ -327,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; } @@ -343,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; } @@ -360,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(); } @@ -400,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(); @@ -431,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); @@ -440,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; @@ -452,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, @@ -503,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; } @@ -597,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, @@ -621,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(); @@ -646,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; @@ -659,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 @@ -671,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/ActiveBody.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ActiveBody.h index ecfa264ae2..57eb9f3d05 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" @@ -89,6 +90,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 @@ -118,7 +124,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 @@ -128,6 +134,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 +156,9 @@ class ActiveBody : public BodyModule Bool shouldRetaliateAgainstAggressor(Object *obj, Object *damager); virtual void internalAddSubdualDamage( Real delta ); ///< change health + virtual void internalAddChronoDamage( Real delta ); ///< change health + + virtual void applyChronoParticleSystems(void); private: @@ -153,6 +167,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; @@ -167,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/Include/GameLogic/Module/ArmorUpgrade.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ArmorUpgrade.h index a62a3244c6..035ec956c3 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/BodyModule.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/BodyModule.h index 4493e55148..b4c568d61c 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. @@ -189,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; @@ -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; @@ -289,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/ChronoDamageHelper.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ChronoDamageHelper.h new file mode 100644 index 0000000000..7a4476c585 --- /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/ChronoDeathBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ChronoDeathBehavior.h new file mode 100644 index 0000000000..45a0af952c --- /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/CostModifierUpgrade.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/CostModifierUpgrade.h index 5b4971ecd1..8a87c79b9e 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/Include/GameLogic/Module/DelayedUpgradeBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DelayedUpgradeBehavior.h new file mode 100644 index 0000000000..c4b69c3068 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DelayedUpgradeBehavior.h @@ -0,0 +1,145 @@ +/* +** 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: Andi W, July 2025 +// Desc: Update that will trigger an upgrade after some time +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#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(); + + // This should be active while disabled + virtual DisabledMaskType getDisabledTypesToProcess() const { return DISABLEDMASK_ALL; } + +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/DumbProjectileBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/DumbProjectileBehavior.h index 88998889e7..1adc2ca995 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/FreeFallProjectileBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/FreeFallProjectileBehavior.h new file mode 100644 index 0000000000..82c15a77c7 --- /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/ImmortalBody.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ImmortalBody.h index a7a290a381..bcc5b555f4 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 9f567262c1..9807e2b78a 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/Include/GameLogic/Module/JetAIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/JetAIUpdate.h index 19fedcdbe6..a3d5f0f584 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/Include/GameLogic/Module/LocomotorSetUpgrade.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/LocomotorSetUpgrade.h index a093521ffd..983dd021bb 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/MissileAIUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h index 703ce7d1c7..43fb44e628 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/MissileAIUpdate.h @@ -63,8 +63,18 @@ 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_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_zDirFactor; ///< Z correction factor for AA weapons with no pitch + + 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); @@ -89,6 +99,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 +132,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 +149,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/Include/GameLogic/Module/ParkingPlaceBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h index 8def59d5eb..37d6a164bb 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ParkingPlaceBehavior.h @@ -50,9 +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_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; @@ -62,6 +69,8 @@ class ParkingPlaceBehaviorModuleData : public UpdateModuleData m_landingDeckHeightOffset = 0.0f; m_hasRunways = false; m_parkInHangars = false; + m_damageScalar = 1.0f; + m_damageScalarUpgraded = 1.0f; } static void buildFieldParse(MultiIniFieldParse& p) @@ -78,8 +87,12 @@ 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) }, + { "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 } @@ -210,8 +223,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/Include/GameLogic/Module/PhysicsUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/PhysicsUpdate.h index 4210847940..a7ca3d3b01 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/Include/GameLogic/Module/ProductionTimeModifierUpgrade.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/ProductionTimeModifierUpgrade.h index 594487fe40..69f2eddbbe 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/Include/GameLogic/Module/RadiusDecalBehavior.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h new file mode 100644 index 0000000000..acaa198403 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/RadiusDecalBehavior.h @@ -0,0 +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_ + diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StealthUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StealthUpdate.h index 351e2b0141..149b5aa79c 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StealthUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StealthUpdate.h @@ -51,8 +51,13 @@ enum 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, + 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 @@ -66,7 +71,12 @@ static const char *TheStealthLevelNames[] = "FIRING_TERTIARY", "NO_BLACK_MARKET", "TAKING_DAMAGE", - "RIDERS_ATTACKING", + "RIDERS_ATTACKING", + "FIRING_WEAPON_FOUR", + "FIRING_WEAPON_FIVE", + "FIRING_WEAPON_SIX", + "FIRING_WEAPON_SEVEN", + "FIRING_WEAPON_EIGHT", NULL }; #endif 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 0000000000..15b8de527f --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/TeleporterAIUpdate.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. // +// // +//////////////////////////////////////////////////////////////////////////////// + +// 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 FXList; + + +//------------------------------------------------------------------------------------------------- +class TeleporterAIUpdateModuleData : public AIUpdateModuleData +{ +public: + Real m_minDistance; + Real m_disabledDuration; + + 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(); + + 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(); + + /// 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); + + 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; + + //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(); + +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/Include/GameLogic/Module/UnitProductionBonusUpgrade.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UnitProductionBonusUpgrade.h new file mode 100644 index 0000000000..f68b6051eb --- /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/Include/GameLogic/Module/UpgradeSpecialPower.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/UpgradeSpecialPower.h new file mode 100644 index 0000000000..8ec2286d3f --- /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/Include/GameLogic/Module/WeaponBonusUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/WeaponBonusUpdate.h index 4e96ce9677..2de8a786a2 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/Include/GameLogic/Object.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h index 9abdb11701..99cdf4b75b 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h @@ -105,6 +105,7 @@ class ObjectSMCHelper; class ObjectRepulsorHelper; class StatusDamageHelper; class SubdualDamageHelper; +class ChronoDamageHelper; class TempWeaponBonusHelper; class ObjectWeaponStatusHelper; class ObjectDefectionHelper; @@ -234,6 +235,7 @@ class Object : public Thing, public Snapshot 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 @@ -739,6 +741,7 @@ class Object : public Thing, public Snapshot 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 diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/WeaponSet.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/WeaponSet.h index 6b6fc75a88..639ef8ca4a 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/Include/GameLogic/WeaponSetType.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/WeaponSetType.h index 34fa3f1861..706853f320 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/BitFlags.cpp b/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp index ef8b2a3ffe..cf50f5ac7b 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/BitFlags.cpp @@ -175,6 +175,43 @@ 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", + + "TAKEOFF", + "LANDING", + + "TELEPORT_RECOVER", NULL }; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp index 7858544a4b..2e218e14ae 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/GlobalData.cpp @@ -543,6 +543,20 @@ GlobalData* GlobalData::m_theOriginal = NULL; { "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 }; @@ -1150,6 +1164,19 @@ GlobalData::GlobalData() } // ------------------------------------------------------------------------------ + 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 diff --git a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp index 10d236cb77..4f873b6b75 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/INI/INI.cpp @@ -44,6 +44,7 @@ #include "Common/ThingFactory.h" #include "Common/ThingTemplate.h" #include "Common/Upgrade.h" +#include "Common/GlobalData.h" #include "Common/Xfer.h" #include "Common/XferCRC.h" @@ -1470,6 +1471,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. @@ -1925,11 +1942,21 @@ void INI::parseDamageTypeFlags(INI* ini, void* /*instance*/, void* store, const 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) @@ -1954,6 +1981,31 @@ void INI::parseDeathTypeFlags(INI* ini, void* /*instance*/, void* store, const v *(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] diff --git a/GeneralsMD/Code/GameEngine/Source/Common/INI/INIMiscAudio.cpp b/GeneralsMD/Code/GameEngine/Source/Common/INI/INIMiscAudio.cpp index a5b2261de5..7b1eac68de 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/Common/RTS/ActionManager.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/ActionManager.cpp index db8f2d3189..f81649a95a 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/RTS/Player.cpp b/GeneralsMD/Code/GameEngine/Source/Common/RTS/Player.cpp index 1861869fa1..9ae1346d2b 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 { @@ -3888,25 +3918,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 +3959,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 +3980,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; @@ -3960,25 +4013,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); } @@ -3986,14 +4046,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) @@ -4002,6 +4067,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; @@ -4744,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/DisabledTypes.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/DisabledTypes.cpp index 65797c548b..32ae45f2ea 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/DisabledTypes.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/DisabledTypes.cpp @@ -48,6 +48,9 @@ const char* DisabledMaskType::s_bitNameList[] = "DISABLED_SCRIPT_DISABLED", "DISABLED_SCRIPT_UNDERPOWERED", + "DISABLED_TELEPORT", + "DISABLED_CHRONO", + NULL }; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp index 47ef9795f9..b9783f48d0 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/KindOf.cpp @@ -158,6 +158,37 @@ 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", + + "TELEPORTER", + + "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 3e2cccaac3..62cad43ea6 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 }, @@ -151,6 +152,7 @@ static PoolSizeRec sizes[] = { "ObjectDefectionHelperPool", 2048, 256 }, { "StatusDamageHelper", 1500, 256 }, { "SubdualDamageHelper", 1500, 256 }, + { "ChronoDamageHelper", 1500, 256 }, { "TempWeaponBonusHelper", 4096, 256 }, { "Locomotor", 2048, 32 }, { "LocomotorTemplate", 192, 32 }, @@ -194,6 +196,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 }, @@ -214,6 +217,7 @@ static PoolSizeRec sizes[] = { "ScatterShotUpdate", 128, 64 }, { "FireWeaponWhenDamagedBehavior", 32, 32 }, { "FireWeaponWhenDeadBehavior", 128, 64 }, + { "DelayedUpgradeBehavior", 128, 64 }, { "GenerateMinefieldBehavior", 32, 32 }, { "HelicopterSlowDeathBehavior", 64, 32 }, { "ParkingPlaceBehavior", 32, 32 }, @@ -236,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 }, @@ -248,6 +253,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 }, @@ -274,6 +280,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 }, @@ -347,6 +354,7 @@ static PoolSizeRec sizes[] = { "JetAIUpdate", 64, 32 }, { "ChinookAIUpdate", 32, 32 }, { "WanderAIUpdate", 32, 32 }, + { "TeleporterAIUpdate", 64, 32 }, { "WaveGuideUpdate", 16, 16 }, { "ArmorDamageScalarUpdate", 256, 32 }, { "WeaponBonusUpgrade", 512, 128 }, @@ -395,6 +403,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 }, @@ -485,7 +494,9 @@ static PoolSizeRec sizes[] = { "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 }, diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index 83a6ff4b14..9a9ad1067c 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -51,7 +51,9 @@ #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" @@ -78,6 +80,7 @@ #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" @@ -144,6 +147,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" @@ -185,6 +189,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" @@ -214,6 +219,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" @@ -272,6 +278,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" @@ -336,8 +343,10 @@ void ModuleFactory::init( void ) addModule( BridgeTowerBehavior ); addModule( CountermeasuresBehavior ); addModule( DumbProjectileBehavior ); + addModule( FreeFallProjectileBehavior ); addModule( PhysicsBehavior ); addModule( InstantDeathBehavior ); + addModule( ChronoDeathBehavior ); addModule( SlowDeathBehavior ); addModule( HelicopterSlowDeathBehavior ); addModule( NeutronMissileSlowDeathBehavior ); @@ -364,6 +373,7 @@ void ModuleFactory::init( void ) addModule( BunkerBusterBehavior ); addModule( FireWeaponWhenDamagedBehavior ); addModule( FireWeaponWhenDeadBehavior ); + addModule( DelayedUpgradeBehavior ); addModule( GenerateMinefieldBehavior ); addModule( ParkingPlaceBehavior ); addModule( FlightDeckBehavior ); @@ -406,6 +416,7 @@ void ModuleFactory::init( void ) addModule( EnemyNearUpdate ); addModule( LifetimeUpdate ); addModule( RadiusDecalUpdate ); + addModule( RadiusDecalBehavior ); addModule( EMPUpdate ); addModule( LeafletDropBehavior ); addModule( AutoDepositUpdate ); @@ -474,6 +485,7 @@ void ModuleFactory::init( void ) addModule( AnimationSteeringUpdate ); addModule( TransportAIUpdate ); addModule( WanderAIUpdate ); + addModule( TeleporterAIUpdate ); addModule( WaveGuideUpdate ); addModule( WorkerAIUpdate ); addModule( PowerPlantUpdate ); @@ -482,6 +494,7 @@ void ModuleFactory::init( void ) // upgrade modules addModule( CostModifierUpgrade ); addModule( ProductionTimeModifierUpgrade ); + addModule( UnitProductionBonusUpgrade ); addModule( ActiveShroudUpgrade ); addModule( ArmorUpgrade ); addModule( CommandSetUpgrade ); @@ -559,6 +572,7 @@ void ModuleFactory::init( void ) addModule( FireWeaponPower ); addModule( SpecialAbility ); addModule( SpyVisionSpecialPower ); + addModule( UpgradeSpecialPower ); addModule( CashBountyPower ); addModule( CleanupAreaPower ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index e91de9272f..330e306ae4 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -128,6 +128,9 @@ const char* TintStatusFlags::s_bitNameList[] = "SHIELDED", "DEMORALIZED", "BOOST", + "TELEPORT_RECOVER", + "DISABLED_CHRONO", + "GAINING_CHRONO_DAMAGE", "EXTRA1", "EXTRA2", "EXTRA3", @@ -289,6 +292,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 +316,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"); @@ -867,6 +878,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; @@ -2980,7 +2992,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; @@ -3031,6 +3042,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; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommand.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/ControlBar/ControlBarCommand.cpp index b5759c34bc..cfa851bb33 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/GameClient/GUI/Shell/Shell.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/GUI/Shell/Shell.cpp index 47039e76a5..def99ca421 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... + //} } } diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/MessageStream/CommandXlat.cpp index 149c1e3110..13074cd6a4 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( "VoiceWeaponModeFour" ); + break; + case WEAPON_FIVE: + soundToPlayPtr = templ->getPerUnitSound( "VoiceWeaponModeFive" ); + break; + case WEAPON_SIX: + soundToPlayPtr = templ->getPerUnitSound( "VoiceWeaponModeSix" ); + break; + case WEAPON_SEVEN: + soundToPlayPtr = templ->getPerUnitSound( "VoiceWeaponModeSeven" ); + break; + case WEAPON_EIGHT: + soundToPlayPtr = templ->getPerUnitSound( "VoiceWeaponModeEight" ); + break; } objectWithSound = obj; skip = true; diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/RadiusDecal.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/RadiusDecal.cpp index c0ffe556a0..c40701076d 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 } @@ -197,13 +197,19 @@ void RadiusDecal::update() { if (m_decal && m_template) { - UnsignedInt now = TheGameLogic->getFrame(); - 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/AI/AIGroup.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp index 018f14a760..5b531f55b5 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. @@ -560,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++; @@ -683,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; @@ -752,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/AI/AIStates.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp index eceebc1324..1e748b2ce5 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 @@ -1486,18 +1500,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/Armor.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Armor.cpp index 3b66ecaafc..5d1fb7cb81 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Armor.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Armor.cpp @@ -77,6 +77,8 @@ Real ArmorTemplate::adjustDamage(DamageType t, Real damage) const return damage; if (t == DAMAGE_SUBDUAL_UNRESISTABLE) return damage; + if (t == DAMAGE_CHRONO_UNRESISTABLE) + return damage; damage *= m_damageCoefficient[t]; diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BunkerBusterBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/BunkerBusterBehavior.cpp index 504c233b34..f3300d9dbe 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 ) 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 0000000000..32c5f930b3 --- /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/Behavior/DelayedUpgradeBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DelayedUpgradeBehavior.cpp new file mode 100644 index 0000000000..f71e31f829 --- /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/Behavior/DumbProjectileBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/DumbProjectileBehavior.cpp index 91de76b64d..e18a38bbc6 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/Behavior/FreeFallProjectileBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/FreeFallProjectileBehavior.cpp new file mode 100644 index 0000000000..b55ef3999f --- /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/Behavior/GenerateMinefieldBehavior.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp index e3477ea5dc..3e2afdbd59 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 e2a0990b2d..8f1f09627d 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 // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp index 093ba428bf..bff18be663 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: @@ -491,6 +498,46 @@ void ActiveBody::attemptDamage( DamageInfo *damageInfo ) 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) ) @@ -536,7 +583,7 @@ void ActiveBody::attemptDamage( DamageInfo *damageInfo ) if (!alreadyHandled) { // do the damage simplistic damage subtraction - internalChangeHealth( -amount ); + internalChangeHealth( -amount, adjustConditions); } #ifdef ALLOW_SURRENDER @@ -608,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) { @@ -620,7 +667,7 @@ void ActiveBody::attemptDamage( DamageInfo *damageInfo ) } } - if (m_curDamageState != oldState) + if (m_curDamageState != oldState && adjustConditions) { for (BehaviorModule** m = obj->getBehaviorModules(); *m; ++m) { @@ -1211,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; @@ -1229,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.) @@ -1263,6 +1312,16 @@ void ActiveBody::internalAddSubdualDamage( Real 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 @@ -1313,6 +1372,113 @@ void ActiveBody::onSubdualChange( Bool isNowSubdued ) } } +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +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 @@ -1362,6 +1528,28 @@ 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 @@ -1534,8 +1722,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 d797024973..85f46c6c03 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 cb17a1b057..88095e8658 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/Collide/CrateCollide/VeterancyCrateCollide.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/VeterancyCrateCollide.cpp index ae7b98d1a1..fe925ed337 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/Contain/OpenContain.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Contain/OpenContain.cpp index c6fd0e1c30..e8e976a421 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/Die/DieModule.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Die/DieModule.cpp index 2dcea456b2..0e33363698 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 0000000000..8d7888ca97 --- /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 984973e1b1..5658e8b600 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp @@ -672,6 +672,8 @@ Locomotor::Locomotor(const LocomotorTemplate* tmpl) 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; } //------------------------------------------------------------------------------------------------- @@ -771,6 +773,8 @@ void Locomotor::xfer( Xfer *xfer ) xfer->xferReal(&m_angleOffset); xfer->xferReal(&m_offsetIncrement); + xfer->xferReal(&m_speedMultiplier); + } // end xfer // ------------------------------------------------------------------------------------------------ @@ -798,6 +802,8 @@ Real Locomotor::getMaxSpeedForCondition(BodyDamageType condition) const else speed = m_template->m_maxSpeedDamaged; + speed *= m_speedMultiplier; + if (speed > m_maxSpeed) speed = m_maxSpeed; @@ -814,6 +820,8 @@ Real Locomotor::getMaxTurnRate(BodyDamageType condition) const else turn = m_template->m_maxTurnRateDamaged; + turn *= m_speedMultiplier; + if (turn > m_maxTurnRate) turn = m_maxTurnRate; @@ -834,6 +842,8 @@ Real Locomotor::getMaxAcceleration(BodyDamageType condition) const else accel = m_template->m_accelerationDamaged; + accel *= m_speedMultiplier; + if (accel > m_maxAccel) accel = m_maxAccel; @@ -845,6 +855,8 @@ Real Locomotor::getBraking() const { Real braking = m_template->m_braking; + braking *= m_speedMultiplier; + if (braking > m_maxBraking) braking = m_maxBraking; @@ -861,6 +873,8 @@ Real Locomotor::getMaxLift(BodyDamageType condition) const else lift = m_template->m_liftDamaged; + lift *= m_speedMultiplier; + if (lift > m_maxLift) lift = m_maxLift; @@ -1136,6 +1150,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 +1998,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); @@ -2028,7 +2064,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/Object.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp index 853f82ffd9..2632e6b6e6 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Object.cpp @@ -93,6 +93,7 @@ #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" @@ -145,6 +146,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 ) }; @@ -205,6 +241,7 @@ Object::Object( const ThingTemplate *tt, const ObjectStatusMaskType &objectStatu m_statusDamageHelper(NULL), m_tempWeaponBonusHelper(NULL), m_subdualDamageHelper(NULL), + m_chronoDamageHelper(NULL), m_smcHelper(NULL), m_wsHelper(NULL), m_defectionHelper(NULL), @@ -355,6 +392,12 @@ Object::Object( const ThingTemplate *tt, const ObjectStatusMaskType &objectStatu 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 @@ -675,6 +718,7 @@ Object::~Object() m_statusDamageHelper = NULL; m_tempWeaponBonusHelper = NULL; m_subdualDamageHelper = NULL; + m_chronoDamageHelper = NULL; m_smcHelper = NULL; m_wsHelper = NULL; m_defectionHelper = NULL; @@ -1277,7 +1321,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)); } @@ -2169,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 ) + if( type != DISABLED_HELD && type != DISABLED_SCRIPT_DISABLED && type != DISABLED_UNMANNED && type != DISABLED_TELEPORT && type != DISABLED_CHRONO) { m_drawable->setTintStatus( TINT_STATUS_DISABLED ); } @@ -2345,6 +2394,8 @@ Bool Object::clearDisabled( DisabledType type ) 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); @@ -2940,7 +2991,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; @@ -5323,6 +5375,51 @@ void Object::notifySubdualDamage( Real amount ) } } +//------------------------------------------------------------------------------------------------- +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 */ 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 0000000000..7f839c16de --- /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/AIUpdate/JetAIUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/JetAIUpdate.cpp index e84bbe419f..5648abf5f7 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,3133 @@ -/* -** 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 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) + +// ------------------------------------------------------------------------------------------------ +//------------------------------------------------------------------------------------------------- +/* + 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); + + 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) + { + 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", + 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 + jetAI->friend_enableTakeOffEffects(true); + + landingApproach = m_parkingLoc; + + 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; + + //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())); + + 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_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()); + } + + } + +}; +EMPTY_DTOR(VtolTakeoffOrLandingState) + + +//------------------------------------------------------------------------------------------------- +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() +{ +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------------------------- +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(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); + 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() +{ + 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 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"); + } + + } + +} + +//------------------------------------------------------------------------------------------------- +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_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; + 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_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 ) +{ + 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 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 5f2bd0ef9b..4c1a2959d1 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,10 @@ 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_zDirFactor = 2.0f; + m_applyLauncherBonus = FALSE; } //----------------------------------------------------------------------------- @@ -100,14 +121,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 ) }, + + { "RandomPathOffset",INI::parseReal, NULL, offsetof( MissileAIUpdateModuleData, m_randomPathOffset ) }, + + // 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) }, + { "ApplyLauncherBonus", INI::parseBool, NULL, offsetof(MissileAIUpdateModuleData, m_applyLauncherBonus) }, + { 0, 0, 0, 0 } }; p.add(dataFieldParse); @@ -130,6 +159,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; @@ -177,6 +207,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(); } @@ -201,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 ); @@ -234,21 +269,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) { @@ -260,6 +304,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); @@ -294,7 +340,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(); } @@ -498,7 +544,7 @@ void MissileAIUpdate::doIgnitionState() } //------------------------------------------------------------------------------------------------- -void MissileAIUpdate::doAttackState(Bool turnOK) +void MissileAIUpdate::doAttackState(Bool turnOK, Bool randomPath) { Locomotor* curLoco = getCurLocomotor(); @@ -522,12 +568,50 @@ void MissileAIUpdate::doAttackState(Bool turnOK) { if (curLoco) { - curLoco->setMaxAcceleration(m_maxAccel); - curLoco->setMaxTurnRate(turnOK ? BIGNUM : 0); + if (randomPath) { //ATTACK_RANDOM_PATH state + if (m_randomPathDistLeft <= 0) { + // 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 + // target dies I can do something cool. + m_victimID = victim->getID(); + } + 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) { + 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); + // DEBUG_LOG((">>> MissileAI setMaxTurnRate = %f\n", turnOK ? d->m_turnRateAttacking : d->m_turnRateInitial)); + // curLoco->setMaxTurnRate(turnOK ? d->m_turnRateAttacking : d->m_turnRateInitial); + } } } - if (d->m_lockDistance > 0) + if (!randomPath && d->m_lockDistance > 0) { Real lockDistanceSquared = d->m_lockDistance; Real distanceToTargetSquared; @@ -547,6 +631,7 @@ void MissileAIUpdate::doAttackState(Bool turnOK) // 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; } @@ -559,16 +644,81 @@ void MissileAIUpdate::doAttackState(Bool turnOK) 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))); + } + } } - if (m_noTurnDistLeft <= 0.0f) + // Have we finished NOTURN? + if (m_noTurnDistLeft <= 0.0f && m_state == ATTACK_NOTURN) { - switchToState(ATTACK); + if (d->m_randomPathOffset > 0.0) { + // ----------------------------------- + // We first reach random path state + // ----------------------------------- + // Pick a random position near the target as new goal, and forget tracking the target for now + Coord3D targetPos; + if (m_isTrackingTarget && getGoalObject()) + targetPos = *getGoalObject()->getPosition(); + else + 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 = { + GameLogicRandomValue(-scatter, scatter), + GameLogicRandomValue(-scatter, scatter), + GameLogicRandomValue(0, scatter * 0.5) + }; + adjustVector(&offset, &mtx); + + 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); + m_isTrackingTarget = FALSE; + + Locomotor* curLoco = getCurLocomotor(); + if (curLoco) + { + curLoco->setMaxAcceleration(m_maxAccel); + // curLoco->setMaxTurnRate(d->m_turnRateAttacking); + curLoco->setMaxTurnRate(BIGNUM); + } + + 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 @@ -603,7 +753,9 @@ void MissileAIUpdate::doKillState(void) if (curLoco) { + // const MissileAIUpdateModuleData* d = getMissileAIUpdateModuleData(); curLoco->setMaxAcceleration(m_maxAccel); + // curLoco->setMaxTurnRate(__min(d->m_turnRateAttacking * 2.0f, BIGNUM)); curLoco->setMaxTurnRate(BIGNUM); } if (isIdle()) { @@ -616,7 +768,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); @@ -652,10 +804,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 +870,10 @@ UpdateSleepTime MissileAIUpdate::update() doAttackState(false); break; + case ATTACK_RANDOM_PATH: + doAttackState(true, true); + break; + case ATTACK: doAttackState(true); break; @@ -860,7 +1019,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 +1084,10 @@ void MissileAIUpdate::xfer( Xfer *xfer ) if( version>= 6 ) xfer->xferBool( &m_isJammed ); + if( version >= 7 ) + { + xfer->xferReal( &m_randomPathDistLeft); + } } // end xfer // ------------------------------------------------------------------------------------------------ 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 0000000000..746dca4d41 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/AIUpdate/TeleporterAIUpdate.cpp @@ -0,0 +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 diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ArmorDamageScalarUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/ArmorDamageScalarUpdate.cpp index f542bf64fc..8f679b8c70 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/PhysicsUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/PhysicsUpdate.cpp index 371fee36fd..12cd6b7ce1 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; 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 0000000000..7b3fbd13a5 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/RadiusDecalBehavior.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: 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/Update/WeaponBonusUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/WeaponBonusUpdate.cpp index 4fd6799cd0..50d5d7fdfe 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 b0c158ae45..51b8c17778 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( ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/CostModifierUpgrade.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/CostModifierUpgrade.cpp index 438ba178d8..6c85ce1178 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 ) { + const CostModifierUpgradeModuleData* d = getCostModifierUpgradeModuleData(); + + // 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; - // remove the radar from the player + Bool stackWithAny = d->m_stackingType == SAME_TYPE; + Bool stackUniqueType = d->m_stackingType == OTHER_TYPE; + + // remove the bonus 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 diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/LocomotorSetUpgrade.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/LocomotorSetUpgrade.cpp index 3dd166bb8e..c5550ff49e 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); + } + } + } // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/ProductionTimeModifierUpgrade.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/ProductionTimeModifierUpgrade.cpp index ba30a45c20..1a7f4d6cd6 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); 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 0000000000..de3754d257 --- /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 diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/WeaponSetUpgrade.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Upgrade/WeaponSetUpgrade.cpp index a2a68a5ca6..f8ca1c5425 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));*/ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp index 7c0745fbb2..f2d8b51197 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. @@ -2834,7 +2839,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)); diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/WeaponSet.cpp index 5fe77f7e2d..8505f25a85 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/GameEngine/Source/GameLogic/System/Damage.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp index 6012a3361e..bb18ec31cb 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/System/Damage.cpp @@ -79,7 +79,22 @@ const char* DamageTypeFlags::s_bitNameList[] = "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 }; diff --git a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h index 59fcef32c1..3ca51084a4 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 9b09cd32a3..161e5cc501 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 fead45dcb7..3cb2a0cb79 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,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) + 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()); @@ -2734,6 +2743,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 +3079,9 @@ void W3DModelDraw::setModelState(const ModelConditionInfo* newState) } // set up shadows - if (m_renderObject && TheW3DShadowManager && tmplate->getShadowType() != SHADOW_NONE) + 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()); @@ -4329,6 +4342,9 @@ void W3DModelDraw::xfer( Xfer *xfer ) if( xfer->getXferMode() == XFER_LOAD && m_subObjectVec.empty() == FALSE ) updateSubObjects(); + // New stuff: + xfer->xferBool( &m_isFirstDrawModule ); + } // end xfer // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/DrawObject.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/DrawObject.cpp index d835153f7f..a30053fafc 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();