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