Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions GeneralsMD/Code/GameEngine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions GeneralsMD/Code/GameEngine/Include/Common/DrawModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

////////////////////////////////////////////////////////////////////////////////
// //
// (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
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#define __STICK_BOMB_UPDATE_H

#include "GameLogic/Module/UpdateModule.h"
#include "GameClient/Anim2D.h"

class WeaponTemplate;
class FXList;
Expand All @@ -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)
Expand All @@ -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);
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -545,6 +546,7 @@ void ModuleFactory::init( void )
addModule( SabotageSupplyCenterCrateCollide );
addModule( SabotageSupplyDropzoneCrateCollide );
addModule( SalvageCrateCollide );
addModule( StickyBombCrateCollide );

// body modules
addModule( InactiveBody );
Expand Down
27 changes: 23 additions & 4 deletions GeneralsMD/Code/GameEngine/Source/GameClient/Drawable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down Expand Up @@ -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();
Expand All @@ -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
Expand Down Expand Up @@ -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 ] )
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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 ) },
Expand Down Expand Up @@ -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
Expand All @@ -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()) )
Expand Down
Loading
Loading