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
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ static PoolSizeRec PoolSizes[] =
{ "ScatterShotUpdate", 128, 64 },
{ "FireWeaponWhenDamagedBehavior", 32, 32 },
{ "FireWeaponWhenDeadBehavior", 128, 64 },
{ "FireWeaponOnKillBehavior", 128, 64 },
{ "CreateObjectOnKillBehavior", 128, 64 },
{ "DelayedUpgradeBehavior", 128, 64 },
{ "GenerateMinefieldBehavior", 32, 32 },
{ "HelicopterSlowDeathBehavior", 64, 32 },
Expand Down
6 changes: 6 additions & 0 deletions GeneralsMD/Code/GameEngine/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,9 @@ set(GAMEENGINE_SRC
Include/GameLogic/Module/FireWeaponAdvancedUpdate.h
Include/GameLogic/Module/FireWeaponWhenDamagedBehavior.h
Include/GameLogic/Module/FireWeaponWhenDeadBehavior.h
Include/GameLogic/Module/FireWeaponOnKillBehavior.h
Include/GameLogic/Module/CreateObjectOnKillBehavior.h
Include/GameLogic/Module/OnKillModule.h
Include/GameLogic/Module/DelayedUpgradeBehavior.h
Include/GameLogic/Module/FlammableUpdate.h
Include/GameLogic/Module/FlightDeckBehavior.h
Expand Down Expand Up @@ -898,6 +901,9 @@ set(GAMEENGINE_SRC
Source/GameLogic/Object/Behavior/FreeFallProjectileBehavior.cpp
Source/GameLogic/Object/Behavior/FireWeaponWhenDamagedBehavior.cpp
Source/GameLogic/Object/Behavior/FireWeaponWhenDeadBehavior.cpp
Source/GameLogic/Object/Behavior/FireWeaponOnKillBehavior.cpp
Source/GameLogic/Object/Behavior/CreateObjectOnKillBehavior.cpp
Source/GameLogic/Object/Behavior/OnKillModule.cpp
Source/GameLogic/Object/Behavior/DelayedUpgradeBehavior.cpp
Source/GameLogic/Object/Behavior/FlightDeckBehavior.cpp
Source/GameLogic/Object/Behavior/GenerateMinefieldBehavior.cpp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class CreateModuleInterface;
class DamageModuleInterface;
class DestroyModuleInterface;
class DieModuleInterface;
class OnKillModuleInterface;
class SpecialPowerModuleInterface;
class UpdateModuleInterface;
class UpgradeModuleInterface;
Expand Down Expand Up @@ -108,6 +109,7 @@ class BehaviorModuleInterface
virtual DamageModuleInterface* getDamage() = 0;
virtual DestroyModuleInterface* getDestroy() = 0;
virtual DieModuleInterface* getDie() = 0;
virtual OnKillModuleInterface* getOnKill() = 0;
virtual SpecialPowerModuleInterface* getSpecialPower() = 0;
virtual UpdateModuleInterface* getUpdate() = 0;
virtual UpgradeModuleInterface* getUpgrade() = 0;
Expand Down Expand Up @@ -165,6 +167,7 @@ class BehaviorModule : public ObjectModule, public BehaviorModuleInterface
virtual DamageModuleInterface* getDamage() { return nullptr; }
virtual DestroyModuleInterface* getDestroy() { return nullptr; }
virtual DieModuleInterface* getDie() { return nullptr; }
virtual OnKillModuleInterface* getOnKill() { return nullptr; }
virtual SpecialPowerModuleInterface* getSpecialPower() { return nullptr; }
virtual UpdateModuleInterface* getUpdate() { return nullptr; }
virtual UpgradeModuleInterface* getUpgrade() { return nullptr; }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
** 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/>.
*/

// FILE: CreateObjectOnKillBehavior.h /////////////////////////////////////////////////////////////
// Desc: Spawns an ObjectCreationList at the position of an object this object kills.
///////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

#include "GameLogic/Module/BehaviorModule.h"
#include "GameLogic/Module/UpgradeModule.h"
#include "GameLogic/Module/OnKillModule.h"

class ObjectCreationList;

//-------------------------------------------------------------------------------------------------
class CreateObjectOnKillBehaviorModuleData : public BehaviorModuleData
{
public:
UpgradeMuxData m_upgradeMuxData;
Bool m_initiallyActive;
KillMuxData m_killMuxData;
const ObjectCreationList* m_ocl; ///< spawn this OCL at the victim when we kill something
Bool m_createAtKillerLocation; ///< spawn at the killer's position instead of the victim's
Bool m_createObjectForVictim; ///< created object is owned by the victim instead of the killer

CreateObjectOnKillBehaviorModuleData()
{
m_initiallyActive = false;
m_ocl = nullptr;
m_createAtKillerLocation = false;
m_createObjectForVictim = false;
}

static void buildFieldParse(MultiIniFieldParse& p)
{
static const FieldParse dataFieldParse[] =
{
{ "StartsActive", INI::parseBool, nullptr, offsetof( CreateObjectOnKillBehaviorModuleData, m_initiallyActive ) },
{ "CreationList", INI::parseObjectCreationList, nullptr, offsetof( CreateObjectOnKillBehaviorModuleData, m_ocl ) },
{ "CreateAtKillerLocation", INI::parseBool, nullptr, offsetof( CreateObjectOnKillBehaviorModuleData, m_createAtKillerLocation ) },
{ "CreateObjectForVictim", INI::parseBool, nullptr, offsetof( CreateObjectOnKillBehaviorModuleData, m_createObjectForVictim ) },
{ 0, 0, 0, 0 }
};

BehaviorModuleData::buildFieldParse(p);
p.add(dataFieldParse);
p.add(UpgradeMuxData::getFieldParse(), offsetof( CreateObjectOnKillBehaviorModuleData, m_upgradeMuxData ));
p.add(KillMuxData::getFieldParse(), offsetof( CreateObjectOnKillBehaviorModuleData, m_killMuxData ));
}
};

//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
class CreateObjectOnKillBehavior : public BehaviorModule,
public UpgradeMux,
public OnKillModuleInterface
{

MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( CreateObjectOnKillBehavior, "CreateObjectOnKillBehavior" )
MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( CreateObjectOnKillBehavior, CreateObjectOnKillBehaviorModuleData )

public:

CreateObjectOnKillBehavior( Thing *thing, const ModuleData* moduleData );
// virtual destructor prototype provided by memory pool declaration

// module methods
static Int getInterfaceMask() { return BehaviorModule::getInterfaceMask() | (MODULEINTERFACE_UPGRADE); }

// BehaviorModule
virtual UpgradeModuleInterface* getUpgrade() { return this; }
virtual OnKillModuleInterface* getOnKill() { return this; }

// OnKillModuleInterface
virtual void onKilledObject( Object *victim, const DamageInfo *damageInfo );

protected:

virtual void upgradeImplementation()
{
// nothing!
}

virtual void getUpgradeActivationMasks(UpgradeMaskType& activation, UpgradeMaskType& conflicting) const
{
getCreateObjectOnKillBehaviorModuleData()->m_upgradeMuxData.getUpgradeActivationMasks(activation, conflicting);
}

virtual void performUpgradeFX()
{
getCreateObjectOnKillBehaviorModuleData()->m_upgradeMuxData.performUpgradeFX(getObject());
}

virtual void processUpgradeRemoval()
{
getCreateObjectOnKillBehaviorModuleData()->m_upgradeMuxData.muxDataProcessUpgradeRemoval(getObject());
}

virtual Bool requiresAllActivationUpgrades() const
{
return getCreateObjectOnKillBehaviorModuleData()->m_upgradeMuxData.m_requiresAllTriggers;
}

Bool isUpgradeActive() const { return isAlreadyUpgraded(); }

virtual Bool isSubObjectsUpgrade() { return false; }

private:

UnsignedInt m_lastTriggerFrame; ///< frame of the last trigger, for TriggerChance/CooldownTime

};
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
** 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/>.
*/

// FILE: FireWeaponOnKillBehavior.h ///////////////////////////////////////////////////////////////
// Desc: Fires a weapon at the position of an object this object kills.
///////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

#include "GameLogic/Module/BehaviorModule.h"
#include "GameLogic/Module/UpgradeModule.h"
#include "GameLogic/Module/OnKillModule.h"

class WeaponTemplate;

//-------------------------------------------------------------------------------------------------
class FireWeaponOnKillBehaviorModuleData : public BehaviorModuleData
{
public:
UpgradeMuxData m_upgradeMuxData;
Bool m_initiallyActive;
KillMuxData m_killMuxData;
const WeaponTemplate* m_killWeapon; ///< fire this weapon at the victim when we kill something

FireWeaponOnKillBehaviorModuleData()
{
m_initiallyActive = false;
m_killWeapon = nullptr;
}

static void buildFieldParse(MultiIniFieldParse& p)
{
static const FieldParse dataFieldParse[] =
{
{ "StartsActive", INI::parseBool, nullptr, offsetof( FireWeaponOnKillBehaviorModuleData, m_initiallyActive ) },
{ "KillWeapon", INI::parseWeaponTemplate, nullptr, offsetof( FireWeaponOnKillBehaviorModuleData, m_killWeapon ) },
{ 0, 0, 0, 0 }
};

BehaviorModuleData::buildFieldParse(p);
p.add(dataFieldParse);
p.add(UpgradeMuxData::getFieldParse(), offsetof( FireWeaponOnKillBehaviorModuleData, m_upgradeMuxData ));
p.add(KillMuxData::getFieldParse(), offsetof( FireWeaponOnKillBehaviorModuleData, m_killMuxData ));
}
};

//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
class FireWeaponOnKillBehavior : public BehaviorModule,
public UpgradeMux,
public OnKillModuleInterface
{

MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( FireWeaponOnKillBehavior, "FireWeaponOnKillBehavior" )
MAKE_STANDARD_MODULE_MACRO_WITH_MODULE_DATA( FireWeaponOnKillBehavior, FireWeaponOnKillBehaviorModuleData )

public:

FireWeaponOnKillBehavior( Thing *thing, const ModuleData* moduleData );
// virtual destructor prototype provided by memory pool declaration

// module methods
static Int getInterfaceMask() { return BehaviorModule::getInterfaceMask() | (MODULEINTERFACE_UPGRADE); }

// BehaviorModule
virtual UpgradeModuleInterface* getUpgrade() { return this; }
virtual OnKillModuleInterface* getOnKill() { return this; }

// OnKillModuleInterface
virtual void onKilledObject( Object *victim, const DamageInfo *damageInfo );

protected:

virtual void upgradeImplementation()
{
// nothing!
}

virtual void getUpgradeActivationMasks(UpgradeMaskType& activation, UpgradeMaskType& conflicting) const
{
getFireWeaponOnKillBehaviorModuleData()->m_upgradeMuxData.getUpgradeActivationMasks(activation, conflicting);
}

virtual void performUpgradeFX()
{
getFireWeaponOnKillBehaviorModuleData()->m_upgradeMuxData.performUpgradeFX(getObject());
}

virtual void processUpgradeRemoval()
{
getFireWeaponOnKillBehaviorModuleData()->m_upgradeMuxData.muxDataProcessUpgradeRemoval(getObject());
}

virtual Bool requiresAllActivationUpgrades() const
{
return getFireWeaponOnKillBehaviorModuleData()->m_upgradeMuxData.m_requiresAllTriggers;
}

Bool isUpgradeActive() const { return isAlreadyUpgraded(); }

virtual Bool isSubObjectsUpgrade() { return false; }

private:

UnsignedInt m_lastTriggerFrame; ///< frame of the last trigger, for TriggerChance/CooldownTime

};
72 changes: 72 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/GameLogic/Module/OnKillModule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
** 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/>.
*/

// FILE: OnKillModule.h ////////////////////////////////////////////////////////////////////////////
// Desc: Behavior interface + shared filter for modules that react when the owning object kills
// another object (the "killer" side, as opposed to DieModule which is the victim side).
///////////////////////////////////////////////////////////////////////////////////////////////////

#pragma once

#include "Common/Module.h"
#include "Common/KindOf.h"
#include "Common/ObjectStatusTypes.h"
#include "GameLogic/Damage.h"
#include "GameLogic/Module/BehaviorModule.h"

class Object;
struct FieldParse;

//-------------------------------------------------------------------------------------------------
/** Implemented by modules that want to be notified whenever their owning object kills another. */
//-------------------------------------------------------------------------------------------------
class OnKillModuleInterface
{
public:
virtual void onKilledObject( Object *victim, const DamageInfo *damageInfo ) = 0;
};

//-------------------------------------------------------------------------------------------------
/** Shared filter describing which kills a OnKill module should react to. Checks the KILLED object
(victim) by KindOf and ObjectStatus, its player relationship to the killer, and (when a DamageInfo
is available) the death type. Does NOT inherit from ModuleData (mirrors DieMuxData). */
//-------------------------------------------------------------------------------------------------
class KillMuxData
{
public:
KindOfMaskType m_victimRequiredKindOf; ///< victim must match these KindOf bits (ALL or ANY, per m_requiresAllKindOfs)
KindOfMaskType m_victimForbiddenKindOf; ///< victim must have none of these KindOf bits
Bool m_requiresAllKindOfs; ///< if true victim needs ALL required KindOfs, else ANY of them
ObjectStatusMaskType m_victimRequiredStatus; ///< victim must have all of these status bits
ObjectStatusMaskType m_victimForbiddenStatus; ///< victim must have none of these status bits
Int m_victimRelationship; ///< bitmask (WEAPON_AFFECTS_ALLIES/ENEMIES/NEUTRALS) of allowed killer->victim relationships
DeathTypeFlags m_deathTypes; ///< only these death types trigger (checked only when a DamageInfo is present)
DamageTypeFlags m_damageTypes; ///< only these damage types trigger (checked only when a DamageInfo is present)
Real m_triggerChance; ///< chance (0..1) that an applicable kill actually triggers the effect
UnsignedInt m_cooldownFrames; ///< min frames between triggers (0 = no cooldown)

KillMuxData();
static const FieldParse* getFieldParse();

Bool isKillApplicable( const Object *killer, const Object *victim, const DamageInfo *damageInfo ) const;

// Rolls TriggerChance and enforces CooldownTime. lastTriggerFrame is per-module-instance
// state (module data is shared per-template, so it cannot live here). On a successful trigger
// lastTriggerFrame is updated to the current frame; a failed chance roll does not start the cooldown.
Bool passesChanceAndCooldown( UnsignedInt& lastTriggerFrame ) const;
};
2 changes: 1 addition & 1 deletion GeneralsMD/Code/GameEngine/Include/GameLogic/Object.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ class Object : public Thing, public Snapshot
void doTempWeaponBonus( WeaponBonusConditionType status, UnsignedInt duration, TintStatus tintStatus = TINT_STATUS_INVALID );///< At this level, we just pass this on to our helper
void applyBuff(const BuffTemplate* buffTemp, UnsignedInt duration, Object* sourceObj);

void scoreTheKill( const Object *victim ); ///< I just killed this object.
void scoreTheKill( const Object *victim, const DamageInfo *damageInfo = nullptr ); ///< I just killed this object.
void onVeterancyLevelChanged( VeterancyLevel oldLevel, VeterancyLevel newLevel, Bool provideFeedback = TRUE ); ///< I just achieved this level right this moment
void createVeterancyLevelFX(VeterancyLevel oldLevel, VeterancyLevel newLevel);
ExperienceTracker* getExperienceTracker() {return m_experienceTracker;}
Expand Down
Loading
Loading