diff --git a/GeneralsMD/Code/GameEngine/CMakeLists.txt b/GeneralsMD/Code/GameEngine/CMakeLists.txt index 135c9bbda2..bbb20158f8 100644 --- a/GeneralsMD/Code/GameEngine/CMakeLists.txt +++ b/GeneralsMD/Code/GameEngine/CMakeLists.txt @@ -425,6 +425,7 @@ set(GAMEENGINE_SRC Include/GameLogic/Module/SabotageSupplyCenterCrateCollide.h Include/GameLogic/Module/SabotageSupplyDropzoneCrateCollide.h Include/GameLogic/Module/SalvageCrateCollide.h + Include/GameLogic/Module/StickyBombCrateCollide.h Include/GameLogic/Module/ScatterShotUpdate.h Include/GameLogic/Module/ShroudCrateCollide.h Include/GameLogic/Module/SlavedUpdate.h @@ -912,6 +913,7 @@ set(GAMEENGINE_SRC Source/GameLogic/Object/Collide/CrateCollide/SabotageSupplyCenterCrateCollide.cpp Source/GameLogic/Object/Collide/CrateCollide/SabotageSupplyDropzoneCrateCollide.cpp Source/GameLogic/Object/Collide/CrateCollide/SalvageCrateCollide.cpp + Source/GameLogic/Object/Collide/CrateCollide/StickyBombCrateCollide.cpp Source/GameLogic/Object/Collide/CrateCollide/ShroudCrateCollide.cpp Source/GameLogic/Object/Collide/CrateCollide/UnitCrateCollide.cpp Source/GameLogic/Object/Collide/CrateCollide/VeterancyCrateCollide.cpp diff --git a/GeneralsMD/Code/GameEngine/Include/Common/DrawModule.h b/GeneralsMD/Code/GameEngine/Include/Common/DrawModule.h index 4bd54b6099..82293a34d1 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/DrawModule.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/DrawModule.h @@ -199,6 +199,7 @@ class ObjectDrawInterface Note that you must call this AFTER setting the condition codes. */ virtual void setAnimationLoopDuration(UnsignedInt numFrames) = 0; + virtual bool isIgnoreAnimLoopDuration() const = 0; /** similar to the above, but assumes that the current state is a "ONCE", diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/CrateCollide.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/CrateCollide.h index e67494da67..526e6ae679 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/CrateCollide.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/CrateCollide.h @@ -48,8 +48,10 @@ class CrateCollideModuleData : public CollideModuleData KindOfMaskType m_kindof; ///< the kind(s) of units that can be collided with KindOfMaskType m_kindofnot; ///< the kind(s) of units that CANNOT be collided with Bool m_isForbidOwnerPlayer; ///< This crate cannot be picked up by the player of the dead thing that made it. + Bool m_isAllowNeutralPlayer; ///< This crate can be picked up by the neutral player Bool m_isBuildingPickup; ///< This crate can be picked up by a Building (bypassing AI requirement) Bool m_isHumanOnlyPickup; ///< Can this crate only be picked up by a human player? (Mission thing) + Bool m_isAllowPickAboveTerrain; ///< Can this crate only be picked when on the ground´? ScienceType m_pickupScience; ///< Can only be picked up by a unit whose player has this science FXList *m_executeFX; ///< FXList to play when activated diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombCrateCollide.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombCrateCollide.h new file mode 100644 index 0000000000..6f445d9c08 --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombCrateCollide.h @@ -0,0 +1,108 @@ +/* +** 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: StickyBombCrateCollide.h +// Author: Kris Morness, June 2003 +// Desc: A crate (actually a saboteur - mobile crate) that resets ALL command center general powers +// +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#pragma once + +#ifndef STICKY_BOMB_CRATE_COLLIDE_H_ +#define STICKY_BOMB_CRATE_COLLIDE_H_ + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "Common/Module.h" +#include "GameLogic/Module/CrateCollide.h" + +// FORWARD REFERENCES ///////////////////////////////////////////////////////////////////////////// +class Thing; + +//------------------------------------------------------------------------------------------------- +class StickyBombCrateCollideModuleData : public CrateCollideModuleData +{ +public: + + Bool m_needsTarget; ///< Need valid AI target (e.g. ordered to enter a building/unit, or projectile target) + Bool m_allowMultiCollide; ///< if we are a crate, allow spawning multiple sticky bombs, or just single use + Bool m_showInfiltrationEvent; ///< show an infiltration map event for the target player + AsciiString m_stickyBombObjectName; ///< the object to create + + StickyBombCrateCollideModuleData() + { + m_needsTarget = FALSE; + m_allowMultiCollide = FALSE; + m_showInfiltrationEvent = FALSE; + m_stickyBombObjectName = AsciiString::TheEmptyString; + } + + static void buildFieldParse(MultiIniFieldParse& p) + { + CrateCollideModuleData::buildFieldParse(p); + + static const FieldParse dataFieldParse[] = + { + { "NeedsTarget", INI::parseBool, NULL, offsetof(StickyBombCrateCollideModuleData, m_needsTarget) }, + { "AllowMultiCollide", INI::parseBool, NULL, offsetof(StickyBombCrateCollideModuleData, m_allowMultiCollide) }, + { "ShowInfiltrationEvent", INI::parseBool, NULL, offsetof(StickyBombCrateCollideModuleData, m_showInfiltrationEvent) }, + { "StickyBombObject", INI::parseAsciiString, NULL, offsetof(StickyBombCrateCollideModuleData, m_stickyBombObjectName) }, + { 0, 0, 0, 0 } + }; + p.add( dataFieldParse ); + } + +}; + +//------------------------------------------------------------------------------------------------- +class StickyBombCrateCollide : public CrateCollide +{ + + MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( StickyBombCrateCollide, "StickyBombCrateCollide" ) + MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( StickyBombCrateCollide, StickyBombCrateCollideModuleData ); + +public: + + StickyBombCrateCollide( Thing *thing, const ModuleData* moduleData ); + // virtual destructor prototype provided by memory pool declaration + +protected: + + /// This allows specific vetoes to certain types of crates and their data + virtual Bool isValidToExecute( const Object *other ) const; + + /// This is the game logic execution function that all real CrateCollides will implement + virtual Bool executeCrateBehavior( Object *other ); + + /// This would allow entering buildings? Lets disable it for now + // virtual Bool isSabotageBuildingCrateCollide() const { return TRUE; } + +private: + Bool m_hasCollided; + +}; + +#endif diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombUpdate.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombUpdate.h index e9bfa21941..e0f1a2b487 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombUpdate.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/Module/StickyBombUpdate.h @@ -34,6 +34,7 @@ #define __STICK_BOMB_UPDATE_H #include "GameLogic/Module/UpdateModule.h" +#include "GameClient/Anim2D.h" class WeaponTemplate; class FXList; @@ -47,11 +48,18 @@ class StickyBombUpdateModuleData : public UpdateModuleData WeaponTemplate* m_geometryBasedDamageWeaponTemplate; FXList* m_geometryBasedDamageFX; + AsciiString m_animBaseTemplate; + AsciiString m_animTimedTemplate; + Bool m_showTimer; + StickyBombUpdateModuleData() { m_offsetZ = 10.0f; m_geometryBasedDamageWeaponTemplate = NULL; m_geometryBasedDamageFX = NULL; + m_animBaseTemplate = AsciiString::TheEmptyString; + m_animTimedTemplate = AsciiString::TheEmptyString; + m_showTimer = TRUE; } static void buildFieldParse(MultiIniFieldParse& p) @@ -63,6 +71,9 @@ class StickyBombUpdateModuleData : public UpdateModuleData { "OffsetZ", INI::parseReal, NULL, offsetof( StickyBombUpdateModuleData, m_offsetZ ) }, { "GeometryBasedDamageWeapon",INI::parseWeaponTemplate, NULL, offsetof( StickyBombUpdateModuleData, m_geometryBasedDamageWeaponTemplate ) }, { "GeometryBasedDamageFX", INI::parseFXList, NULL, offsetof( StickyBombUpdateModuleData, m_geometryBasedDamageFX ) }, + { "Animation2DBase", INI::parseAsciiString, NULL, offsetof( StickyBombUpdateModuleData, m_animBaseTemplate) }, + { "Animation2DTimed", INI::parseAsciiString, NULL, offsetof( StickyBombUpdateModuleData, m_animTimedTemplate) }, + { "ShowTimer", INI::parseBool, NULL, offsetof( StickyBombUpdateModuleData, m_showTimer) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); @@ -87,16 +98,26 @@ class StickyBombUpdate : public UpdateModule void initStickyBomb( Object *object, const Object *bomber, const Coord3D *specificPos = NULL ); void detonate(); - Bool isTimedBomb() const { return m_dieFrame > 0; } + Bool isTimedBomb() const { return (m_dieFrame > 0) && getStickyBombUpdateModuleData()->m_showTimer; } UnsignedInt getDetonationFrame() const { return m_dieFrame; } Object* getTargetObject() const; void setTargetObject( Object *obj ); + //AsciiString getAnimBaseTemplate() { return getStickyBombUpdateModuleData()->m_animBaseTemplate; } + //AsciiString getAnimTimedTemplate() { return getStickyBombUpdateModuleData()->m_animTimedTemplate; } + + Anim2DTemplate* getAnimBaseTemplate(); + Anim2DTemplate* getAnimTimedTemplate(); + private: ObjectID m_targetID; UnsignedInt m_dieFrame; UnsignedInt m_nextPingFrame; + + Anim2DTemplate* m_animBaseTemplate; + Anim2DTemplate* m_animTimedTemplate; + }; #endif // __STICK_BOMB_UPDATE_H diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp index a4387c1a60..87e032a3c7 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/MemoryInit.cpp @@ -294,6 +294,7 @@ static PoolSizeRec sizes[] = { "RailedTransportContain", 16, 16 }, { "RailroadBehavior", 16, 16 }, { "SalvageCrateCollide", 32, 32 }, + { "StickyBombCrateCollide", 32, 32 }, { "ShroudCrateCollide", 32, 32 }, { "SlavedUpdate", 64, 32 }, { "SlowDeathBehavior", 1400, 256 }, diff --git a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp index 5b3850300d..49913b613c 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/Thing/ModuleFactory.cpp @@ -253,6 +253,7 @@ #include "GameLogic/Module/SabotageSupplyCenterCrateCollide.h" #include "GameLogic/Module/SabotageSupplyDropzoneCrateCollide.h" #include "GameLogic/Module/SalvageCrateCollide.h" +#include "GameLogic/Module/StickyBombCrateCollide.h" #include "GameLogic/Module/ShroudCrateCollide.h" #include "GameLogic/Module/UnitCrateCollide.h" #include "GameLogic/Module/VeterancyCrateCollide.h" @@ -545,6 +546,7 @@ void ModuleFactory::init( void ) addModule( SabotageSupplyCenterCrateCollide ); addModule( SabotageSupplyDropzoneCrateCollide ); addModule( SalvageCrateCollide ); + addModule( StickyBombCrateCollide ); // body modules addModule( InactiveBody ); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp index 2e7b9c085d..6312386e2a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp @@ -726,7 +726,7 @@ void Drawable::setAnimationLoopDuration(UnsignedInt numFrames) for (DrawModule** dm = getDrawModules(); *dm; ++dm) { ObjectDrawInterface* di = (*dm)->getObjectDrawInterface(); - if (di) + if (di && !di->isIgnoreAnimLoopDuration()) di->setAnimationLoopDuration(numFrames); } } @@ -3679,6 +3679,7 @@ void Drawable::drawBombed(const IRegion2D* healthBarRegion) StickyBombUpdate *update = (StickyBombUpdate*)obj->findUpdateModule( key_StickyBombUpdate ); if( update ) { + //This case is tricky. The object that is bombed doesn't know it... but the bomb itself does. //So what we do is get it's target, then determine if the target has the icon or not. Object *target = update->getTargetObject(); @@ -3689,8 +3690,20 @@ void Drawable::drawBombed(const IRegion2D* healthBarRegion) //Timed bomb if( !getIconInfo()->m_icon[ ICON_BOMB_TIMED ] ) { - getIconInfo()->m_icon[ ICON_BOMB_REMOTE ] = newInstance(Anim2D)( s_animationTemplates[ ICON_BOMB_REMOTE ], TheAnim2DCollection ); - getIconInfo()->m_icon[ ICON_BOMB_TIMED ] = newInstance(Anim2D)( s_animationTemplates[ ICON_BOMB_TIMED ], TheAnim2DCollection ); + Anim2DTemplate* templ = update->getAnimBaseTemplate(); + + if (templ == NULL) // Default icon + templ = s_animationTemplates[ICON_BOMB_REMOTE]; + + getIconInfo()->m_icon[ICON_BOMB_REMOTE] = newInstance(Anim2D)(templ, TheAnim2DCollection); + + templ = update->getAnimTimedTemplate(); + + if (templ == NULL) // Default icon + templ = s_animationTemplates[ICON_BOMB_TIMED]; + + getIconInfo()->m_icon[ICON_BOMB_TIMED] = newInstance(Anim2D)(templ, TheAnim2DCollection); + //Because this is a counter icon that ranges from 0-60 seconds, we need to calculate which frame to //start the animation from. Because timers are second based -- 1000 ms equal 1 frame. So we simply @@ -3752,7 +3765,13 @@ void Drawable::drawBombed(const IRegion2D* healthBarRegion) //Timed bomb if( !getIconInfo()->m_icon[ ICON_BOMB_REMOTE ] ) { - getIconInfo()->m_icon[ ICON_BOMB_REMOTE ] = newInstance(Anim2D)( s_animationTemplates[ ICON_BOMB_REMOTE ], TheAnim2DCollection ); + Anim2DTemplate* templ = update->getAnimBaseTemplate(); + + if (templ == NULL) // Default icon + templ = s_animationTemplates[ICON_BOMB_REMOTE]; + + getIconInfo()->m_icon[ICON_BOMB_REMOTE] = newInstance(Anim2D)(templ, TheAnim2DCollection); + } if( getIconInfo()->m_icon[ ICON_BOMB_REMOTE ] ) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp index 525734ea8c..e77b8ef3c1 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/CrateCollide.cpp @@ -48,11 +48,13 @@ CrateCollideModuleData::CrateCollideModuleData() { m_isForbidOwnerPlayer = FALSE; + m_isAllowNeutralPlayer = FALSE; m_executeAnimationDisplayTimeInSeconds = 0.0f; m_executeAnimationZRisePerSecond = 0.0f; m_executeAnimationFades = TRUE; m_isBuildingPickup = FALSE; m_isHumanOnlyPickup = FALSE; + m_isAllowPickAboveTerrain = FALSE; m_executeFX = NULL; m_pickupScience = SCIENCE_INVALID; @@ -75,8 +77,10 @@ void CrateCollideModuleData::buildFieldParse(MultiIniFieldParse& p) { "RequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( CrateCollideModuleData, m_kindof ) }, { "ForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( CrateCollideModuleData, m_kindofnot ) }, { "ForbidOwnerPlayer", INI::parseBool, NULL, offsetof( CrateCollideModuleData, m_isForbidOwnerPlayer ) }, + { "AllowNeutralPlayer", INI::parseBool, NULL, offsetof( CrateCollideModuleData, m_isAllowNeutralPlayer ) }, { "BuildingPickup", INI::parseBool, NULL, offsetof( CrateCollideModuleData, m_isBuildingPickup ) }, { "HumanOnly", INI::parseBool, NULL, offsetof( CrateCollideModuleData, m_isHumanOnlyPickup ) }, + { "AllowPickAboveTerrain", INI::parseBool, NULL, offsetof( CrateCollideModuleData, m_isAllowPickAboveTerrain ) }, { "PickupScience", INI::parseScience, NULL, offsetof( CrateCollideModuleData, m_pickupScience ) }, { "ExecuteFX", INI::parseFXList, NULL, offsetof( CrateCollideModuleData, m_executeFX ) }, { "ExecuteAnimation", INI::parseAsciiString, NULL, offsetof( CrateCollideModuleData, m_executionAnimationTemplate ) }, @@ -149,11 +153,12 @@ Bool CrateCollide::isValidToExecute( const Object *other ) const if( other == NULL ) return FALSE; + const CrateCollideModuleData* md = getCrateCollideModuleData(); + //Nothing Neutral can pick up any type of crate - if( other->isNeutralControlled() ) + if(other->isNeutralControlled() && !md->m_isAllowNeutralPlayer) return FALSE; - const CrateCollideModuleData* md = getCrateCollideModuleData(); Bool validBuildingAttempt = md->m_isBuildingPickup && other->isKindOf( KINDOF_STRUCTURE ); // Must be a "Unit" type thing. Real Game Object, not just Object @@ -168,7 +173,7 @@ Bool CrateCollide::isValidToExecute( const Object *other ) const return FALSE; // crates cannot be claimed while in the air, except by buildings - if( getObject()->isAboveTerrain() && !validBuildingAttempt ) + if( getObject()->isAboveTerrain() && !(validBuildingAttempt || md->m_isAllowPickAboveTerrain)) return FALSE; if( md->m_isForbidOwnerPlayer && (getObject()->getControllingPlayer() == other->getControllingPlayer()) ) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/StickyBombCrateCollide.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/StickyBombCrateCollide.cpp new file mode 100644 index 0000000000..e377b4aeef --- /dev/null +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Collide/CrateCollide/StickyBombCrateCollide.cpp @@ -0,0 +1,206 @@ +/* +** 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: StickyBombCrateCollide.cpp +// Author: Kris Morness, June 2003 +// Desc: A crate (actually a saboteur - mobile crate) that resets the timer on the target supply dropzone. +// +/////////////////////////////////////////////////////////////////////////////////////////////////// + + + +// INCLUDES /////////////////////////////////////////////////////////////////////////////////////// +#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine + +// #include "Common/GameAudio.h" +// #include "Common/MiscAudio.h" +#include "Common/Player.h" +// #include "Common/PlayerList.h" +#include "Common/Radar.h" +// #include "Common/SpecialPower.h" +#include "Common/ThingFactory.h" +#include "Common/ThingTemplate.h" +#include "Common/Xfer.h" + +// #include "GameClient/Drawable.h" +#include "GameClient/Eva.h" +// #include "GameClient/InGameUI.h" // useful for printing quick debug strings when we need to + +//#include "GameLogic/ExperienceTracker.h" +#include "GameLogic/Object.h" +//#include "GameLogic/PartitionManager.h" +//#include "GameLogic/ScriptEngine.h" + +#include "GameLogic/Module/AIUpdate.h" +//#include "GameLogic/Module/ContainModule.h" +//#include "GameLogic/Module/DozerAIUpdate.h" +//#include "GameLogic/Module/HijackerUpdate.h" +//#include "GameLogic/Module/OCLUpdate.h" +#include "GameLogic/Module/StickyBombCrateCollide.h" +#include "GameLogic/Module/StickyBombUpdate.h" +//#include "GameLogic/Module/SpecialPowerModule.h" + + + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +StickyBombCrateCollide::StickyBombCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData ) +{ +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +StickyBombCrateCollide::~StickyBombCrateCollide( void ) +{ +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool StickyBombCrateCollide::isValidToExecute( const Object *other ) const +{ + if( !CrateCollide::isValidToExecute(other) ) + { + if (other != NULL) + DEBUG_LOG(("StickyBombCrateCollide::isValidToExecute >>> target '%s' Not Valid.", other->getTemplate()->getName().str())); + else + DEBUG_LOG(("StickyBombCrateCollide::isValidToExecute >>> collide with ground!")); + //Extend functionality. + return FALSE; + } + + // Do we need anything here? + DEBUG_LOG(("StickyBombCrateCollide::isValidToExecute >>> target '%s' Valid!", other->getTemplate()->getName().str())); + return TRUE; +} + +//------------------------------------------------------------------------------------------------- +//------------------------------------------------------------------------------------------------- +Bool StickyBombCrateCollide::executeCrateBehavior( Object *other ) +{ + + DEBUG_LOG(("StickyBombCrateCollide::executeCrateBehavior 0")); + const StickyBombCrateCollideModuleData* md = getStickyBombCrateCollideModuleData(); + + if (m_hasCollided && !md->m_allowMultiCollide) + return FALSE; + + if (md->m_needsTarget) { + //Check to make sure that the other object is also the goal object in the AIUpdateInterface + //in order to prevent an unintentional conversion simply by having the terrorist walk too close + //to it. This should also be valid for Projectiles hitting their intended targets + //Assume ai is valid because CrateCollide::isValidToExecute(other) checks it. + + Object* obj = getObject(); + AIUpdateInterface* ai = obj->getAIUpdateInterface(); + if (ai) { + if (ai->getGoalObject() != other) + { + return false; + } + } + else { + ProjectileUpdateInterface* pi = obj->getProjectileUpdateInterface(); + if (pi && pi->getTargetObject() != other) + { + return false; + } + } + + } + + if (md->m_showInfiltrationEvent) + TheRadar->tryInfiltrationEvent( other ); + + //Get the template of the special object + const ThingTemplate* thingTemplate = TheThingFactory->findTemplate(md->m_stickyBombObjectName); + if (thingTemplate) + { + //Create a new special object + Object* stickyBombObject = TheThingFactory->newObject(thingTemplate, getObject()->getTeam()); + if (stickyBombObject) + { + static NameKeyType key_StickyBombUpdate = NAMEKEY("StickyBombUpdate"); + StickyBombUpdate* update = (StickyBombUpdate*)stickyBombObject->findUpdateModule(key_StickyBombUpdate); + if (!update) + { + DEBUG_ASSERTCRASH(0, + ("Unit '%s' attempted to place %s on %s but the bomb requires a StickyBombUpdate module.", + getObject()->getTemplate()->getName().str(), + stickyBombObject->getTemplate()->getName().str(), + other->getTemplate()->getName().str())); + return TRUE; + } + //Setting the producer ID allows the sticky bomb update module to initialize + //and setup timers, etc. + update->initStickyBomb(other, getObject()); + } + } + + m_hasCollided = TRUE; + return TRUE; +} + +// ------------------------------------------------------------------------------------------------ +/** CRC */ +// ------------------------------------------------------------------------------------------------ +void StickyBombCrateCollide::crc( Xfer *xfer ) +{ + + // extend base class + CrateCollide::crc( xfer ); + +} // end crc + +// ------------------------------------------------------------------------------------------------ +/** Xfer method + * Version Info: + * 1: Initial version */ +// ------------------------------------------------------------------------------------------------ +void StickyBombCrateCollide::xfer( Xfer *xfer ) +{ + + // version + XferVersion currentVersion = 1; + XferVersion version = currentVersion; + xfer->xferVersion( &version, currentVersion ); + + // extend base class + CrateCollide::xfer( xfer ); + + xfer->xferBool(&m_hasCollided); + +} // end xfer + +// ------------------------------------------------------------------------------------------------ +/** Load post process */ +// ------------------------------------------------------------------------------------------------ +void StickyBombCrateCollide::loadPostProcess( void ) +{ + + // extend base class + CrateCollide::loadPostProcess(); + +} // end loadPostProcess diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StickyBombUpdate.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StickyBombUpdate.cpp index e3fe0ab8c7..8f36302999 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StickyBombUpdate.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Update/StickyBombUpdate.cpp @@ -285,6 +285,46 @@ void StickyBombUpdate::detonate() getObject()->kill();// Most things just fire weapons in their death modules } + + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +Anim2DTemplate* StickyBombUpdate::getAnimBaseTemplate() { + + if (getStickyBombUpdateModuleData()->m_animBaseTemplate.isNotEmpty()) { + //DEBUG_LOG(("SBU::getAnimBaseTemplate - isNotEmpty")); + if (m_animBaseTemplate == NULL) { + m_animBaseTemplate = TheAnim2DCollection->findTemplate(getStickyBombUpdateModuleData()->m_animBaseTemplate); + } + //DEBUG_LOG(("SBU::getAnimBaseTemplate - isNull = %d", m_animBaseTemplate == NULL)); + return m_animBaseTemplate; + } + + //DEBUG_LOG(("SBU::getAnimBaseTemplate - is Empty ?!")); + + return NULL; +} +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ + +Anim2DTemplate* StickyBombUpdate::getAnimTimedTemplate() { + if (getStickyBombUpdateModuleData()->m_animTimedTemplate.isNotEmpty()) { + //DEBUG_LOG(("SBU::getAnimTimedTemplate - isNotEmpty")); + if (m_animTimedTemplate == NULL) { + m_animTimedTemplate = TheAnim2DCollection->findTemplate(getStickyBombUpdateModuleData()->m_animTimedTemplate); + } + //DEBUG_LOG(("SBU::getAnimTimedTemplate - isNull = %d", m_animTimedTemplate == NULL)); + return m_animTimedTemplate; + } + + //DEBUG_LOG(("SBU::getAnimTimedTemplate - is Empty ?!")); + + return NULL; + +} +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ + // ------------------------------------------------------------------------------------------------ /** CRC */ // ------------------------------------------------------------------------------------------------ diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp index 9d30ca063e..fa7395ac61 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/Weapon.cpp @@ -2866,6 +2866,8 @@ Bool Weapon::privateFireWeapon( targetPos.z = TheTerrainLogic->getGroundHeight(targetPos.x, targetPos.y); + // Note AW: We have to ignore Ranges when using ScatterTargets, or else the weapon can fail in the next stage + ignoreRanges = TRUE; m_template->fireWeaponTemplate(sourceObj, m_wslot, m_curBarrel, victimObj, &targetPos, bonus, isProjectileDetonation, ignoreRanges, this, projectileID, inflictDamage ); } else diff --git a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h index 0e2ff1dc31..2e8d3dbf1a 100644 --- a/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h +++ b/GeneralsMD/Code/GameEngineDevice/Include/W3DDevice/GameClient/Module/W3DModelDraw.h @@ -311,6 +311,8 @@ class W3DModelDrawModuleData : public ModuleData Bool m_receivesDynamicLights; ///< just like it sounds... it sets a property of Drawable, actually + Bool m_ignoreAnimScaling; ///< ignore external scaling of animation speed, e.g. for PreAttack. + W3DModelDrawModuleData(); ~W3DModelDrawModuleData(); @@ -396,6 +398,7 @@ class W3DModelDraw : public DrawModule, public ObjectDrawInterface Note that you must call this AFTER setting the condition codes. */ virtual void setAnimationLoopDuration(UnsignedInt numFrames); + virtual bool isIgnoreAnimLoopDuration() const { return getW3DModelDrawModuleData()->m_ignoreAnimScaling; } /** similar to the above, but assumes that the current state is a "ONCE", diff --git a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp index efb6e2f747..8af0024574 100644 --- a/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp +++ b/GeneralsMD/Code/GameEngineDevice/Source/W3DDevice/GameClient/Drawable/Draw/W3DModelDraw.cpp @@ -875,7 +875,8 @@ void ModelConditionInfo::validateTurretInfo() const { if (findPristineBone(tur.m_turretAngleNameKey, &tur.m_turretAngleBone) == NULL) { - DEBUG_CRASH(("*** ASSET ERROR: TurretBone %s not found! (%s)",KEYNAME(tur.m_turretAngleNameKey).str(),m_modelName.str())); + //DEBUG_CRASH(("*** ASSET ERROR: TurretBone %s not found! (%s)",KEYNAME(tur.m_turretAngleNameKey).str(),m_modelName.str())); + DEBUG_LOG(("*** ASSET ERROR: TurretBone %s not found! (%s)",KEYNAME(tur.m_turretAngleNameKey).str(),m_modelName.str())); tur.m_turretAngleBone = 0; } } @@ -888,7 +889,8 @@ void ModelConditionInfo::validateTurretInfo() const { if (findPristineBone(tur.m_turretPitchNameKey, &tur.m_turretPitchBone) == NULL) { - DEBUG_CRASH(("*** ASSET ERROR: TurretBone %s not found! (%s)",KEYNAME(tur.m_turretPitchNameKey).str(),m_modelName.str())); + //DEBUG_CRASH(("*** ASSET ERROR: TurretBone %s not found! (%s)",KEYNAME(tur.m_turretPitchNameKey).str(),m_modelName.str())); + DEBUG_LOG(("*** ASSET ERROR: TurretBone %s not found! (%s)",KEYNAME(tur.m_turretPitchNameKey).str(),m_modelName.str())); tur.m_turretPitchBone = 0; } } @@ -898,6 +900,7 @@ void ModelConditionInfo::validateTurretInfo() const } } + if (isValidTimeToCalcLogicStuff()) { m_validStuff |= TURRETS_VALID; @@ -1092,10 +1095,10 @@ void W3DModelDrawModuleData::validateStuffForTimeAndWeather(const Drawable* draw for (c_it = m_conditionStates.begin(); c_it != m_conditionStates.end(); ++c_it) { - if (!a && c_it->m_transitionKey == src && c_it->matchesMode(night, snowy)) + if (!a && c_it->m_transitionKey == src && (c_it->matchesMode(night, snowy) || c_it->matchesMode(false, false))) a = true; - if (!b && c_it->m_transitionKey == dst && c_it->matchesMode(night, snowy)) + if (!b && c_it->m_transitionKey == dst && (c_it->matchesMode(night, snowy) || c_it->matchesMode(false, false))) b = true; } @@ -1214,6 +1217,7 @@ void W3DModelDrawModuleData::buildFieldParse(MultiIniFieldParse& p) { "AttachToBoneInAnotherModule", parseAsciiStringLC, NULL, offsetof(W3DModelDrawModuleData, m_attachToDrawableBone) }, { "IgnoreConditionStates", ModelConditionFlags::parseFromINI, NULL, offsetof(W3DModelDrawModuleData, m_ignoreConditionStates) }, { "ReceivesDynamicLights", INI::parseBool, NULL, offsetof(W3DModelDrawModuleData, m_receivesDynamicLights) }, + { "IgnoreAnimationSpeedScaling", INI::parseBool, NULL, offsetof(W3DModelDrawModuleData, m_ignoreAnimScaling) }, { 0, 0, 0, 0 } }; p.add(dataFieldParse); @@ -2046,6 +2050,7 @@ void W3DModelDraw::adjustTransformMtx(Matrix3D& mtx) const //------------------------------------------------------------------------------------------------- void W3DModelDraw::doDrawModule(const Matrix3D* transformMtx) { + // update whether or not we should be animating. setPauseAnimation( !getDrawable()->getShouldAnimate(getW3DModelDrawModuleData()->m_animationsRequirePower) ); @@ -2421,6 +2426,7 @@ void W3DModelDraw::stopClientParticleSystems() */ void W3DModelDraw::handleClientTurretPositioning() { + if (!m_curState || !(m_curState->m_validStuff & ModelConditionInfo::TURRETS_VALID)) return; @@ -2920,6 +2926,7 @@ static Bool turretNamesDiffer(const ModelConditionInfo* a, const ModelConditionI //------------------------------------------------------------------------------------------------- void W3DModelDraw::setModelState(const ModelConditionInfo* newState) { + DEBUG_ASSERTCRASH(newState, ("invalid state in W3DModelDraw::setModelState")); #ifdef DEBUG_OBJECT_ID_EXISTS