/* ** 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: SpecialAbilityUpdate.h ///////////////////////////////////////////////////////////////////////// // Author: Kris Morness, July 2002 // Desc: Handles processing of unit special abilities. /////////////////////////////////////////////////////////////////////////////////////////////////// #pragma once #ifndef __SPECIAL_ABILITY_UPDATE_H #define __SPECIAL_ABILITY_UPDATE_H // INCLUDES /////////////////////////////////////////////////////////////////////////////////////// #include "Common/AudioEventRTS.h" #include "Common/INI.h" #include "GameLogic/Module/SpecialPowerUpdateModule.h" #include "GameClient/ParticleSys.h" class DamageInfo; class SpecialPowerTemplate; class SpecialPowerModule; class FXList; enum SpecialPowerType; #define SPECIAL_ABILITY_HUGE_DISTANCE 10000000.0f //------------------------------------------------------------------------------------------------- class SpecialAbilityUpdateModuleData : public UpdateModuleData { public: AsciiString m_specialObjectName; AsciiString m_specialObjectAttachToBoneName; const SpecialPowerTemplate* m_specialPowerTemplate; ///< pointer to the special power template Real m_startAbilityRange; Real m_abilityAbortRange; Real m_packUnpackVariationFactor; Real m_fleeRangeAfterCompletion; Int m_effectValue; Int m_awardXPForTriggering; Int m_skillPointsForTriggering; UnsignedInt m_preparationFrames; UnsignedInt m_persistentPrepFrames; UnsignedInt m_effectDuration; UnsignedInt m_maxSpecialObjects; UnsignedInt m_packTime; UnsignedInt m_unpackTime; UnsignedInt m_preTriggerUnstealthFrames; Bool m_skipPackingWithNoTarget; Bool m_specialObjectsPersistent; Bool m_uniqueSpecialObjectTargets; Bool m_specialObjectsPersistWhenOwnerDies; Bool m_flipObjectAfterPacking; Bool m_flipObjectAfterUnpacking; Bool m_alwaysValidateSpecialObjects; Bool m_doCaptureFX; ///< the house color flashing while a building is getting captured Bool m_loseStealthOnTrigger; Bool m_approachRequiresLOS; Bool m_needToFaceTarget; Bool m_persistenceRequiresRecharge; const ParticleSystemTemplate *m_disableFXParticleSystem; AudioEventRTS m_packSound; AudioEventRTS m_unpackSound; AudioEventRTS m_prepSoundLoop; AudioEventRTS m_triggerSound; SpecialAbilityUpdateModuleData() { m_specialPowerTemplate = NULL; m_startAbilityRange = SPECIAL_ABILITY_HUGE_DISTANCE; m_abilityAbortRange = SPECIAL_ABILITY_HUGE_DISTANCE; m_preparationFrames = 0; m_persistentPrepFrames = 0; m_packTime = 0; m_unpackTime = 0; m_packUnpackVariationFactor = 0.0f; m_effectDuration = 0; m_maxSpecialObjects = 1; m_effectValue = 1; m_specialObjectsPersistent = FALSE; m_uniqueSpecialObjectTargets = FALSE; m_specialObjectsPersistWhenOwnerDies = FALSE; m_skipPackingWithNoTarget = FALSE; m_flipObjectAfterPacking = FALSE; m_flipObjectAfterUnpacking = FALSE; m_disableFXParticleSystem = NULL; m_fleeRangeAfterCompletion = 0.0f; m_doCaptureFX = FALSE; m_alwaysValidateSpecialObjects = FALSE; m_loseStealthOnTrigger = FALSE; m_awardXPForTriggering = 0; m_skillPointsForTriggering = -1; m_approachRequiresLOS = TRUE; m_preTriggerUnstealthFrames = 0; m_needToFaceTarget = TRUE; m_persistenceRequiresRecharge = FALSE; } static void buildFieldParse(MultiIniFieldParse& p) { UpdateModuleData::buildFieldParse(p); static const FieldParse dataFieldParse[] = { //Primary data values { "SpecialPowerTemplate", INI::parseSpecialPowerTemplate, NULL, offsetof( SpecialAbilityUpdateModuleData, m_specialPowerTemplate ) }, { "StartAbilityRange", INI::parseReal, NULL, offsetof( SpecialAbilityUpdateModuleData, m_startAbilityRange ) }, { "AbilityAbortRange", INI::parseReal, NULL, offsetof( SpecialAbilityUpdateModuleData, m_abilityAbortRange ) }, { "PreparationTime", INI::parseDurationUnsignedInt, NULL, offsetof( SpecialAbilityUpdateModuleData, m_preparationFrames ) }, { "PersistentPrepTime", INI::parseDurationUnsignedInt, NULL, offsetof( SpecialAbilityUpdateModuleData, m_persistentPrepFrames ) }, { "PackTime", INI::parseDurationUnsignedInt, NULL, offsetof( SpecialAbilityUpdateModuleData, m_packTime ) }, { "UnpackTime", INI::parseDurationUnsignedInt, NULL, offsetof( SpecialAbilityUpdateModuleData, m_unpackTime ) }, { "PreTriggerUnstealthTime", INI::parseDurationUnsignedInt, NULL, offsetof( SpecialAbilityUpdateModuleData, m_preTriggerUnstealthFrames ) }, { "SkipPackingWithNoTarget", INI::parseBool, NULL, offsetof( SpecialAbilityUpdateModuleData, m_skipPackingWithNoTarget ) }, { "PackUnpackVariationFactor", INI::parseReal, NULL, offsetof( SpecialAbilityUpdateModuleData, m_packUnpackVariationFactor ) }, //Secondary data values { "SpecialObject", INI::parseAsciiString, NULL, offsetof( SpecialAbilityUpdateModuleData, m_specialObjectName ) }, { "SpecialObjectAttachToBone", INI::parseAsciiString, NULL, offsetof( SpecialAbilityUpdateModuleData, m_specialObjectAttachToBoneName ) }, { "MaxSpecialObjects", INI::parseUnsignedInt, NULL, offsetof( SpecialAbilityUpdateModuleData, m_maxSpecialObjects ) }, { "SpecialObjectsPersistent", INI::parseBool, NULL, offsetof( SpecialAbilityUpdateModuleData, m_specialObjectsPersistent ) }, { "EffectDuration", INI::parseDurationUnsignedInt, NULL, offsetof( SpecialAbilityUpdateModuleData, m_effectDuration ) }, { "EffectValue", INI::parseInt, NULL, offsetof( SpecialAbilityUpdateModuleData, m_effectValue ) }, { "UniqueSpecialObjectTargets", INI::parseBool, NULL, offsetof( SpecialAbilityUpdateModuleData, m_uniqueSpecialObjectTargets ) }, { "SpecialObjectsPersistWhenOwnerDies", INI::parseBool, NULL, offsetof( SpecialAbilityUpdateModuleData, m_specialObjectsPersistWhenOwnerDies ) }, { "AlwaysValidateSpecialObjects", INI::parseBool, NULL, offsetof( SpecialAbilityUpdateModuleData, m_alwaysValidateSpecialObjects ) }, { "FlipOwnerAfterPacking", INI::parseBool, NULL, offsetof( SpecialAbilityUpdateModuleData, m_flipObjectAfterPacking ) }, { "FlipOwnerAfterUnpacking", INI::parseBool, NULL, offsetof( SpecialAbilityUpdateModuleData, m_flipObjectAfterUnpacking ) }, { "FleeRangeAfterCompletion", INI::parseReal, NULL, offsetof( SpecialAbilityUpdateModuleData, m_fleeRangeAfterCompletion ) }, { "DisableFXParticleSystem", INI::parseParticleSystemTemplate, NULL, offsetof( SpecialAbilityUpdateModuleData, m_disableFXParticleSystem ) }, { "DoCaptureFX", INI::parseBool, NULL, offsetof( SpecialAbilityUpdateModuleData, m_doCaptureFX ) }, { "PackSound", INI::parseAudioEventRTS, NULL, offsetof( SpecialAbilityUpdateModuleData, m_packSound ) }, { "UnpackSound", INI::parseAudioEventRTS, NULL, offsetof( SpecialAbilityUpdateModuleData, m_unpackSound ) }, { "PrepSoundLoop", INI::parseAudioEventRTS, NULL, offsetof( SpecialAbilityUpdateModuleData, m_prepSoundLoop ) }, { "TriggerSound", INI::parseAudioEventRTS, NULL, offsetof( SpecialAbilityUpdateModuleData, m_triggerSound ) }, { "LoseStealthOnTrigger", INI::parseBool, NULL, offsetof( SpecialAbilityUpdateModuleData, m_loseStealthOnTrigger ) }, { "AwardXPForTriggering", INI::parseInt, NULL, offsetof( SpecialAbilityUpdateModuleData, m_awardXPForTriggering ) }, { "SkillPointsForTriggering", INI::parseInt, NULL, offsetof( SpecialAbilityUpdateModuleData, m_skillPointsForTriggering ) }, { "ApproachRequiresLOS", INI::parseBool, NULL, offsetof( SpecialAbilityUpdateModuleData, m_approachRequiresLOS ) }, { "ApproachRequiresLOS", INI::parseBool, NULL, offsetof( SpecialAbilityUpdateModuleData, m_approachRequiresLOS ) }, { "NeedToFaceTarget", INI::parseBool, NULL, offsetof( SpecialAbilityUpdateModuleData, m_needToFaceTarget ) }, { "PersistenceRequiresRecharge",INI::parseBool, NULL, offsetof( SpecialAbilityUpdateModuleData, m_persistenceRequiresRecharge ) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); } }; //------------------------------------------------------------------------------------------------- class SpecialAbilityUpdate : public SpecialPowerUpdateModule { MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( SpecialAbilityUpdate, "SpecialAbilityUpdate" ) MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( SpecialAbilityUpdate, SpecialAbilityUpdateModuleData ) public: SpecialAbilityUpdate( Thing *thing, const ModuleData* moduleData ); // virtual destructor prototype provided by memory pool declaration // SpecialPowerUpdateInterface virtual Bool initiateIntentToDoSpecialPower(const SpecialPowerTemplate *specialPowerTemplate, const Object *targetObj, const Coord3D *targetPos, const Waypoint *way, UnsignedInt commandOptions ); virtual Bool isSpecialAbility() const { return true; } virtual Bool isSpecialPower() const { return false; } virtual Bool isActive() const { return m_active; } virtual Bool doesSpecialPowerHaveOverridableDestinationActive() const { return false; } //Is it active now? virtual Bool doesSpecialPowerHaveOverridableDestination() const { return false; } //Does it have it, even if it's not active? virtual void setSpecialPowerOverridableDestination( const Coord3D *loc ) {} virtual Bool isPowerCurrentlyInUse( const CommandButton *command = NULL ) const; // virtual Bool isBusy() const { return m_isBusy; } // UpdateModule virtual SpecialPowerUpdateInterface* getSpecialPowerUpdateInterface() { return this; } virtual CommandOption getCommandOption() const { return (CommandOption)0; } virtual UpdateSleepTime update(); // ??? ugh, public stuff that shouldn't be -- hell yeah! UnsignedInt getSpecialObjectCount() const; UnsignedInt getSpecialObjectMax() const; Object* findSpecialObjectWithProducerID( const Object *target ); SpecialPowerType getSpecialPowerType( void ) const; protected: void onExit( Bool cleanup ); const SpecialPowerTemplate* getTemplate() const; SpecialPowerModuleInterface* getMySPM(); //Wrapper function that determines if our object is close enough to the target object or position (auto knowledge)... only if necessary. //Returns true if it's not necessary, in which case, it can skip any approach. Bool isWithinStartAbilityRange() const; Bool isWithinAbilityAbortRange() const; //If not, we abort the ability Bool initLaser(Object* specialObject, Object* target); //Various steps of performing any special ability. Not all special abilities will use all of them. Bool approachTarget(); //Approaches the target before starting the special attack (if appropriate) void startPreparation(); //Begins the preparation of ability -- like firing off a hacker attack, or laser tracer, etc. Bool continuePreparation(); //Updates the preparation of ability -- like recalculating tracer lines. void triggerAbilityEffect(); //After the preparation time has elapsed, this part actually triggers the desired effect of the special ability. void finishAbility(); void validateSpecialObjects(); Object* createSpecialObject(); void killSpecialObjects(); Bool handlePackingProcessing(); void startPacking(Bool success); void startUnpacking(); Bool needToPack() const; Bool needToUnpack() const; Bool isPreparationComplete() const { return !m_prepFrames; } void endPreparation(); Bool isPersistentAbility() const { return getSpecialAbilityUpdateModuleData()->m_persistentPrepFrames > 0; } void resetPreparation() { m_prepFrames = getSpecialAbilityUpdateModuleData()->m_persistentPrepFrames; } Bool isFacing(); Bool needToFace() const; void startFacing(); // Lorenzen added this additional flag to support the NapalmBombDrop // It causes this update to force a recharge of the SPM between drops Bool getDoesPersistenceRequireRecharge() const { return getSpecialAbilityUpdateModuleData()->m_persistenceRequiresRecharge; } // void setBusy ( Bool is ) { m_isBusy = is; } // Bool m_isBusy; ///< whether I am between trigger and completion protected: UpdateSleepTime calcSleepTime() { return (m_active || getSpecialAbilityUpdateModuleData()->m_alwaysValidateSpecialObjects) ? UPDATE_SLEEP_NONE : UPDATE_SLEEP_FOREVER; } private: enum PackingState { STATE_NONE, STATE_PACKING, STATE_UNPACKING, STATE_PACKED, STATE_UNPACKED, }; AudioEventRTS m_prepSoundLoop; UnsignedInt m_prepFrames; UnsignedInt m_animFrames; //Used for packing/unpacking unit before or after using ability. ObjectID m_targetID; Coord3D m_targetPos; Int m_locationCount; std::list m_specialObjectIDList; //The list of special objects UnsignedInt m_specialObjectEntries; //The size of the list of member Objects Real m_captureFlashPhase; ///< used to track the accellerating flash of the capture FX PackingState m_packingState; Bool m_active; Bool m_noTargetCommand; Bool m_facingInitiated; Bool m_facingComplete; Bool m_withinStartAbilityRange; Bool m_doDisableFXParticles; // smaller targets cause this flag to toggle, making the particle effect more sparse }; #endif // _SPECIAL_POWER_UPDATE_H_