Initial commit of Command & Conquer Generals and Command & Conquer Generals Zero Hour source code.

This commit is contained in:
LFeenanEA
2025-02-27 17:34:39 +00:00
parent 2e338c00cb
commit 3d0ee53a05
6072 changed files with 2283311 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,819 @@
/*
** 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. //
// //
////////////////////////////////////////////////////////////////////////////////
// AIDock.cpp
// Implementation of docking behavior
// Author: Michael S. Booth, February 2002
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Module.h"
#include "Common/Player.h"
#include "GameLogic/Object.h"
#include "GameLogic/AIDock.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/SupplyTruckAIUpdate.h"
#include "GameLogic/Module/UpdateModule.h"
//----------------------------------------------------------------------------------------------------------
/**
* Create an AI state machine. Define all of the states the machine
* can possibly be in, and set the initial (default) state.
*/
AIDockMachine::AIDockMachine( Object *obj ) : StateMachine( obj, "AIDockMachine" )
{
static const StateConditionInfo waitForClearanceConditions[] =
{
StateConditionInfo(ableToAdvance, AI_DOCK_ADVANCE_POSITION, NULL),
StateConditionInfo(NULL, NULL, NULL) // keep last
};
// order matters: first state is the default state.
defineState( AI_DOCK_APPROACH, newInstance(AIDockApproachState)( this ), AI_DOCK_WAIT_FOR_CLEARANCE, EXIT_MACHINE_WITH_FAILURE );
defineState( AI_DOCK_WAIT_FOR_CLEARANCE, newInstance(AIDockWaitForClearanceState)( this ), AI_DOCK_MOVE_TO_ENTRY, EXIT_MACHINE_WITH_FAILURE, waitForClearanceConditions );
defineState( AI_DOCK_ADVANCE_POSITION, newInstance(AIDockAdvancePositionState)( this ), AI_DOCK_WAIT_FOR_CLEARANCE, EXIT_MACHINE_WITH_FAILURE );
defineState( AI_DOCK_MOVE_TO_ENTRY, newInstance(AIDockMoveToEntryState)( this ), AI_DOCK_MOVE_TO_DOCK, AI_DOCK_MOVE_TO_EXIT );
defineState( AI_DOCK_MOVE_TO_DOCK, newInstance(AIDockMoveToDockState)( this ), AI_DOCK_PROCESS_DOCK, AI_DOCK_MOVE_TO_EXIT );
defineState( AI_DOCK_PROCESS_DOCK, newInstance(AIDockProcessDockState)( this ), AI_DOCK_MOVE_TO_EXIT, AI_DOCK_MOVE_TO_EXIT );
defineState( AI_DOCK_MOVE_TO_EXIT, newInstance(AIDockMoveToExitState)( this ), AI_DOCK_MOVE_TO_RALLY, EXIT_MACHINE_WITH_FAILURE );
defineState( AI_DOCK_MOVE_TO_RALLY, newInstance(AIDockMoveToRallyState)( this ), EXIT_MACHINE_WITH_SUCCESS, EXIT_MACHINE_WITH_FAILURE );
m_approachPosition = -1;
}
AIDockMachine::~AIDockMachine()
{
}
//-----------------------------------------------------------------------------
void AIDockMachine::halt()
{
Object *goalObject = getGoalObject();
// sanity
if( goalObject != NULL )
{
// get dock update interface
DockUpdateInterface *dock = goalObject->getDockUpdateInterface();
// We need to say goodbye, or we will leave our spot taken forever.
if( dock != NULL )
dock->cancelDock( getOwner() );
}
StateMachine::halt();
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AIDockMachine::crc( Xfer *xfer )
{
StateMachine::crc(xfer);
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AIDockMachine::xfer( Xfer *xfer )
{
XferVersion cv = 1;
XferVersion v = cv;
xfer->xferVersion( &v, cv );
StateMachine::xfer(xfer);
xfer->xferInt(&m_approachPosition);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AIDockMachine::loadPostProcess( void )
{
StateMachine::loadPostProcess();
} // end loadPostProcess
// State transition conditions ----------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/* static */ Bool AIDockMachine::ableToAdvance( State *thisState, void* userData )
{
Object *goalObject = thisState->getMachineGoalObject();
AIDockMachine *myMachine = (AIDockMachine *)thisState->getMachine();
if( goalObject == NULL )
return FALSE;
DockUpdateInterface *dock = goalObject->getDockUpdateInterface();
// if we have nothing to dock with, fail
if( dock == NULL )
return FALSE;
// if the dock says we can advance, then sidetrack to the scoot forward state
if( dock->isClearToAdvance( thisState->getMachineOwner(), myMachine->m_approachPosition ) )
return TRUE;
// continue to wait
return FALSE;
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AIDockApproachState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 2;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
if (version>=2) {
AIInternalMoveToState::xfer(xfer);
}
} // end xfer
//----------------------------------------------------------------------------------------------
/**
* Approach our waiting spot next to the dock.
*/
StateReturnType AIDockApproachState::onEnter( void )
{
Object *goalObject = getMachineGoalObject();
// sanity
if( goalObject == NULL )
return STATE_FAILURE;
// get dock update interface
DockUpdateInterface *dock = goalObject->getDockUpdateInterface();
// if we have nothing to dock with, fail
if (dock == NULL)
return STATE_FAILURE;
// fail if the dock is closed
if( dock->isDockOpen() == FALSE )
{
dock->cancelDock( getMachineOwner() );
return STATE_FAILURE;
}
// get a good place to wait from the dock
Bool reserved = dock->reserveApproachPosition( getMachineOwner(), &m_goalPosition, &(( (AIDockMachine*)getMachine() )->m_approachPosition) );
if( reserved == FALSE )
{
// dock is full
return STATE_FAILURE;
}
AIUpdateInterface *ai = getMachineOwner()->getAIUpdateInterface();
if (ai) {
ai->ignoreObstacle( NULL );
}
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::onEnter();
}
//----------------------------------------------------------------------------------------------
StateReturnType AIDockApproachState::update( void )
{
Object *goalObject = getMachineGoalObject();
// if we have nothing to dock with, fail
if (goalObject == NULL)
return STATE_FAILURE;
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::update();
}
//----------------------------------------------------------------------------------------------
void AIDockApproachState::onExit( StateExitType status )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// tell the dock we have approached
if (dock)
{
// if we were interrupted, let the dock know we're not coming
if (status == EXIT_RESET || dock->isDockOpen() == FALSE)
dock->cancelDock( getMachineOwner() );
else
dock->onApproachReached( getMachineOwner() );
}
// this behavior is an extention of basic MoveTo
AIInternalMoveToState::onExit( status );
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
/**
* We have approached, now wait at our queue position until the dock says we can enter.
*/
StateReturnType AIDockWaitForClearanceState::onEnter( void )
{
m_enterFrame = TheGameLogic->getFrame();
return STATE_CONTINUE;
}
/**
* We have approached, now wait at our queue position until the dock says we can enter.
* @todo What if we are pushed off of our queue spot? We need to move back on... (MSB)
*/
StateReturnType AIDockWaitForClearanceState::update( void )
{
Object *goalObject = getMachineGoalObject();
if( goalObject == NULL )
return STATE_FAILURE;
DockUpdateInterface *dock = goalObject->getDockUpdateInterface();
// if we have nothing to dock with, fail
if (dock == NULL)
return STATE_FAILURE;
// fail if the dock is closed
if( dock->isDockOpen() == FALSE )
{
dock->cancelDock( getMachineOwner() );
return STATE_FAILURE;
}
// if the dock says we can enter, our wait is over
if (dock->isClearToEnter( getMachineOwner() ))
return STATE_SUCCESS;
if (m_enterFrame + 30*LOGICFRAMES_PER_SECOND < TheGameLogic->getFrame()) {
return STATE_FAILURE;
}
// continue to wait
return STATE_CONTINUE;
}
//----------------------------------------------------------------------------------------------
void AIDockWaitForClearanceState::onExit( StateExitType status )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// if we were interrupted, let the dock know we're not coming
if (dock && (dock->isDockOpen() == FALSE || status == EXIT_RESET))
dock->cancelDock( getMachineOwner() );
}
//----------------------------------------------------------------------------------------------
void AIDockWaitForClearanceState::xfer(Xfer *xfer )
{
XferVersion cv = 2;
XferVersion v = cv;
xfer->xferVersion( &v, cv );
if (v >= 2) {
xfer->xferUnsignedInt(&m_enterFrame);
} else {
m_enterFrame = TheGameLogic->getFrame();
}
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
/**
* Advance to our next waiting spot next to the dock.
*/
StateReturnType AIDockAdvancePositionState::onEnter( void )
{
Object *goalObject = getMachineGoalObject();
// sanity
if( goalObject == NULL )
return STATE_FAILURE;
// get dock update interface
DockUpdateInterface *dock = goalObject->getDockUpdateInterface();
// if we have nothing to dock with, fail
if (dock == NULL)
return STATE_FAILURE;
// fail if the dock is closed
if( dock->isDockOpen() == FALSE )
{
dock->cancelDock( getMachineOwner() );
return STATE_FAILURE;
}
// get a good place to wait from the dock
Bool reserved = dock->advanceApproachPosition( getMachineOwner(), &m_goalPosition, &(( (AIDockMachine*)getMachine() )->m_approachPosition) );
if( reserved == FALSE )
{
// dock is full
return STATE_FAILURE;
}
AIUpdateInterface *ai = getMachineOwner()->getAIUpdateInterface();
if (ai) {
ai->ignoreObstacle( NULL );
}
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::onEnter();
}
//----------------------------------------------------------------------------------------------
StateReturnType AIDockAdvancePositionState::update( void )
{
Object *goalObject = getMachineGoalObject();
// if we have nothing to dock with, fail
if (goalObject == NULL)
return STATE_FAILURE;
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::update();
}
//----------------------------------------------------------------------------------------------
void AIDockAdvancePositionState::onExit( StateExitType status )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// tell the dock we have approached
if (dock)
{
// if we were interrupted, let the dock know we're not coming
if (status == EXIT_RESET || dock->isDockOpen() == FALSE)
dock->cancelDock( getMachineOwner() );
else
dock->onApproachReached( getMachineOwner() );
}
// this behavior is an extention of basic MoveTo
AIInternalMoveToState::onExit( status );
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
/**
* Move to the dock's entry position.
*/
StateReturnType AIDockMoveToEntryState::onEnter( void )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// if we have nothing to dock with, fail
if (dock == NULL)
return STATE_FAILURE;
// fail if the dock is closed
if( dock->isDockOpen() == FALSE )
{
dock->cancelDock( getMachineOwner() );
return STATE_FAILURE;
}
AIUpdateInterface *ai = getMachineOwner()->getAIUpdateInterface();
if( ai && dock->isAllowPassthroughType() )
{
ai->ignoreObstacle( getMachineGoalObject() );
}
// get the enter position and set as our goal position
dock->getEnterPosition( getMachineOwner(), &m_goalPosition );
( (AIDockMachine*)getMachine() )->m_approachPosition = -1;
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::onEnter();
}
//----------------------------------------------------------------------------------------------
StateReturnType AIDockMoveToEntryState::update( void )
{
// if we have nothing to dock with, fail
if (getMachineGoalObject() == NULL)
return STATE_FAILURE;
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::update();
}
//----------------------------------------------------------------------------------------------
void AIDockMoveToEntryState::onExit( StateExitType status )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
if (dock)
{
if (dock->isDockOpen() == FALSE || status == EXIT_RESET)
{
// if we were interrupted, let the dock know we're not coming
dock->cancelDock( getMachineOwner() );
}
else
{
// tell the dock we are at the entrance
dock->onEnterReached( getMachineOwner() );
}
}
// this behavior is an extention of basic MoveTo
AIInternalMoveToState::onExit( status );
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
/**
* Move to the dock's docking position.
*/
StateReturnType AIDockMoveToDockState::onEnter( void )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// if we have nothing to dock with, fail
if (dock == NULL)
return STATE_FAILURE;
// fail if the dock is closed
if( dock->isDockOpen() == FALSE )
{
dock->cancelDock( getMachineOwner() );
return STATE_FAILURE;
}
// get the docking position
dock->getDockPosition( getMachineOwner(), &m_goalPosition );
AIUpdateInterface *ai = getMachineOwner()->getAIUpdateInterface();
if( ai && dock->isAllowPassthroughType() )
{
ai->ignoreObstacle( getMachineGoalObject() );
setAdjustsDestination(false);
}
// since we are moving inside the dock, disallow interruptions
getMachine()->lock("AIDockMoveToDockState::onEnter");
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::onEnter();
}
//----------------------------------------------------------------------------------------------
StateReturnType AIDockMoveToDockState::update( void )
{
Object *goalObject = getMachineGoalObject();
// if we have nothing to dock with, fail
if (goalObject == NULL)
return STATE_FAILURE;
DockUpdateInterface *dock = goalObject->getDockUpdateInterface();
if( dock->isDockOpen() == FALSE )
return STATE_FAILURE;
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::update();
}
//----------------------------------------------------------------------------------------------
void AIDockMoveToDockState::onExit( StateExitType status )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// tell the dock we are at the docking point
if (dock)
{
// if we were interrupted, let the dock know we're not coming
if (status == EXIT_RESET || dock->isDockOpen() == FALSE )
dock->cancelDock( getMachineOwner() );
else
dock->onDockReached( getMachineOwner() );
}
// unlock the machine
getMachine()->unlock();
// this behavior is an extention of basic MoveTo
AIInternalMoveToState::onExit( status );
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
AIDockProcessDockState::AIDockProcessDockState( StateMachine *machine ) : State( machine, "AIDockProcessDockState" )
{
m_nextDockActionFrame = 0;
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
void AIDockProcessDockState::setNextDockActionFrame()
{
// If we have a SupplyTruck Interface, then we will ask for our specific delay time
SupplyTruckAIInterface *supplyTruck = getMachineOwner()->getAI()->getSupplyTruckAIInterface();
if( supplyTruck )
{
m_nextDockActionFrame = TheGameLogic->getFrame() + supplyTruck->getActionDelayForDock( getMachineGoalObject() );
return;
}
// The default is that it is simply okay to Action right away
m_nextDockActionFrame = TheGameLogic->getFrame();
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
StateReturnType AIDockProcessDockState::onEnter( void )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// if we have nothing to dock with, fail
if (dock == NULL)
return STATE_FAILURE;
setNextDockActionFrame();
return STATE_CONTINUE;
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
/**
* We are now docked. Invoke the dock's action() method until it returns false.
*/
StateReturnType AIDockProcessDockState::update( void )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// if we have nothing to dock with, fail
if (dock == NULL)
return STATE_FAILURE;
// Some dockers can have a delay built in
if( TheGameLogic->getFrame() < m_nextDockActionFrame )
return STATE_CONTINUE;
setNextDockActionFrame();
Object *drone = findMyDrone();
// invoke the dock's action until it tells us it is done or the dock becomes closed
if( dock->isDockOpen() == false || dock->action( getMachineOwner(), drone ) == false )
return STATE_SUCCESS;
return STATE_CONTINUE;
}
//----------------------------------------------------------------------------------------------
struct DroneInfo
{
Object *owner;
Object *drone;
Bool found;
};
void findDrone( Object *obj, void *droneInfo )
{
DroneInfo *dInfo = (DroneInfo*)droneInfo;
if( !dInfo->found && obj )
{
if( obj->isKindOf( KINDOF_DRONE ) && obj->getProducerID() == dInfo->owner->getID() )
{
dInfo->found = TRUE;
dInfo->drone = obj;
}
}
}
//----------------------------------------------------------------------------------------------
Object* AIDockProcessDockState::findMyDrone()
{
//First do the fast cached check.
Object *drone = TheGameLogic->findObjectByID( m_droneID );
if( drone )
{
return drone;
}
//Nope... look for a drone (perhaps we just finished building one after docking?)
Object *self = getMachineOwner();
Player *player = self->getControllingPlayer();
DroneInfo dInfo;
dInfo.found = FALSE;
dInfo.drone = NULL;
dInfo.owner = self;
//Iterate the objects in search for a drone with a producer ID of me.
if( player )
{
player->iterateObjects( findDrone, &dInfo );
}
//If we found a drone, store it's ID as cached.
if( dInfo.drone )
{
m_droneID = dInfo.drone->getID();
}
return dInfo.drone;
}
//----------------------------------------------------------------------------------------------
void AIDockProcessDockState::onExit( StateExitType status )
{
// unlock the machine
getMachine()->unlock();
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
/**
* Move to the dock's exit position.
*/
StateReturnType AIDockMoveToExitState::onEnter( void )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// if we have nothing to dock with, fail
if (dock == NULL)
return STATE_FAILURE;
// get the exit position
dock->getExitPosition( getMachineOwner(), &m_goalPosition );
AIUpdateInterface *ai = getMachineOwner()->getAIUpdateInterface();
if( ai && dock->isAllowPassthroughType() )
{
ai->ignoreObstacle( getMachineGoalObject() );
setAdjustsDestination(false);
}
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::onEnter();
}
//----------------------------------------------------------------------------------------------
StateReturnType AIDockMoveToExitState::update( void )
{
// if we have nothing to dock with, fail
if (getMachineGoalObject() == NULL)
return STATE_FAILURE;
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::update();
}
//----------------------------------------------------------------------------------------------
void AIDockMoveToExitState::onExit( StateExitType status )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// tell the dock we have exited
if (dock)
dock->onExitReached( getMachineOwner() );
// unlock the machine
getMachine()->unlock();
// this behavior is an extention of basic MoveTo
AIInternalMoveToState::onExit( status );
}
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
/**
* Move to the dock's rally position, if he wants me to.
*/
StateReturnType AIDockMoveToRallyState::onEnter( void )
{
Object *goalObject = getMachineGoalObject();
DockUpdateInterface *dock = NULL;
if( goalObject )
dock = goalObject->getDockUpdateInterface();
// if we have nothing to dock with, fail
if (dock == NULL)
return STATE_FAILURE;
// if they don't have anywhere to send us, then we are good
if( ! dock->isRallyPointAfterDockType() //Chooses not to
|| goalObject->getObjectExitInterface() == NULL //or can't
|| goalObject->getObjectExitInterface()->getRallyPoint() == NULL //or can't right now.
)
{
return STATE_SUCCESS; // Success in an Enter is like success in an update. We're all fine here
}
// get the rally point and set as our goal position
m_goalPosition = *goalObject->getObjectExitInterface()->getRallyPoint();
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::onEnter();
}
//----------------------------------------------------------------------------------------------
StateReturnType AIDockMoveToRallyState::update( void )
{
// This state is fine with the loss of the goal object after the move starts
// this behavior is an extention of basic MoveTo
return AIInternalMoveToState::update();
}
//----------------------------------------------------------------------------------------------
void AIDockMoveToRallyState::onExit( StateExitType status )
{
// This state is fine with the loss of the goal object after the move starts
// this behavior is an extention of basic MoveTo
AIInternalMoveToState::onExit( status );
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,904 @@
/*
** 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: AIGuard.cpp
/*---------------------------------------------------------------------------*/
/* EA Pacific */
/* Confidential Information */
/* Copyright (C) 2001 - All Rights Reserved */
/* DO NOT DISTRIBUTE */
/*---------------------------------------------------------------------------*/
/* Project: RTS3 */
/* File name: AIGuard.cpp */
/* Created: John K. McDonald, Jr., 3/29/2002 */
/* Desc: // Set up guard states for AI */
/* Revision History: */
/* 3/29/2002 : Initial creation */
/*---------------------------------------------------------------------------*/
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/PerfTimer.h"
#include "Common/Team.h"
#include "Common/Xfer.h"
#include "Common/ThingTemplate.h"
#include "GameLogic/AI.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/AIGuard.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/CollideModule.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/PolygonTrigger.h"
const Real CLOSE_ENOUGH = (25.0f);
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
static Bool hasAttackedMeAndICanReturnFire( State *thisState, void* /*userData*/ )
{
Object *obj = thisState->getMachineOwner();
BodyModuleInterface *bmi = obj ? obj->getBodyModule() : NULL;
if (!(obj && bmi)) {
return FALSE;
}
if (bmi->getClearableLastAttacker() == INVALID_ID) {
return FALSE;
}
// K. It appears we have a valid aggressor. Find it, and determine if we can attack it, etc.
Object *target = TheGameLogic->findObjectByID(bmi->getClearableLastAttacker());
bmi->clearLastAttacker();
// We use the clearable last attacker because we should continue attacking the guy. But if he
// stops attacking us, then we want our timer to kick us off of him and make us go attack
// other units instead.
if (!target) {
return FALSE;
}
if (obj->getRelationship(target) != ENEMIES) {
return FALSE;
}
// This is a quick test on the target. It will be duplicated in getAbleToAttackSpecificObject,
// but the payoff is worth the duplication.
if (target->isEffectivelyDead()) {
return FALSE;
}
//@todo: Get this out of here. Move it into the declaration of calling this function, or figure
// out some way to call it less often.
if (!obj->isAbleToAttack()) {
return FALSE;
}
CanAttackResult result = obj->getAbleToAttackSpecificObject(ATTACK_NEW_TARGET, target, CMD_FROM_AI);
if( result == ATTACKRESULT_POSSIBLE || result == ATTACKRESULT_POSSIBLE_AFTER_MOVING )
{
return TRUE;
}
return FALSE;
}
//-- ExitConditions -------------------------------------------------------------------------------
/**
* This returns true if the conditions specified have been met, false otherwise.
*/
Bool ExitConditions::shouldExit(const StateMachine* machine) const
{
if (!machine->getGoalObject())
{
if (m_conditionsToConsider & ATTACK_ExitIfNoUnitFound)
{
return true;
}
else
{
return false;
}
}
if (m_conditionsToConsider & ATTACK_ExitIfExpiredDuration)
{
if (TheGameLogic->getFrame() >= m_attackGiveUpFrame)
{
return true;
}
}
if (m_conditionsToConsider & ATTACK_ExitIfOutsideRadius)
{
Coord3D deltaAggressor;
Coord3D objPos = *machine->getGoalObject()->getPosition();
deltaAggressor.x = objPos.x - m_center.x;
deltaAggressor.y = objPos.y - m_center.y;
// deltaAggressor.z = objPos.z - m_center.z;
deltaAggressor.z = 0; // BGC - when we search for a target we don't account for Z, so why should we here?
// changing this fixed a crash where a GLARebelInfantry would be in GuardReturnState, find
// a target that is within range, then not be able to attack because its actually out of range.
// then it would look for a new target, get the same one, and proceed in an infinite recursive
// loop that eventually blew the stack.
if (deltaAggressor.lengthSqr() > m_radiusSqr)
{
return true;
}
}
return false;
}
//-- AIGuardMachine -------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
AIGuardMachine::AIGuardMachine( Object *owner ) :
StateMachine(owner, "AIGuardMachine"),
m_targetToGuard(INVALID_ID),
m_areaToGuard(NULL),
m_nemesisToAttack(INVALID_ID),
m_guardMode(GUARDMODE_NORMAL)
{
m_positionToGuard.zero();
static const StateConditionInfo attackAggressors[] =
{
StateConditionInfo(hasAttackedMeAndICanReturnFire, AI_GUARD_ATTACK_AGGRESSOR, NULL),
StateConditionInfo(NULL, NULL, NULL) // keep last
};
// order matters: first state is the default state.
// srj sez: I made "return" the start state, so that if ordered to guard a position
// that isn't the unit's current position, it moves to that position first.
//Kris: Except that guard return is more like an attack move, and will acquire targets while moving there.
//This breaks deployAI units because they have to completely unpack before realizing that there is a target in range.
//So I'm making AI_GUARD_INNER the first state.
defineState( AI_GUARD_INNER, newInstance(AIGuardInnerState)( this ), AI_GUARD_OUTER, AI_GUARD_OUTER, attackAggressors );
defineState( AI_GUARD_RETURN, newInstance(AIGuardReturnState)( this ), AI_GUARD_IDLE, AI_GUARD_INNER, attackAggressors );
defineState( AI_GUARD_IDLE, newInstance(AIGuardIdleState)( this ), AI_GUARD_INNER, AI_GUARD_RETURN, attackAggressors );
defineState( AI_GUARD_OUTER, newInstance(AIGuardOuterState)( this ), AI_GUARD_GET_CRATE, AI_GUARD_GET_CRATE );
defineState( AI_GUARD_GET_CRATE, newInstance(AIGuardPickUpCrateState)( this ), AI_GUARD_RETURN, AI_GUARD_RETURN );
defineState( AI_GUARD_ATTACK_AGGRESSOR, newInstance(AIGuardAttackAggressorState)( this ), AI_GUARD_INNER, AI_GUARD_INNER );
}
//--------------------------------------------------------------------------------------
AIGuardMachine::~AIGuardMachine()
{
}
//--------------------------------------------------------------------------------------
/*static*/ Real AIGuardMachine::getStdGuardRange(const Object* obj)
{
Real visionRange = TheAI->getAdjustedVisionRangeForObject(obj,
AI_VISIONFACTOR_OWNERTYPE | AI_VISIONFACTOR_MOOD | AI_VISIONFACTOR_GUARDINNER);
return visionRange;
}
//--------------------------------------------------------------------------------------
Bool AIGuardMachine::lookForInnerTarget(void)
{
Object* owner = getOwner();
if (!owner->isAbleToAttack())
{
return false; // my, that was easy
}
// Check if team auto targets same victim.
Object *teamVictim = NULL;
if (owner->getTeam()->getPrototype()->getTemplateInfo()->m_attackCommonTarget)
{
teamVictim = owner->getTeam()->getTeamTargetObject();
if (teamVictim)
{
setNemesisID(teamVictim->getID());
return true; // Transitions to AIGuardInnerState.
}
}
Object* targetToGuard = findTargetToGuardByID();
Coord3D pos = targetToGuard ? *targetToGuard->getPosition() : *getPositionToGuard();
const PolygonTrigger* area = getAreaToGuard();
PartitionFilterRelationship f1(owner, PartitionFilterRelationship::ALLOW_ENEMIES);
PartitionFilterPossibleToAttack f2(ATTACK_NEW_TARGET, owner, CMD_FROM_AI);
PartitionFilterSameMapStatus filterMapStatus(owner);
PartitionFilterPolygonTrigger f3(area);
PartitionFilterIsFlying f4;
PartitionFilterRelationship f5(owner, PartitionFilterRelationship::ALLOW_NEUTRAL);
PartitionFilterPossibleToEnter f6(owner, CMD_FROM_AI);
PartitionFilterPossibleToHijack f7(owner, CMD_FROM_AI);
PartitionFilter *filters[16];
Int count = 0;
// Enter Guard state
if (owner->getTemplate()->isEnterGuard())
{
filters[count++] = &f6;
// Hijack Guard state
if (owner->getTemplate()->isHijackGuard())
{
filters[count++] = &f1;
filters[count++] = &f7;
}
else
{
filters[count++] = &f5;
}
}
// Attack Guard state
else
{
filters[count++] = &f1;
filters[count++] = &f2;
}
filters[count++] = &filterMapStatus;
Real visionRange = AIGuardMachine::getStdGuardRange(owner);
if (area)
{
UnsignedInt checkFrame = TheGameLogic->getFrameObjectsChangedTriggerAreas()+TheAI->getAiData()->m_guardEnemyScanRate;
if (TheGameLogic->getFrame()>checkFrame) {
return false;
}
filters[count++] = &f3;
visionRange = area->getRadius();
area->getCenterPoint(&pos);
}
if (getGuardMode() == GUARDMODE_GUARD_FLYING_UNITS_ONLY)
{
// only consider flying targets
filters[count++] = &f4;
}
filters[count++] = NULL;
// SimpleObjectIterator* iter = ThePartitionManager->iterateObjectsInRange(
// &pos, visionRange, FROM_CENTER_2D, filters, ITER_SORTED_NEAR_TO_FAR);
// MemoryPoolObjectHolder hold(iter);
// Object* target = iter->first();
//
// srj sez: the above code is stupid and slow. since we only want the closest object,
// just ask for that; the above has to find ALL objects in range, but we ignore all
// but the first (closest).
//
Object* target = ThePartitionManager->getClosestObject(&pos, visionRange, FROM_CENTER_2D, filters);
if (target)
{
setNemesisID(target->getID());
return true; // Transitions to AIGuardInnerState.
}
else
{
return false;
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AIGuardMachine::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AIGuardMachine::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 2;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
if (version>=2) {
StateMachine::xfer(xfer); // Forgot this in initial implementation. jba.
}
xfer->xferObjectID(&m_targetToGuard);
xfer->xferObjectID(&m_nemesisToAttack);
xfer->xferCoord3D(&m_positionToGuard);
AsciiString triggerName;
if (m_areaToGuard) triggerName = m_areaToGuard->getTriggerName();
xfer->xferAsciiString(&triggerName);
if (xfer->getXferMode() == XFER_LOAD)
{
if (triggerName.isNotEmpty()) {
m_areaToGuard = TheTerrainLogic->getTriggerAreaByName(triggerName);
}
}
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AIGuardMachine::loadPostProcess( void )
{
} // end loadPostProcess
//-- AIGuardInnerState ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AIGuardInnerState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AIGuardInnerState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AIGuardInnerState::loadPostProcess( void )
{
onEnter();
} // end loadPostProcess
//--------------------------------------------------------------------------------------
StateReturnType AIGuardInnerState::onEnter( void )
{
// See if we try to enter the target
if (getMachineOwner()->getTemplate()->isEnterGuard())
{
Object* nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID()) ;
if (nemesis == NULL)
{
DEBUG_LOG(("Unexpected NULL nemesis in AIGuardInnerState.\n"));
return STATE_SUCCESS;
}
m_enterState = newInstance(AIEnterState)(getMachine());
m_enterState->getMachine()->setGoalObject(nemesis);
StateReturnType returnVal = m_enterState->onEnter();
if (returnVal == STATE_CONTINUE) {
return STATE_CONTINUE;
}
}
// Or try to destroy the target
else
{
Object* targetToGuard = getGuardMachine()->findTargetToGuardByID();
Coord3D pos = targetToGuard ? *targetToGuard->getPosition() : *getGuardMachine()->getPositionToGuard();
Object* nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID()) ;
if (nemesis == NULL)
{
DEBUG_LOG(("Unexpected NULL nemesis in AIGuardInnerState.\n"));
return STATE_SUCCESS;
}
m_exitConditions.m_center = pos;
m_exitConditions.m_radiusSqr = sqr(AIGuardMachine::getStdGuardRange(getMachineOwner()));
m_exitConditions.m_conditionsToConsider = (ExitConditions::ATTACK_ExitIfOutsideRadius |
ExitConditions::ATTACK_ExitIfNoUnitFound);
m_attackState = newInstance(AIAttackState)(getMachine(), false, true, false, &m_exitConditions);
m_attackState->getMachine()->setGoalObject(nemesis);
StateReturnType returnVal = m_attackState->onEnter();
if (returnVal == STATE_CONTINUE) {
return STATE_CONTINUE;
}
}
// if we had no one to attack, we were successful, so go to the next state.
return STATE_SUCCESS;
}
//--------------------------------------------------------------------------------------
StateReturnType AIGuardInnerState::update( void )
{
if (m_attackState)
{
// if the position has moved (IE we're guarding an object), move with it.
Object* targetToGuard = getGuardMachine()->findTargetToGuardByID();
if (targetToGuard)
{
m_exitConditions.m_center = *targetToGuard->getPosition();
}
return m_attackState->update();
}
else if (m_enterState)
{
return m_enterState->update();
}
return STATE_SUCCESS;
}
//--------------------------------------------------------------------------------------
void AIGuardInnerState::onExit( StateExitType status )
{
Object *obj = getMachineOwner();
if (m_attackState)
{
m_attackState->onExit(status);
m_attackState->deleteInstance();
m_attackState = NULL;
}
else if (m_enterState)
{
m_enterState->onExit(status);
m_enterState->deleteInstance();
m_enterState = NULL;
}
if (obj->getTeam())
{
obj->getTeam()->setTeamTargetObject(NULL); // clear the target.
}
}
//-- AIGuardOuterState ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AIGuardOuterState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AIGuardOuterState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AIGuardOuterState::loadPostProcess( void )
{ AIGuardOuterState
onEnter();
} // end loadPostProcess
//--------------------------------------------------------------------------------------
StateReturnType AIGuardOuterState::onEnter( void )
{
if (getGuardMachine()->getGuardMode() == GUARDMODE_GUARD_WITHOUT_PURSUIT)
{
// "patrol" mode does not follow targets outside the guard area.
return STATE_SUCCESS;
}
Object* targetToGuard = getGuardMachine()->findTargetToGuardByID();
Coord3D pos = targetToGuard ? *targetToGuard->getPosition() : *getGuardMachine()->getPositionToGuard();
Object* nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID()) ;
if (nemesis == NULL)
{
DEBUG_LOG(("Unexpected NULL nemesis in AIGuardInnerState.\n"));
return STATE_SUCCESS;
}
Object *obj = getMachineOwner();
Real range = TheAI->getAdjustedVisionRangeForObject(obj, AI_VISIONFACTOR_OWNERTYPE | AI_VISIONFACTOR_MOOD);
const PolygonTrigger *area = getGuardMachine()->getAreaToGuard();
if (area)
{
if (range < area->getRadius())
range = area->getRadius();
area->getCenterPoint(&pos);
}
m_exitConditions.m_center = pos;
m_exitConditions.m_radiusSqr = sqr(range);
m_exitConditions.m_attackGiveUpFrame = TheGameLogic->getFrame() + TheAI->getAiData()->m_guardChaseUnitFrames;
m_exitConditions.m_conditionsToConsider = (ExitConditions::ATTACK_ExitIfExpiredDuration |
ExitConditions::ATTACK_ExitIfOutsideRadius |
ExitConditions::ATTACK_ExitIfNoUnitFound);
m_attackState = newInstance(AIAttackState)(getMachine(), false, true, false, &m_exitConditions);
m_attackState->getMachine()->setGoalObject(nemesis);
StateReturnType returnVal = m_attackState->onEnter();
if (returnVal == STATE_CONTINUE) {
return STATE_CONTINUE;
}
// if we had no one to attack, we were successful, so go to the next state.
return STATE_SUCCESS;
}
//--------------------------------------------------------------------------------------
StateReturnType AIGuardOuterState::update( void )
{
if (m_attackState==NULL) return STATE_SUCCESS;
// if the position has moved (IE we're guarding an object), move with it.
Object* targetToGuard = getGuardMachine()->findTargetToGuardByID();
if (targetToGuard)
{
m_exitConditions.m_center = *targetToGuard->getPosition();
}
Object* goalObj = m_attackState->getMachineGoalObject();
if (goalObj)
{
Coord3D deltaAggr;
deltaAggr.x = m_exitConditions.m_center.x - goalObj->getPosition()->x;
deltaAggr.y = m_exitConditions.m_center.y - goalObj->getPosition()->y;
deltaAggr.z = m_exitConditions.m_center.z - goalObj->getPosition()->z;
Real visionSqr = sqr(AIGuardMachine::getStdGuardRange(getMachineOwner()));
if (deltaAggr.lengthSqr() <= visionSqr)
{
// reset the counter
m_exitConditions.m_attackGiveUpFrame = TheGameLogic->getFrame() + TheAI->getAiData()->m_guardChaseUnitFrames;
}
}
return m_attackState->update();
}
//--------------------------------------------------------------------------------------
void AIGuardOuterState::onExit( StateExitType status )
{
if (m_attackState)
{
m_attackState->onExit(status);
m_attackState->deleteInstance();
m_attackState = NULL;
}
}
//-- AIGuardReturnState ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AIGuardReturnState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AIGuardReturnState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
xfer->xferUnsignedInt(&m_nextReturnScanTime);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AIGuardReturnState::loadPostProcess( void )
{
} // end loadPostProcess
//--------------------------------------------------------------------------------------
StateReturnType AIGuardReturnState::onEnter( void )
{
UnsignedInt now = TheGameLogic->getFrame();
m_nextReturnScanTime = now + GameLogicRandomValue(0, TheAI->getAiData()->m_guardEnemyReturnScanRate);
// no, no, no, don't do this in onEnter, unless you like really slow maps. (srj)
// if (getGuardMachine()->lookForInnerTarget())
// return STATE_FAILURE; // early termination because we found a target.
Object* targetToGuard = getGuardMachine()->findTargetToGuardByID();
m_goalPosition = targetToGuard ? *targetToGuard->getPosition() : *getGuardMachine()->getPositionToGuard();
const PolygonTrigger *area = getGuardMachine()->getAreaToGuard();
if (area)
{
area->getCenterPoint(&m_goalPosition);
}
AIUpdateInterface *ai = getMachineOwner()->getAIUpdateInterface();
if (ai && ai->isDoingGroundMovement())
{
TheAI->pathfinder()->adjustDestination(getMachineOwner(), ai->getLocomotorSet(), &m_goalPosition);
}
setAdjustsDestination(true);
return AIInternalMoveToState::onEnter();
}
//--------------------------------------------------------------------------------------
StateReturnType AIGuardReturnState::update( void )
{
UnsignedInt now = TheGameLogic->getFrame();
if (now >= m_nextReturnScanTime)
{
m_nextReturnScanTime = now + TheAI->getAiData()->m_guardEnemyReturnScanRate;
if (getGuardMachine()->lookForInnerTarget())
return STATE_FAILURE; // early termination because we found a target.
}
// Just let the return movement finish.
return AIInternalMoveToState::update();
}
//--------------------------------------------------------------------------------------
void AIGuardReturnState::onExit( StateExitType status )
{
AIInternalMoveToState::onExit( status );
}
//-- AIGuardIdleState ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AIGuardIdleState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AIGuardIdleState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
xfer->xferUnsignedInt(&m_nextEnemyScanTime);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AIGuardIdleState::loadPostProcess( void )
{
} // end loadPostProcess
//--------------------------------------------------------------------------------------
StateReturnType AIGuardIdleState::onEnter( void )
{
// first time thru, use a random amount so that everyone doesn't scan on the same frame,
// to avoid "spikes".
UnsignedInt now = TheGameLogic->getFrame();
m_nextEnemyScanTime = now + GameLogicRandomValue(0, TheAI->getAiData()->m_guardEnemyScanRate);
return STATE_CONTINUE;
}
//--------------------------------------------------------------------------------------
StateReturnType AIGuardIdleState::update( void )
{
//DEBUG_LOG(("AIGuardIdleState frame %d: %08lx\n",TheGameLogic->getFrame(),getMachineOwner()));
UnsignedInt now = TheGameLogic->getFrame();
if (now < m_nextEnemyScanTime)
return STATE_SLEEP(m_nextEnemyScanTime - now);
m_nextEnemyScanTime = now + TheAI->getAiData()->m_guardEnemyScanRate;
#ifdef STATE_MACHINE_DEBUG
//getMachine()->setDebugOutput(true);
#endif
Object *owner = getMachineOwner();
AIUpdateInterface *ai = owner->getAIUpdateInterface();
// Check to see if we have created a crate we need to pick up.
if (ai->getCrateID() != INVALID_ID)
{
getMachine()->setState(AI_GUARD_GET_CRATE);
return STATE_SLEEP(m_nextEnemyScanTime - now);
}
// if anyone is in the inner area, return success.
if (getGuardMachine()->lookForInnerTarget())
{
return STATE_SUCCESS; // Transitions to AIGuardInnerState.
}
// See if the object we are guarding moved.
Object* targetToGuard = getGuardMachine()->findTargetToGuardByID();
if (targetToGuard)
{
Coord3D pos = *targetToGuard->getPosition();
Real delta = m_guardeePos.x-pos.x;
if (delta*delta > 4*PATHFIND_CELL_SIZE_F*PATHFIND_CELL_SIZE_F) {
m_guardeePos = pos;
return STATE_FAILURE; // goes to AIGuardReturnState.
}
delta = m_guardeePos.y-pos.y;
if (delta*delta > 4*PATHFIND_CELL_SIZE_F*PATHFIND_CELL_SIZE_F) {
m_guardeePos = pos;
return STATE_FAILURE; // goes to AIGuardReturnState.
}
}
return STATE_SLEEP(m_nextEnemyScanTime - now);
}
//--------------------------------------------------------------------------------------
void AIGuardIdleState::onExit( StateExitType status )
{
}
//-- AIGuardPickUpCrateState ----------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
AIGuardPickUpCrateState::AIGuardPickUpCrateState( StateMachine *machine ) : AIPickUpCrateState(machine)
{
#ifdef STATE_MACHINE_DEBUG
setName("AIGuardPickUpCrateState");
#endif
}
//--------------------------------------------------------------------------------------
StateReturnType AIGuardPickUpCrateState::onEnter( void )
{
Object *owner = getMachineOwner();
AIUpdateInterface *ai = owner->getAIUpdateInterface();
// Check to see if we have created a crate we need to pick up.
Object* crate = ai->checkForCrateToPickup();
if (crate)
{
getMachine()->setGoalObject(crate);
return AIPickUpCrateState::onEnter();
}
return STATE_SUCCESS; // no crate, so we're done.
}
//--------------------------------------------------------------------------------------
StateReturnType AIGuardPickUpCrateState::update( void )
{
return AIPickUpCrateState::update();
}
//--------------------------------------------------------------------------------------
void AIGuardPickUpCrateState::onExit( StateExitType status )
{
}
//-- AIGuardAttackAggressorState ------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
AIGuardAttackAggressorState::AIGuardAttackAggressorState( StateMachine *machine ) :
State( machine, "AIGuardAttackAggressorState" )
{
m_attackState = NULL;
}
//-------------------------------------------------------------------------------------------------
StateReturnType AIGuardAttackAggressorState::onEnter( void )
{
Object *obj = getMachineOwner();
ObjectID nemID = INVALID_ID;
if (obj->getBodyModule() && obj->getBodyModule()->getLastDamageInfo()->in.m_sourceID) {
nemID = obj->getBodyModule()->getLastDamageInfo()->in.m_sourceID;
getGuardMachine()->setNemesisID(nemID);
}
Object *nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID());
if (nemesis == NULL)
{
DEBUG_LOG(("Unexpected NULL nemesis in AIGuardAttackAggressorState.\n"));
return STATE_SUCCESS;
}
Object* targetToGuard = getGuardMachine()->findTargetToGuardByID();
Coord3D pos = targetToGuard ? *targetToGuard->getPosition() : *getGuardMachine()->getPositionToGuard();
//Don't allow guarding units to leave their guard radius!
m_exitConditions.m_center = pos;
m_exitConditions.m_radiusSqr = sqr(AIGuardMachine::getStdGuardRange(getMachineOwner()));
m_exitConditions.m_attackGiveUpFrame = TheGameLogic->getFrame() + TheAI->getAiData()->m_guardChaseUnitFrames;
m_exitConditions.m_conditionsToConsider = (ExitConditions::ATTACK_ExitIfExpiredDuration |
ExitConditions::ATTACK_ExitIfNoUnitFound |
ExitConditions::ATTACK_ExitIfOutsideRadius );
m_attackState = newInstance(AIAttackState)(getMachine(), true, true, false, &m_exitConditions);
m_attackState->getMachine()->setGoalObject(nemesis);
StateReturnType returnVal = m_attackState->onEnter();
if (returnVal == STATE_CONTINUE) {
return STATE_CONTINUE;
}
// if we had no one to attack, we were successful, so go to the next state.
return STATE_SUCCESS;
}
//-------------------------------------------------------------------------------------------------
StateReturnType AIGuardAttackAggressorState::update( void )
{
if (m_attackState==NULL) return STATE_SUCCESS;
// if the position has moved (IE we're guarding an object), move with it.
Object* targetToGuard = getGuardMachine()->findTargetToGuardByID();
if (targetToGuard)
{
m_exitConditions.m_center = *targetToGuard->getPosition();
}
return m_attackState->update();
}
//-------------------------------------------------------------------------------------------------
void AIGuardAttackAggressorState::onExit( StateExitType status )
{
Object *obj = getMachineOwner();
if (m_attackState)
{
m_attackState->onExit(status);
m_attackState->deleteInstance();
m_attackState = NULL;
}
if (obj->getTeam())
{
obj->getTeam()->setTeamTargetObject(NULL); // clear the target.
}
}
//-------------------------------------------------------------------------------------------------
void AIGuardAttackAggressorState::crc( Xfer *xfer )
{
}
//-------------------------------------------------------------------------------------------------
void AIGuardAttackAggressorState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
}
//-------------------------------------------------------------------------------------------------
void AIGuardAttackAggressorState::loadPostProcess()
{
onEnter();
}

View File

@@ -0,0 +1,860 @@
/*
** 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: AIGuardRetaliate.h
/*---------------------------------------------------------------------------*/
/* Electronic Arts Los Angeles */
/* Confidential Information */
/* Copyright (C) 2003 - All Rights Reserved */
/* DO NOT DISTRIBUTE */
/*---------------------------------------------------------------------------*/
/* Project: RTS3 */
/* File name: AIGuardRetaliate.h */
/* Created: Kris Morness July 2003 */
/* Desc: // Define Guard Retaliation states for AI */
/*---------------------------------------------------------------------------*/
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/PerfTimer.h"
#include "Common/Team.h"
#include "Common/Xfer.h"
#include "Common/ThingTemplate.h"
#include "GameLogic/AI.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/AIGuardRetaliate.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/CollideModule.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/PolygonTrigger.h"
const Real CLOSE_ENOUGH = (25.0f);
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
static Bool hasAttackedMeAndICanReturnFire( State *thisState, void* /*userData*/ )
{
Object *obj = thisState->getMachineOwner();
BodyModuleInterface *bmi = obj ? obj->getBodyModule() : NULL;
if (!(obj && bmi)) {
return FALSE;
}
if (bmi->getClearableLastAttacker() == INVALID_ID) {
return FALSE;
}
// K. It appears we have a valid aggressor. Find it, and determine if we can attack it, etc.
Object *target = TheGameLogic->findObjectByID(bmi->getClearableLastAttacker());
bmi->clearLastAttacker();
// We use the clearable last attacker because we should continue attacking the guy. But if he
// stops attacking us, then we want our timer to kick us off of him and make us go attack
// other units instead.
if (!target) {
return FALSE;
}
if (obj->getRelationship(target) != ENEMIES) {
return FALSE;
}
// This is a quick test on the target. It will be duplicated in getAbleToAttackSpecificObject,
// but the payoff is worth the duplication.
if (target->isEffectivelyDead()) {
return FALSE;
}
//@todo: Get this out of here. Move it into the declaration of calling this function, or figure
// out some way to call it less often.
if (!obj->isAbleToAttack()) {
return FALSE;
}
CanAttackResult result = obj->getAbleToAttackSpecificObject(ATTACK_NEW_TARGET, target, CMD_FROM_AI);
if( result == ATTACKRESULT_POSSIBLE || result == ATTACKRESULT_POSSIBLE_AFTER_MOVING )
{
return TRUE;
}
return FALSE;
}
//-- ExitConditions -------------------------------------------------------------------------------
/**
* This returns true if the conditions specified have been met, false otherwise.
*/
Bool GuardRetaliateExitConditions::shouldExit(const StateMachine* machine) const
{
if (!machine->getGoalObject())
{
if (m_conditionsToConsider & ATTACK_ExitIfNoUnitFound)
{
return true;
}
else
{
return false;
}
}
if (m_conditionsToConsider & ATTACK_ExitIfExpiredDuration)
{
if (TheGameLogic->getFrame() >= m_attackGiveUpFrame)
{
return true;
}
}
if (m_conditionsToConsider & ATTACK_ExitIfOutsideRadius)
{
Coord3D deltaAggressor, myRange;
Coord3D objPos = *machine->getGoalObject()->getPosition();
Coord3D myPos = *machine->getOwner()->getPosition();
deltaAggressor.x = objPos.x - m_center.x;
deltaAggressor.y = objPos.y - m_center.y;
deltaAggressor.z = 0; // BGC - when we search for a target we don't account for Z, so why should we here?
// changing this fixed a crash where a GLARebelInfantry would be in GuardReturnState, find
// a target that is within range, then not be able to attack because its actually out of range.
// then it would look for a new target, get the same one, and proceed in an infinite recursive
// loop that eventually blew the stack.
Real guardRangeSqr = sqr( AIGuardRetaliateMachine::getStdGuardRange( machine->getOwner() ) );
myRange.x = myPos.x - m_center.x;
myRange.y = myPos.y - m_center.y;
myRange.z = 0;
if( deltaAggressor.lengthSqr() > m_radiusSqr )
{
//The aggressor is too far away now... give up retaliation.
return TRUE;
}
if( myRange.lengthSqr() > guardRangeSqr )
{
//Never go beyond standard guard radius.
return TRUE;
}
}
return FALSE;
}
//-- AIGuardRetaliateMachine -------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
AIGuardRetaliateMachine::AIGuardRetaliateMachine( Object *owner ) :
StateMachine(owner, "AIGuardRetaliateMachine"),
m_nemesisToAttack(INVALID_ID)
{
m_positionToGuard.zero();
static const StateConditionInfo attackAggressors[] =
{
StateConditionInfo(hasAttackedMeAndICanReturnFire, AI_GUARD_RETALIATE_ATTACK_AGGRESSOR, NULL),
StateConditionInfo(NULL, NULL, NULL) // keep last
};
// order matters: first state is the default state.
// srj sez: I made "return" the start state, so that if ordered to guard a position
// that isn't the unit's current position, it moves to that position first.
defineState( AI_GUARD_RETALIATE_ATTACK_AGGRESSOR, newInstance(AIGuardRetaliateAttackAggressorState)( this ), AI_GUARD_RETALIATE_RETURN, AI_GUARD_RETALIATE_RETURN );
defineState( AI_GUARD_RETALIATE_RETURN, newInstance(AIGuardRetaliateReturnState)( this ), AI_GUARD_RETALIATE_IDLE, AI_GUARD_RETALIATE_INNER, attackAggressors );
defineState( AI_GUARD_RETALIATE_IDLE, newInstance(AIGuardRetaliateIdleState)( this ), AI_GUARD_RETALIATE_INNER, EXIT_MACHINE_WITH_SUCCESS, attackAggressors );
defineState( AI_GUARD_RETALIATE_INNER, newInstance(AIGuardRetaliateInnerState)( this ), AI_GUARD_RETALIATE_OUTER, AI_GUARD_RETALIATE_OUTER );
defineState( AI_GUARD_RETALIATE_OUTER, newInstance(AIGuardRetaliateOuterState)( this ), AI_GUARD_RETALIATE_GET_CRATE, AI_GUARD_RETALIATE_GET_CRATE );
defineState( AI_GUARD_RETALIATE_GET_CRATE, newInstance(AIGuardRetaliatePickUpCrateState)( this ), AI_GUARD_RETALIATE_RETURN, AI_GUARD_RETALIATE_RETURN );
}
//--------------------------------------------------------------------------------------
AIGuardRetaliateMachine::~AIGuardRetaliateMachine()
{
}
Bool AIGuardRetaliateMachine::isIdle() const
{
if( getCurrentStateID() == AI_IDLE )
{
return TRUE;
}
return FALSE;
}
//--------------------------------------------------------------------------------------
/*static*/ Real AIGuardRetaliateMachine::getStdGuardRange(const Object* obj)
{
Real visionRange = TheAI->getAdjustedVisionRangeForObject(obj,
AI_VISIONFACTOR_OWNERTYPE | AI_VISIONFACTOR_MOOD | AI_VISIONFACTOR_GUARDINNER);
return visionRange;
}
//--------------------------------------------------------------------------------------
Bool AIGuardRetaliateMachine::lookForInnerTarget(void)
{
Object* owner = getOwner();
if (!owner->isAbleToAttack())
{
return false; // my, that was easy
}
// Check if team auto targets same victim.
Object *teamVictim = NULL;
if (owner->getTeam()->getPrototype()->getTemplateInfo()->m_attackCommonTarget)
{
teamVictim = owner->getTeam()->getTeamTargetObject();
if (teamVictim)
{
setNemesisID(teamVictim->getID());
return true; // Transitions to AIGuardRetaliateInnerState.
}
}
PartitionFilterRelationship f1(owner, PartitionFilterRelationship::ALLOW_ENEMIES);
PartitionFilterPossibleToAttack f2(ATTACK_NEW_TARGET, owner, CMD_FROM_AI);
PartitionFilterSameMapStatus filterMapStatus(owner);
PartitionFilterRelationship f5(owner, PartitionFilterRelationship::ALLOW_NEUTRAL);
PartitionFilterPossibleToEnter f6(owner, CMD_FROM_AI);
PartitionFilterPossibleToHijack f7(owner, CMD_FROM_AI);
PartitionFilterRejectBuildings f8( owner );
PartitionFilter *filters[16];
Int count = 0;
// Enter Guard state
if (owner->getTemplate()->isEnterGuard())
{
filters[count++] = &f6;
// Hijack Guard state
if (owner->getTemplate()->isHijackGuard())
{
filters[count++] = &f1;
filters[count++] = &f7;
}
else
{
filters[count++] = &f5;
}
}
// Attack Guard state
else
{
filters[count++] = &f1;
filters[count++] = &f2;
filters[count++] = &f8; //Different than guard... we won't allow acquiring of structures (unless base defenses)
}
filters[count++] = &filterMapStatus;
Real visionRange = AIGuardRetaliateMachine::getStdGuardRange(owner);
filters[count++] = NULL;
// SimpleObjectIterator* iter = ThePartitionManager->iterateObjectsInRange(
// &pos, visionRange, FROM_CENTER_2D, filters, ITER_SORTED_NEAR_TO_FAR);
// MemoryPoolObjectHolder hold(iter);
// Object* target = iter->first();
//
// srj sez: the above code is stupid and slow. since we only want the closest object,
// just ask for that; the above has to find ALL objects in range, but we ignore all
// but the first (closest).
//
const Coord3D *pos = getPositionToGuard();
Object* target = ThePartitionManager->getClosestObject(pos, visionRange, FROM_CENTER_2D, filters);
if (target)
{
setNemesisID(target->getID());
return true; // Transitions to AIGuardRetaliateInnerState.
}
else
{
return false;
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AIGuardRetaliateMachine::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AIGuardRetaliateMachine::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 2;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
if (version>=2) {
StateMachine::xfer(xfer); // Forgot this in initial implementation. jba.
}
xfer->xferObjectID(&m_nemesisToAttack);
xfer->xferCoord3D(&m_positionToGuard);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AIGuardRetaliateMachine::loadPostProcess( void )
{
} // end loadPostProcess
//-- AIGuardRetaliateInnerState ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AIGuardRetaliateInnerState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AIGuardRetaliateInnerState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AIGuardRetaliateInnerState::loadPostProcess( void )
{
onEnter();
} // end loadPostProcess
//--------------------------------------------------------------------------------------
StateReturnType AIGuardRetaliateInnerState::onEnter( void )
{
// See if we try to enter the target
if (getMachineOwner()->getTemplate()->isEnterGuard())
{
Object* nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID()) ;
if (nemesis == NULL)
{
DEBUG_LOG(("Unexpected NULL nemesis in AIGuardRetaliateInnerState.\n"));
return STATE_SUCCESS;
}
m_enterState = newInstance(AIEnterState)(getMachine());
m_enterState->getMachine()->setGoalObject(nemesis);
StateReturnType returnVal = m_enterState->onEnter();
if (returnVal == STATE_CONTINUE) {
return STATE_CONTINUE;
}
}
// Or try to destroy the target
else
{
Coord3D pos = *getGuardMachine()->getPositionToGuard();
Object* nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID()) ;
if (nemesis == NULL)
{
DEBUG_LOG(("Unexpected NULL nemesis in AIGuardRetaliateInnerState.\n"));
return STATE_SUCCESS;
}
m_exitConditions.m_center = pos;
m_exitConditions.m_radiusSqr = sqr( 1.5f * AIGuardRetaliateMachine::getStdGuardRange( getMachineOwner() ) );
m_exitConditions.m_conditionsToConsider = (GuardRetaliateExitConditions::ATTACK_ExitIfOutsideRadius |
GuardRetaliateExitConditions::ATTACK_ExitIfNoUnitFound);
m_attackState = newInstance(AIAttackState)(getMachine(), false, true, false, &m_exitConditions);
m_attackState->getMachine()->setGoalObject(nemesis);
StateReturnType returnVal = m_attackState->onEnter();
if (returnVal == STATE_CONTINUE) {
return STATE_CONTINUE;
}
}
// if we had no one to attack, we were successful, so go to the next state.
return STATE_SUCCESS;
}
//--------------------------------------------------------------------------------------
StateReturnType AIGuardRetaliateInnerState::update( void )
{
if (m_attackState)
{
return m_attackState->update();
}
else if (m_enterState)
{
return m_enterState->update();
}
return STATE_SUCCESS;
}
//--------------------------------------------------------------------------------------
void AIGuardRetaliateInnerState::onExit( StateExitType status )
{
Object *obj = getMachineOwner();
if (m_attackState)
{
m_attackState->onExit(status);
m_attackState->deleteInstance();
m_attackState = NULL;
}
else if (m_enterState)
{
m_enterState->onExit(status);
m_enterState->deleteInstance();
m_enterState = NULL;
}
if (obj->getTeam())
{
obj->getTeam()->setTeamTargetObject(NULL); // clear the target.
}
}
//-- AIGuardRetaliateOuterState ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AIGuardRetaliateOuterState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AIGuardRetaliateOuterState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AIGuardRetaliateOuterState::loadPostProcess( void )
{ AIGuardRetaliateOuterState
onEnter();
} // end loadPostProcess
//--------------------------------------------------------------------------------------
StateReturnType AIGuardRetaliateOuterState::onEnter( void )
{
//if (getGuardMachine()->getGuardMode() == GUARDMODE_GUARD_WITHOUT_PURSUIT)
//{
// // "patrol" mode does not follow targets outside the guard area.
// return STATE_SUCCESS;
//}
Coord3D pos = *getGuardMachine()->getPositionToGuard();
Object* nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID()) ;
if (nemesis == NULL)
{
DEBUG_LOG(("Unexpected NULL nemesis in AIGuardRetaliateOuterState.\n"));
return STATE_SUCCESS;
}
Object *obj = getMachineOwner();
Real range = TheAI->getAdjustedVisionRangeForObject(obj, AI_VISIONFACTOR_OWNERTYPE | AI_VISIONFACTOR_MOOD);
m_exitConditions.m_center = pos;
m_exitConditions.m_radiusSqr = sqr( 0.67f * (range + AIGuardRetaliateMachine::getStdGuardRange( getMachineOwner() )) );
m_exitConditions.m_attackGiveUpFrame = TheGameLogic->getFrame() + TheAI->getAiData()->m_guardChaseUnitFrames;
m_exitConditions.m_conditionsToConsider = (GuardRetaliateExitConditions::ATTACK_ExitIfExpiredDuration |
GuardRetaliateExitConditions::ATTACK_ExitIfOutsideRadius |
GuardRetaliateExitConditions::ATTACK_ExitIfNoUnitFound);
m_attackState = newInstance(AIAttackState)(getMachine(), false, true, false, &m_exitConditions);
m_attackState->getMachine()->setGoalObject(nemesis);
StateReturnType returnVal = m_attackState->onEnter();
if (returnVal == STATE_CONTINUE) {
return STATE_CONTINUE;
}
// if we had no one to attack, we were successful, so go to the next state.
return STATE_SUCCESS;
}
//--------------------------------------------------------------------------------------
StateReturnType AIGuardRetaliateOuterState::update( void )
{
if (m_attackState==NULL) return STATE_SUCCESS;
Object* goalObj = m_attackState->getMachineGoalObject();
if (goalObj)
{
Coord3D deltaAggr;
deltaAggr.x = m_exitConditions.m_center.x - goalObj->getPosition()->x;
deltaAggr.y = m_exitConditions.m_center.y - goalObj->getPosition()->y;
deltaAggr.z = m_exitConditions.m_center.z - goalObj->getPosition()->z;
Real visionSqr = sqr(AIGuardRetaliateMachine::getStdGuardRange(getMachineOwner()));
if (deltaAggr.lengthSqr() <= visionSqr)
{
// reset the counter
m_exitConditions.m_attackGiveUpFrame = TheGameLogic->getFrame() + TheAI->getAiData()->m_guardChaseUnitFrames;
}
}
return m_attackState->update();
}
//--------------------------------------------------------------------------------------
void AIGuardRetaliateOuterState::onExit( StateExitType status )
{
if (m_attackState)
{
m_attackState->onExit(status);
m_attackState->deleteInstance();
m_attackState = NULL;
}
}
//-- AIGuardRetaliateReturnState ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AIGuardRetaliateReturnState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AIGuardRetaliateReturnState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
xfer->xferUnsignedInt(&m_nextReturnScanTime);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AIGuardRetaliateReturnState::loadPostProcess( void )
{
} // end loadPostProcess
//--------------------------------------------------------------------------------------
StateReturnType AIGuardRetaliateReturnState::onEnter( void )
{
UnsignedInt now = TheGameLogic->getFrame();
m_nextReturnScanTime = now + GameLogicRandomValue(0, TheAI->getAiData()->m_guardEnemyReturnScanRate);
// no, no, no, don't do this in onEnter, unless you like really slow maps. (srj)
// if (getGuardMachine()->lookForInnerTarget())
// return STATE_FAILURE; // early termination because we found a target.
m_goalPosition = *getGuardMachine()->getPositionToGuard();
AIUpdateInterface *ai = getMachineOwner()->getAIUpdateInterface();
if (ai && ai->isDoingGroundMovement())
{
TheAI->pathfinder()->adjustDestination(getMachineOwner(), ai->getLocomotorSet(), &m_goalPosition);
}
setAdjustsDestination(true);
return AIInternalMoveToState::onEnter();
}
//--------------------------------------------------------------------------------------
StateReturnType AIGuardRetaliateReturnState::update( void )
{
UnsignedInt now = TheGameLogic->getFrame();
if (now >= m_nextReturnScanTime)
{
m_nextReturnScanTime = now + TheAI->getAiData()->m_guardEnemyReturnScanRate;
if (getGuardMachine()->lookForInnerTarget())
return STATE_FAILURE; // early termination because we found a target.
}
// Just let the return movement finish.
return AIInternalMoveToState::update();
}
//--------------------------------------------------------------------------------------
void AIGuardRetaliateReturnState::onExit( StateExitType status )
{
AIInternalMoveToState::onExit( status );
}
//-- AIGuardRetaliateIdleState ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AIGuardRetaliateIdleState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AIGuardRetaliateIdleState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
xfer->xferUnsignedInt(&m_nextEnemyScanTime);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AIGuardRetaliateIdleState::loadPostProcess( void )
{
} // end loadPostProcess
//--------------------------------------------------------------------------------------
StateReturnType AIGuardRetaliateIdleState::onEnter( void )
{
// first time thru, use a random amount so that everyone doesn't scan on the same frame,
// to avoid "spikes".
UnsignedInt now = TheGameLogic->getFrame();
m_nextEnemyScanTime = now + GameLogicRandomValue(0, TheAI->getAiData()->m_guardEnemyScanRate);
return STATE_CONTINUE;
}
//--------------------------------------------------------------------------------------
StateReturnType AIGuardRetaliateIdleState::update( void )
{
//DEBUG_LOG(("AIGuardRetaliateIdleState frame %d: %08lx\n",TheGameLogic->getFrame(),getMachineOwner()));
UnsignedInt now = TheGameLogic->getFrame();
if (now < m_nextEnemyScanTime)
return STATE_SLEEP(m_nextEnemyScanTime - now);
m_nextEnemyScanTime = now + TheAI->getAiData()->m_guardEnemyScanRate;
#ifdef STATE_MACHINE_DEBUG
//getMachine()->setDebugOutput(true);
#endif
Object *owner = getMachineOwner();
AIUpdateInterface *ai = owner->getAIUpdateInterface();
// Check to see if we have created a crate we need to pick up.
if (ai->getCrateID() != INVALID_ID)
{
getMachine()->setState(AI_GUARD_RETALIATE_GET_CRATE);
return STATE_SLEEP(m_nextEnemyScanTime - now);
}
// if anyone is in the inner area, return success.
if (getGuardMachine()->lookForInnerTarget())
{
return STATE_SUCCESS; // Transitions to AIGuardRetaliateInnerState.
}
else
{
return STATE_FAILURE;
}
return STATE_SLEEP(m_nextEnemyScanTime - now);
}
//--------------------------------------------------------------------------------------
void AIGuardRetaliateIdleState::onExit( StateExitType status )
{
}
//-- AIGuardRetaliatePickUpCrateState ----------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
AIGuardRetaliatePickUpCrateState::AIGuardRetaliatePickUpCrateState( StateMachine *machine ) : AIPickUpCrateState(machine)
{
#ifdef STATE_MACHINE_DEBUG
setName("AIGuardRetaliatePickUpCrateState");
#endif
}
//--------------------------------------------------------------------------------------
StateReturnType AIGuardRetaliatePickUpCrateState::onEnter( void )
{
Object *owner = getMachineOwner();
AIUpdateInterface *ai = owner->getAIUpdateInterface();
// Check to see if we have created a crate we need to pick up.
Object* crate = ai->checkForCrateToPickup();
if (crate)
{
getMachine()->setGoalObject(crate);
return AIPickUpCrateState::onEnter();
}
return STATE_SUCCESS; // no crate, so we're done.
}
//--------------------------------------------------------------------------------------
StateReturnType AIGuardRetaliatePickUpCrateState::update( void )
{
return AIPickUpCrateState::update();
}
//--------------------------------------------------------------------------------------
void AIGuardRetaliatePickUpCrateState::onExit( StateExitType status )
{
}
//-- AIGuardRetaliateAttackAggressorState ------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
AIGuardRetaliateAttackAggressorState::AIGuardRetaliateAttackAggressorState( StateMachine *machine ) :
State( machine, "AIGuardRetaliateAttackAggressorState" )
{
m_attackState = NULL;
}
#ifdef STATE_MACHINE_DEBUG
//----------------------------------------------------------------------------------------------------------
AsciiString AIGuardRetaliateAttackAggressorState::getName( ) const
{
AsciiString name = m_name;
name.concat("/ ");
if (m_attackState) name.concat(m_attackState->getName());
else name.concat("*NULL m_attackState");
return name;
}
#endif
//-------------------------------------------------------------------------------------------------
StateReturnType AIGuardRetaliateAttackAggressorState::onEnter( void )
{
Object *obj = getMachineOwner();
ObjectID nemID = INVALID_ID;
//Get the specified nemesis. If he doesn't exist... look at the object that damaged
//us last.
Object *nemesis = TheGameLogic->findObjectByID( getGuardMachine()->getNemesisID() );
BodyModuleInterface *body = obj->getBodyModule();
if( !nemesis && body && body->getLastDamageInfo()->in.m_sourceID && body->getLastDamageInfo()->in.m_damageType != DAMAGE_HEALING )
{
nemID = obj->getBodyModule()->getLastDamageInfo()->in.m_sourceID;
nemesis = TheGameLogic->findObjectByID( nemID );
if( nemesis && obj->getRelationship( nemesis ) == ENEMIES )
{
getGuardMachine()->setNemesisID(nemID);
}
}
if( !nemesis )
{
DEBUG_LOG(("Unexpected NULL nemesis in AIGuardRetaliateAttackAggressorState.\n"));
return STATE_SUCCESS;
}
Coord3D pos = *getGuardMachine()->getPositionToGuard();
Real range = TheAI->getAdjustedVisionRangeForObject(obj, AI_VISIONFACTOR_OWNERTYPE | AI_VISIONFACTOR_MOOD);
m_exitConditions.m_center = pos;
m_exitConditions.m_attackGiveUpFrame = TheGameLogic->getFrame() + TheAI->getAiData()->m_guardChaseUnitFrames;
m_exitConditions.m_radiusSqr = sqr( range + AIGuardRetaliateMachine::getStdGuardRange( obj ) );
m_exitConditions.m_conditionsToConsider = ( GuardRetaliateExitConditions::ATTACK_ExitIfExpiredDuration |
GuardRetaliateExitConditions::ATTACK_ExitIfOutsideRadius |
GuardRetaliateExitConditions::ATTACK_ExitIfNoUnitFound );
m_attackState = newInstance(AIAttackState)(getMachine(), false, true, false, &m_exitConditions);
m_attackState->getMachine()->setGoalObject(nemesis);
StateReturnType returnVal = m_attackState->onEnter();
if (returnVal == STATE_CONTINUE) {
return STATE_CONTINUE;
}
// if we had no one to attack, we were successful, so go to the next state.
return STATE_SUCCESS;
}
//-------------------------------------------------------------------------------------------------
StateReturnType AIGuardRetaliateAttackAggressorState::update( void )
{
if (m_attackState==NULL) return STATE_SUCCESS;
return m_attackState->update();
}
//-------------------------------------------------------------------------------------------------
void AIGuardRetaliateAttackAggressorState::onExit( StateExitType status )
{
Object *obj = getMachineOwner();
if (m_attackState)
{
m_attackState->onExit(status);
m_attackState->deleteInstance();
m_attackState = NULL;
}
if (obj->getTeam())
{
obj->getTeam()->setTeamTargetObject(NULL); // clear the target.
}
}
//-------------------------------------------------------------------------------------------------
void AIGuardRetaliateAttackAggressorState::crc( Xfer *xfer )
{
}
//-------------------------------------------------------------------------------------------------
void AIGuardRetaliateAttackAggressorState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
}
//-------------------------------------------------------------------------------------------------
void AIGuardRetaliateAttackAggressorState::loadPostProcess()
{
onEnter();
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,878 @@
/*
** 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: AITNGuard.cpp
/*---------------------------------------------------------------------------*/
/* EA Pacific */
/* Confidential Information */
/* Copyright (C) 2001 - All Rights Reserved */
/* DO NOT DISTRIBUTE */
/*---------------------------------------------------------------------------*/
/* Project: RTS3 */
/* File name: AITNGuard.cpp */
/* Created: John Ahlquist., 12/20/2002 */
/* Desc: // Set up guard tunnel network states for AI */
/* Revision History: */
/* 12/20/2002 : Initial creation - modified from AIGuard.cpp */
/*---------------------------------------------------------------------------*/
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/PerfTimer.h"
#include "Common/Team.h"
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "GameLogic/AI.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/AITNGuard.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/CollideModule.h"
#include "Common/TunnelTracker.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/PolygonTrigger.h"
const Real CLOSE_ENOUGH = (25.0f);
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
static Bool hasAttackedMeAndICanReturnFire( State *thisState, void* /*userData*/ )
{
Object *obj = thisState->getMachineOwner();
BodyModuleInterface *bmi = obj ? obj->getBodyModule() : NULL;
if (!(obj && bmi)) {
return FALSE;
}
if (bmi->getClearableLastAttacker() == INVALID_ID) {
return FALSE;
}
// K. It appears we have a valid aggressor. Find it, and determine if we can attack it, etc.
Object *target = TheGameLogic->findObjectByID(bmi->getClearableLastAttacker());
bmi->clearLastAttacker();
// We use the clearable last attacker because we should continue attacking the guy. But if he
// stops attacking us, then we want our timer to kick us off of him and make us go attack
// other units instead.
if (!target) {
return FALSE;
}
if (obj->getRelationship(target) != ENEMIES) {
return FALSE;
}
// This is a quick test on the target. It will be duplicated in getAbleToAttackSpecificObject,
// but the payoff is worth the duplication.
if (target->isEffectivelyDead()) {
return FALSE;
}
CanAttackResult result = obj->getAbleToAttackSpecificObject(ATTACK_NEW_TARGET, target, CMD_FROM_AI);
if( result == ATTACKRESULT_POSSIBLE || result == ATTACKRESULT_POSSIBLE_AFTER_MOVING )
{
return TRUE;
}
return FALSE;
}
static Object *findBestTunnel(Player *ownerPlayer, const Coord3D *pos)
{
if (!ownerPlayer) return NULL; // should never happen, but hey. jba.
TunnelTracker *tunnels = ownerPlayer->getTunnelSystem();
Object *bestTunnel = NULL;
Real bestDistSqr = 0;
const std::list<ObjectID> *allTunnels = tunnels->getContainerList();
for( std::list<ObjectID>::const_iterator iter = allTunnels->begin(); iter != allTunnels->end(); iter++ ) {
// For each ID, look it up and change its team. We all get captured together.
Object *currentTunnel = TheGameLogic->findObjectByID( *iter );
if( currentTunnel ) {
Real dx = currentTunnel->getPosition()->x-pos->x;
Real dy = currentTunnel->getPosition()->y-pos->y;
Real distSqr = dx*dx+dy*dy;
if (bestTunnel==NULL || distSqr<bestDistSqr) {
bestDistSqr = distSqr;
bestTunnel = currentTunnel;
}
}
}
return bestTunnel;
}
//-- ExitConditions -------------------------------------------------------------------------------
/**
* This returns true if the conditions specified have been met, false otherwise.
*/
Bool TunnelNetworkExitConditions::shouldExit(const StateMachine* machine) const
{
if (TheGameLogic->getFrame() >= m_attackGiveUpFrame)
{
return true;
}
return false;
}
//-- AITNGuardMachine -------------------------------------------------------------------------------
//--------------------------------------------------------------------------------------
AITNGuardMachine::AITNGuardMachine( Object *owner ) :
StateMachine(owner, "AITNGuardMachine"),
m_nemesisToAttack(INVALID_ID),
m_guardMode(GUARDMODE_NORMAL)
{
m_positionToGuard.zero();
static const StateConditionInfo attackAggressors[] =
{
StateConditionInfo(hasAttackedMeAndICanReturnFire, AI_TN_GUARD_ATTACK_AGGRESSOR, NULL),
StateConditionInfo(NULL, NULL, NULL) // keep last
};
// order matters: first state is the default state.
// srj sez: I made "return" the start state, so that if ordered to guard a position
// that isn't the unit's current position, it moves to that position first.
defineState( AI_TN_GUARD_RETURN, newInstance(AITNGuardReturnState)( this ), AI_TN_GUARD_IDLE, AI_TN_GUARD_INNER, attackAggressors );
defineState( AI_TN_GUARD_IDLE, newInstance(AITNGuardIdleState)( this ), AI_TN_GUARD_INNER, AI_TN_GUARD_RETURN );
defineState( AI_TN_GUARD_INNER, newInstance(AITNGuardInnerState)( this ), AI_TN_GUARD_OUTER, AI_TN_GUARD_OUTER , attackAggressors);
defineState( AI_TN_GUARD_OUTER, newInstance(AITNGuardOuterState)( this ), AI_TN_GUARD_GET_CRATE, AI_TN_GUARD_GET_CRATE );
defineState( AI_TN_GUARD_GET_CRATE, newInstance(AITNGuardPickUpCrateState)( this ), AI_TN_GUARD_RETURN, AI_TN_GUARD_RETURN );
defineState( AI_TN_GUARD_ATTACK_AGGRESSOR, newInstance(AITNGuardAttackAggressorState)( this ), AI_TN_GUARD_RETURN, AI_TN_GUARD_RETURN );
#ifdef STATE_MACHINE_DEBUG
setDebugOutput(true);
#endif
}
//--------------------------------------------------------------------------------------
AITNGuardMachine::~AITNGuardMachine()
{
}
//--------------------------------------------------------------------------------------
/*static*/ Real AITNGuardMachine::getStdGuardRange(const Object* obj)
{
Real visionRange = TheAI->getAdjustedVisionRangeForObject(obj,
AI_VISIONFACTOR_OWNERTYPE | AI_VISIONFACTOR_MOOD | AI_VISIONFACTOR_GUARDINNER);
return visionRange;
}
//--------------------------------------------------------------------------------------
Bool AITNGuardMachine::lookForInnerTarget(void)
{
Object* owner = getOwner();
// Check if team auto targets same victim.
Object *teamVictim = NULL;
if (owner->getTeam()->getPrototype()->getTemplateInfo()->m_attackCommonTarget)
{
teamVictim = owner->getTeam()->getTeamTargetObject();
if (teamVictim)
{
setNemesisID(teamVictim->getID());
return true; // Transitions to AITNGuardInnerState.
}
}
// Find tunnel network to defend.
// Scan my tunnels.
Player *ownerPlayer = getOwner()->getControllingPlayer();
if (!ownerPlayer) return false; // should never happen, but hey. jba.
TunnelTracker *tunnels = ownerPlayer->getTunnelSystem();
if (tunnels==NULL) return false;
if (tunnels->getCurNemesis()) {
setNemesisID(tunnels->getCurNemesis()->getID());
return true; // Transitions to AITNGuardInnerState.
}
const std::list<ObjectID> *allTunnels = tunnels->getContainerList();
for( std::list<ObjectID>::const_iterator iter = allTunnels->begin(); iter != allTunnels->end(); iter++ ) {
Object *currentTunnel = TheGameLogic->findObjectByID( *iter );
if( currentTunnel ) {
// Check for attacking.
if (currentTunnel->getAI()) {
Object *victim = currentTunnel->getAI()->getGoalObject();
if (owner->getRelationship(victim) == ENEMIES) {
setNemesisID(victim->getID());
return true;
}
}
// check for attacked.
BodyModuleInterface *body = currentTunnel->getBodyModule();
if (body) {
const DamageInfo *info = body->getLastDamageInfo();
if (info) {
if (info->out.m_noEffect) {
continue;
}
if (body->getLastDamageTimestamp() + TheAI->getAiData()->m_guardEnemyScanRate > TheGameLogic->getFrame()) {
// winner.
ObjectID attackerID = info->in.m_sourceID;
Object *attacker = TheGameLogic->findObjectByID(attackerID);
if( attacker )
{
if (owner->getRelationship(attacker) != ENEMIES) {
continue;
}
CanAttackResult result = getOwner()->getAbleToAttackSpecificObject(ATTACK_TUNNEL_NETWORK_GUARD, attacker, CMD_FROM_AI);
if( result == ATTACKRESULT_POSSIBLE || result == ATTACKRESULT_POSSIBLE_AFTER_MOVING )
{
setNemesisID(attackerID);
owner->getTeam()->setTeamTargetObject(attacker);
tunnels->updateNemesis(attacker);
return true; // Transitions to AITNGuardInnerState.
}
}
}
}
}
}
}
return false;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AITNGuardMachine::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AITNGuardMachine::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 2;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
if (version>=2) {
StateMachine::xfer(xfer); // Forgot this in initial implementation. jba.
}
xfer->xferObjectID(&m_nemesisToAttack);
xfer->xferCoord3D(&m_positionToGuard);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AITNGuardMachine::loadPostProcess( void )
{
} // end loadPostProcess
//-- AITNGuardInnerState ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AITNGuardInnerState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AITNGuardInnerState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AITNGuardInnerState::loadPostProcess( void )
{
onEnter();
} // end loadPostProcess
//--------------------------------------------------------------------------------------
StateReturnType AITNGuardInnerState::onEnter( void )
{
Object* nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID()) ;
if (nemesis == NULL)
{
DEBUG_LOG(("Unexpected NULL nemesis in AITNGuardInnerState.\n"));
return STATE_SUCCESS;
}
m_exitConditions.m_attackGiveUpFrame = TheGameLogic->getFrame() + TheAI->getAiData()->m_guardChaseUnitFrames;
m_attackState = newInstance(AIAttackState)(getMachine(), false, true, false, &m_exitConditions);
m_attackState->getMachine()->setGoalObject(nemesis);
StateReturnType returnVal = m_attackState->onEnter();
if (returnVal == STATE_CONTINUE) {
return STATE_CONTINUE;
}
// if we had no one to attack, we were successful, so go to the next state.
return STATE_SUCCESS;
}
static Object *TunnelNetworkScan(Object *owner)
{
PartitionFilterRelationship f1(owner, PartitionFilterRelationship::ALLOW_ENEMIES);
PartitionFilterPossibleToAttack f2(ATTACK_NEW_TARGET, owner, CMD_FROM_AI);
PartitionFilterSameMapStatus filterMapStatus(owner);
PartitionFilter *filters[16];
Int count = 0;
filters[count++] = &f1;
filters[count++] = &f2;
filters[count++] = &filterMapStatus;
Real visionRange = AITNGuardMachine::getStdGuardRange(owner);
filters[count++] = NULL;
Object* target = ThePartitionManager->getClosestObject(owner->getPosition(), visionRange, FROM_CENTER_2D, filters);
return target;
}
//--------------------------------------------------------------------------------------
StateReturnType AITNGuardInnerState::update( void )
{
Object* nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID()) ;
Player *ownerPlayer = getMachineOwner()->getControllingPlayer();
TunnelTracker *tunnels = NULL;
if (ownerPlayer) {
tunnels = ownerPlayer->getTunnelSystem();
}
Object* owner = getMachineOwner();
// killed him.
Object *teamVictim = owner->getTeam()->getTeamTargetObject();
if (nemesis == NULL)
{
if (teamVictim)
{
getGuardMachine()->setNemesisID(teamVictim->getID());
m_exitConditions.m_attackGiveUpFrame = TheGameLogic->getFrame() + TheAI->getAiData()->m_guardChaseUnitFrames;
return STATE_CONTINUE;
}
// Check tunnel.
if (tunnels) {
nemesis = tunnels->getCurNemesis();
if (nemesis) {
getGuardMachine()->setNemesisID(nemesis->getID());
m_exitConditions.m_attackGiveUpFrame = TheGameLogic->getFrame() + TheAI->getAiData()->m_guardChaseUnitFrames;
return STATE_CONTINUE;
}
}
if (m_scanForEnemy) {
m_scanForEnemy = false; // we just do 1 scan.
nemesis = TunnelNetworkScan(owner);
if (nemesis) {
m_attackState->onExit(EXIT_RESET);
m_attackState->getMachine()->setGoalObject(nemesis);
if (tunnels) {
tunnels->updateNemesis(nemesis);
}
StateReturnType returnVal = m_attackState->onEnter();
return returnVal;
}
}
} else {
if (nemesis != teamVictim && teamVictim != NULL) {
tunnels->updateNemesis(nemesis);
getGuardMachine()->setNemesisID(teamVictim->getID());
}
}
return m_attackState->update();
}
//--------------------------------------------------------------------------------------
void AITNGuardInnerState::onExit( StateExitType status )
{
if (m_attackState)
{
m_attackState->onExit(status);
m_attackState->deleteInstance();
m_attackState = NULL;
}
}
//-- AITNGuardOuterState ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AITNGuardOuterState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AITNGuardOuterState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AITNGuardOuterState::loadPostProcess( void )
{ AITNGuardOuterState
onEnter();
} // end loadPostProcess
//--------------------------------------------------------------------------------------
StateReturnType AITNGuardOuterState::onEnter( void )
{
if (getGuardMachine()->getGuardMode() == GUARDMODE_GUARD_WITHOUT_PURSUIT)
{
// "patrol" mode does not follow targets outside the guard area.
return STATE_SUCCESS;
}
Object* nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID()) ;
if (nemesis == NULL)
{
DEBUG_LOG(("Unexpected NULL nemesis in AITNGuardOuterState.\n"));
return STATE_SUCCESS;
}
m_exitConditions.m_attackGiveUpFrame = TheGameLogic->getFrame() + TheAI->getAiData()->m_guardChaseUnitFrames;
m_attackState = newInstance(AIAttackState)(getMachine(), false, true, false, &m_exitConditions);
m_attackState->getMachine()->setGoalObject(nemesis);
StateReturnType returnVal = m_attackState->onEnter();
if (returnVal == STATE_CONTINUE) {
return STATE_CONTINUE;
}
// if we had no one to attack, we were successful, so go to the next state.
return STATE_SUCCESS;
}
//--------------------------------------------------------------------------------------
StateReturnType AITNGuardOuterState::update( void )
{
Object *owner = getMachineOwner();
Object* goalObj = m_attackState->getMachineGoalObject();
if (goalObj)
{
} else {
Object* nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID()) ;
if (nemesis) {
goalObj = nemesis;
}
// Check if team auto targets same victim.
Object *teamVictim = NULL;
if (goalObj == NULL && owner->getTeam()->getPrototype()->getTemplateInfo()->m_attackCommonTarget)
{
teamVictim = owner->getTeam()->getTeamTargetObject();
if (teamVictim)
{
goalObj = teamVictim;
}
m_attackState->getMachine()->setGoalObject(goalObj);
return m_attackState->onEnter();
}
}
return m_attackState->update();
}
//--------------------------------------------------------------------------------------
void AITNGuardOuterState::onExit( StateExitType status )
{
if (m_attackState)
{
m_attackState->onExit(status);
m_attackState->deleteInstance();
m_attackState = NULL;
}
}
//-- AITNGuardReturnState ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AITNGuardReturnState::crc( Xfer *xfer )
{
AIEnterState::crc(xfer);
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AITNGuardReturnState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
AIEnterState::xfer(xfer);
xfer->xferUnsignedInt(&m_nextReturnScanTime);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AITNGuardReturnState::loadPostProcess( void )
{
AIEnterState::loadPostProcess();
} // end loadPostProcess
//--------------------------------------------------------------------------------------
StateReturnType AITNGuardReturnState::onEnter( void )
{
UnsignedInt now = TheGameLogic->getFrame();
m_nextReturnScanTime = now + GameLogicRandomValue(0, TheAI->getAiData()->m_guardEnemyReturnScanRate);
if (getMachineOwner()->getContainedBy()) {
// we are already inside our tunnel. Return success. jba [8/24/2003]
return STATE_SUCCESS;
}
if (getMachineOwner()->getTeam()) {
Object *teamVictim = getMachineOwner()->getTeam()->getTeamTargetObject();
if (teamVictim) {
// We have a team target. Go attack it rather than returning to the tunel. jba [8/24/2003]
getGuardMachine()->setNemesisID(teamVictim->getID());
return STATE_FAILURE; // Fail to return goes to inner attack state.
}
}
// no, no, no, don't do this in onEnter, unless you like really slow maps. (srj)
// if (getGuardMachine()->lookForInnerTarget())
// return STATE_FAILURE; // early termination because we found a target.
// Find tunnel network to enter.
// Scan my tunnels.
Object *bestTunnel = findBestTunnel(getMachineOwner()->getControllingPlayer(), getMachineOwner()->getPosition());
if (bestTunnel==NULL) return STATE_FAILURE;
getMachine()->setGoalObject(bestTunnel);
getMachineOwner()->getAI()->friend_setGoalObject(bestTunnel);
return AIEnterState::onEnter();
}
//--------------------------------------------------------------------------------------
StateReturnType AITNGuardReturnState::update( void )
{
Player *ownerPlayer = getMachineOwner()->getControllingPlayer();
if (getMachineOwner()->getTeam()) {
Object *teamVictim = getMachineOwner()->getTeam()->getTeamTargetObject();
if (teamVictim) {
getGuardMachine()->setNemesisID(teamVictim->getID());
return STATE_FAILURE; // Fail to return goes to inner attack state.
}
}
// Check tunnel for target.
TunnelTracker *tunnels = NULL;
if (ownerPlayer) {
tunnels = ownerPlayer->getTunnelSystem();
}
if (tunnels) {
Object *nemesis = tunnels->getCurNemesis();
if (nemesis) {
// Check distance.
//Coord3D dist;
//Coord3D curPos;
//dist.set()
getGuardMachine()->setNemesisID(nemesis->getID());
return STATE_FAILURE; // Fail to return goes to inner attack state.
}
}
// Just let the return movement finish.
StateReturnType ret = AIEnterState::update();
if (ret==STATE_CONTINUE) return STATE_CONTINUE;
return STATE_SUCCESS;
}
//--------------------------------------------------------------------------------------
void AITNGuardReturnState::onExit( StateExitType status )
{
AIEnterState::onExit( status );
}
//-- AITNGuardIdleState ----------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AITNGuardIdleState::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void AITNGuardIdleState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
xfer->xferUnsignedInt(&m_nextEnemyScanTime);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AITNGuardIdleState::loadPostProcess( void )
{
} // end loadPostProcess
//--------------------------------------------------------------------------------------
StateReturnType AITNGuardIdleState::onEnter( void )
{
// first time thru, use a random amount so that everyone doesn't scan on the same frame,
// to avoid "spikes".
UnsignedInt now = TheGameLogic->getFrame();
m_nextEnemyScanTime = now + GameLogicRandomValue(0, TheAI->getAiData()->m_guardEnemyScanRate);
getMachineOwner()->getAI()->friend_setGoalObject(NULL);
return STATE_CONTINUE;
}
//--------------------------------------------------------------------------------------
StateReturnType AITNGuardIdleState::update( void )
{
//DEBUG_LOG(("AITNGuardIdleState frame %d: %08lx\n",TheGameLogic->getFrame(),getMachineOwner()));
UnsignedInt now = TheGameLogic->getFrame();
if (now < m_nextEnemyScanTime)
return STATE_SLEEP(m_nextEnemyScanTime - now);
m_nextEnemyScanTime = now + TheAI->getAiData()->m_guardEnemyScanRate;
getMachineOwner()->getAI()->friend_setGoalObject(NULL);
#ifdef STATE_MACHINE_DEBUG
//getMachine()->setDebugOutput(true);
#endif
Object *owner = getMachineOwner();
AIUpdateInterface *ai = owner->getAIUpdateInterface();
// Check to see if we have created a crate we need to pick up.
if (ai->getCrateID() != INVALID_ID)
{
getMachine()->setState(AI_TN_GUARD_GET_CRATE);
return STATE_SLEEP(m_nextEnemyScanTime - now);
}
// if anyone is in the inner area, return success.
if (getGuardMachine()->lookForInnerTarget())
{
Object *nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID());
if (nemesis == NULL)
{
DEBUG_LOG(("Unexpected NULL nemesis in AITNGuardAttackAggressorState.\n"));
return STATE_SLEEP(0);
}
if (getMachineOwner()->getContainedBy()) {
Object *bestTunnel = findBestTunnel(owner->getControllingPlayer(), nemesis->getPosition());
ExitInterface* goalExitInterface = bestTunnel->getContain() ? bestTunnel->getContain()->getContainExitInterface() : NULL;
if( goalExitInterface == NULL )
return STATE_FAILURE;
if( goalExitInterface->isExitBusy() )
return STATE_SLEEP(0);// Just wait a sec.
goalExitInterface->exitObjectInAHurry(getMachineOwner());
return STATE_SLEEP(0);
}
return STATE_SUCCESS; // Transitions to AITNGuardInnerState.
}
if (!owner->getContainedBy() && findBestTunnel(owner->getControllingPlayer(), owner->getPosition())) {
return STATE_FAILURE; // go to AITNGuardReturnState, & enter a tunnel.
}
return STATE_SLEEP(m_nextEnemyScanTime - now);
}
//--------------------------------------------------------------------------------------
void AITNGuardIdleState::onExit( StateExitType status )
{
}
//-- AITNGuardPickUpCrateState ----------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
AITNGuardPickUpCrateState::AITNGuardPickUpCrateState( StateMachine *machine ) : AIPickUpCrateState(machine)
{
#ifdef STATE_MACHINE_DEBUG
setName("AITNGuardPickUpCrateState");
#endif
}
//--------------------------------------------------------------------------------------
StateReturnType AITNGuardPickUpCrateState::onEnter( void )
{
Object *owner = getMachineOwner();
AIUpdateInterface *ai = owner->getAIUpdateInterface();
// Check to see if we have created a crate we need to pick up.
Object* crate = ai->checkForCrateToPickup();
if (crate)
{
getMachine()->setGoalObject(crate);
return AIPickUpCrateState::onEnter();
}
return STATE_SUCCESS; // no crate, so we're done.
}
//--------------------------------------------------------------------------------------
StateReturnType AITNGuardPickUpCrateState::update( void )
{
return AIPickUpCrateState::update();
}
//--------------------------------------------------------------------------------------
void AITNGuardPickUpCrateState::onExit( StateExitType status )
{
}
//-- AITNGuardAttackAggressorState ------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
AITNGuardAttackAggressorState::AITNGuardAttackAggressorState( StateMachine *machine ) :
State( machine, "AITNGuardAttackAggressorState" )
{
m_attackState = NULL;
}
//-------------------------------------------------------------------------------------------------
StateReturnType AITNGuardAttackAggressorState::onEnter( void )
{
Object *obj = getMachineOwner();
ObjectID nemID = INVALID_ID;
if (obj->getBodyModule() && obj->getBodyModule()->getLastDamageInfo()->in.m_sourceID) {
nemID = obj->getBodyModule()->getLastDamageInfo()->in.m_sourceID;
getGuardMachine()->setNemesisID(nemID);
}
Object *nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID());
if (nemesis == NULL)
{
DEBUG_LOG(("Unexpected NULL nemesis in AITNGuardAttackAggressorState.\n"));
return STATE_SUCCESS;
}
Player *ownerPlayer = getMachineOwner()->getControllingPlayer();
TunnelTracker *tunnels = NULL;
if (ownerPlayer) {
tunnels = ownerPlayer->getTunnelSystem();
}
if (tunnels) tunnels->updateNemesis(nemesis);
m_exitConditions.m_attackGiveUpFrame = TheGameLogic->getFrame() + TheAI->getAiData()->m_guardChaseUnitFrames;
m_attackState = newInstance(AIAttackState)(getMachine(), true, true, false, &m_exitConditions);
m_attackState->getMachine()->setGoalObject(nemesis);
StateReturnType returnVal = m_attackState->onEnter();
if (returnVal == STATE_CONTINUE) {
return STATE_CONTINUE;
}
// if we had no one to attack, we were successful, so go to the next state.
return STATE_SUCCESS;
}
//-------------------------------------------------------------------------------------------------
StateReturnType AITNGuardAttackAggressorState::update( void )
{
if (m_attackState->getMachine()->getCurrentStateID() == AttackStateMachine::FIRE_WEAPON) {
Object *nemesis = TheGameLogic->findObjectByID(getGuardMachine()->getNemesisID());
Player *ownerPlayer = getMachineOwner()->getControllingPlayer();
TunnelTracker *tunnels = NULL;
if (ownerPlayer) {
tunnels = ownerPlayer->getTunnelSystem();
}
if (tunnels) tunnels->updateNemesis(nemesis);
}
return m_attackState->update();
}
//-------------------------------------------------------------------------------------------------
void AITNGuardAttackAggressorState::onExit( StateExitType status )
{
Object *obj = getMachineOwner();
if (m_attackState)
{
m_attackState->onExit(status);
m_attackState->deleteInstance();
m_attackState = NULL;
}
if (obj->getTeam())
{
obj->getTeam()->setTeamTargetObject(NULL); // clear the target.
}
}
//-------------------------------------------------------------------------------------------------
void AITNGuardAttackAggressorState::crc( Xfer *xfer )
{
}
//-------------------------------------------------------------------------------------------------
void AITNGuardAttackAggressorState::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
}
//-------------------------------------------------------------------------------------------------
void AITNGuardAttackAggressorState::loadPostProcess()
{
onEnter();
}

View File

@@ -0,0 +1,271 @@
/*
** 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: Squad.cpp
/*---------------------------------------------------------------------------*/
/* EA Pacific */
/* Confidential Information */
/* Copyright (C) 2001 - All Rights Reserved */
/* DO NOT DISTRIBUTE */
/*---------------------------------------------------------------------------*/
/* Project: RTS3 */
/* File name: Squad.cpp */
/* Created: John K. McDonald, Jr., 4/19/2002 */
/* Desc: // @todo */
/* Revision History: */
/* 4/19/2002 : Initial creation */
/*---------------------------------------------------------------------------*/
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameLogic/Squad.h"
#include "Common/GameState.h"
#include "Common/Team.h"
#include "Common/Xfer.h"
#include "GameLogic/AI.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// addObject //////////////////////////////////////////////////////////////////////////////////////
void Squad::addObject(Object *objectToAdd)
{
if (objectToAdd) {
m_objectIDs.push_back(objectToAdd->getID());
}
}
// addObjectID ////////////////////////////////////////////////////////////////////////////////////
void Squad::addObjectID(ObjectID objectID) {
m_objectIDs.push_back(objectID);
}
// removeObject ///////////////////////////////////////////////////////////////////////////////////
void Squad::removeObject(Object *objectToRemove)
{
if (objectToRemove) {
ObjectID objID;
objID = objectToRemove->getID();
VecObjectIDIt it = std::find(m_objectIDs.begin(), m_objectIDs.end(), objID);
if (it != m_objectIDs.end()) {
m_objectIDs.erase(it);
}
}
}
// clearSquad /////////////////////////////////////////////////////////////////////////////////////
void Squad::clearSquad() {
m_objectIDs.clear();
m_objectsCached.clear();
}
// getAllObjects //////////////////////////////////////////////////////////////////////////////////
const VecObjectPtr& Squad::getAllObjects(void) // Not a const function cause we clear away dead object here too
{
// prunes all NULL objects
m_objectsCached.clear();
for (VecObjectIDIt it = m_objectIDs.begin(); it != m_objectIDs.end(); ) {
Object *obj = TheGameLogic->findObjectByID(*it);
if (obj) {
m_objectsCached.push_back(obj);
++it;
} else {
it = m_objectIDs.erase(it);
}
}
return m_objectsCached;
}
// getLiveObjects /////////////////////////////////////////////////////////////////////////////////
const VecObjectPtr& Squad::getLiveObjects(void)
{
// first get all the objects.
// cheat, since we are a member function, and just use m_objectsCached
getAllObjects();
for (VecObjectPtrIt it = m_objectsCached.begin(); it != m_objectsCached.end(); ) {
if (!(*it)->isSelectable()) {
it = m_objectsCached.erase(it);
} else {
++it;
}
}
return m_objectsCached;
}
// getSizeOfGroup /////////////////////////////////////////////////////////////////////////////////
Int Squad::getSizeOfGroup(void) const
{
return m_objectIDs.size();
}
// isOnSquad //////////////////////////////////////////////////////////////////////////////////////
Bool Squad::isOnSquad(const Object *objToTest) const
{
// @todo need a faster way to do this. Perhaps a more efficient data structure?
ObjectID objID = objToTest->getID();
for (VecObjectID::const_iterator cit = m_objectIDs.begin(); cit != m_objectIDs.end(); ++cit) {
if (objID == (*cit)) {
return true;
}
}
return false;
}
/**
* There should never be a TeamFromSqaud as Teams are entirely a construct to work with the AI.
* Since things can only be on one Team at a time, creating a Team from an arbitrary Squad will
* cause weird, difficult to reproduce bugs. Please don't do it.
*/
// squadFromTeam //////////////////////////////////////////////////////////////////////////////////
void Squad::squadFromTeam(const Team* fromTeam, Bool clearSquadFirst)
{
if (!fromTeam) {
return;
}
if (clearSquadFirst) {
m_objectIDs.clear();
}
for (DLINK_ITERATOR<Object> iter = fromTeam->iterate_TeamMemberList(); !iter.done(); iter.advance()) {
Object *obj = iter.cur();
m_objectIDs.push_back(obj->getID());
}
}
// squadFromAIGroup ///////////////////////////////////////////////////////////////////////////////
void Squad::squadFromAIGroup(const AIGroup* fromAIGroup, Bool clearSquadFirst)
{
if (!fromAIGroup) {
return;
}
if (clearSquadFirst) {
m_objectIDs.clear();
}
m_objectIDs = fromAIGroup->getAllIDs();
}
// aiGroupFromSquad ///////////////////////////////////////////////////////////////////////////////
void Squad::aiGroupFromSquad(AIGroup* aiGroupToFill)
{
if (!aiGroupToFill) {
return;
}
// cheat, since we are a member function, and just use m_objectsCached
getLiveObjects();
for (VecObjectPtr::iterator it = m_objectsCached.begin(); it != m_objectsCached.end(); ++it) {
aiGroupToFill->add((*it));
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void Squad::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void Squad::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// length of object ID list
UnsignedShort objectCount = m_objectIDs.size();
xfer->xferUnsignedShort( &objectCount );
// object id elements
ObjectID objectID;
if( xfer->getXferMode() == XFER_SAVE )
{
// save each object id
VecObjectIDIt it;
for( it = m_objectIDs.begin(); it != m_objectIDs.end(); ++it )
{
// save object ID
objectID = *it;
xfer->xferObjectID( &objectID );
} // end for, it
} // end if, save
else
{
// the cached objects list should be empty
if( m_objectsCached.size() != 0 )
{
DEBUG_CRASH(( "Squad::xfer - m_objectsCached should be emtpy, but is not\n" ));
throw SC_INVALID_DATA;
} // end of
// read all items
for( UnsignedShort i = 0; i < objectCount; ++i )
{
// read id
xfer->xferObjectID( &objectID );
// put on list
m_objectIDs.push_back( objectID );
} // end for, i
} // end else, load
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void Squad::loadPostProcess( void )
{
} // end loadPostProcess

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,559 @@
/*
** 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. //
// //
////////////////////////////////////////////////////////////////////////////////
// PolygonTrigger.cpp
// Class to encapsulate polygon trigger areas.
// Author: John Ahlquist, November 2001
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/DataChunk.h"
#include "Common/MapObject.h"
#include "Common/MapReaderWriterInfo.h"
#include "Common/Xfer.h"
#include "GameLogic/PolygonTrigger.h"
#include "GameLogic/TerrainLogic.h"
/* ********* PolygonTrigger class ****************************/
PolygonTrigger *PolygonTrigger::ThePolygonTriggerListPtr = NULL;
Int PolygonTrigger::s_currentID = 1;
/**
PolygonTrigger - Constructor.
*/
PolygonTrigger::PolygonTrigger(Int initialAllocation) :
m_nextPolygonTrigger(NULL),
m_points(NULL),
m_numPoints(0),
m_sizePoints(0),
m_exportWithScripts(false),
m_isWaterArea(false),
m_shouldRender(true),
m_selected(false),
//Added By Sadullah Nader
//Initializations inserted
m_isRiver(FALSE),
m_riverStart(0)
//
{
if (initialAllocation < 2) initialAllocation = 2;
m_points = NEW ICoord3D[initialAllocation]; // pool[]ify
m_sizePoints = initialAllocation;
m_triggerID = s_currentID++;
m_waterHandle.m_polygon = this;
}
/**
PolygonTrigger - Destructor - note - if linked, deletes linked items.
*/
PolygonTrigger::~PolygonTrigger(void)
{
if (m_points) {
delete [] m_points;
m_points = NULL;
}
if (m_nextPolygonTrigger) {
PolygonTrigger *cur = m_nextPolygonTrigger;
PolygonTrigger *next;
while (cur) {
next = cur->getNext();
cur->setNextPoly(NULL); // prevents recursion.
cur->deleteInstance();
cur = next;
}
}
}
/**
PolygonTrigger::reallocate - increases the size of the points list.
NOTE: It is expected that this will only get called in the editor, as in the game
the poly triggers don't change.
*/
void PolygonTrigger::reallocate(void)
{
DEBUG_ASSERTCRASH(m_numPoints <= m_sizePoints, ("Invalid m_numPoints."));
if (m_numPoints == m_sizePoints) {
// Reallocate.
m_sizePoints += m_sizePoints;
ICoord3D *newPts = NEW ICoord3D[m_sizePoints];
Int i;
for (i=0; i<m_numPoints; i++) {
newPts[i] = m_points[i];
}
delete [] m_points;
m_points = newPts;
}
}
/**
* Find the polygon trigger with the matching ID
*/
PolygonTrigger *PolygonTrigger::getPolygonTriggerByID(Int triggerID)
{
for( PolygonTrigger *poly = PolygonTrigger::getFirstPolygonTrigger();
poly; poly = poly->getNext() )
if( poly->getID() == triggerID )
return poly;
// not found
return NULL;
}
/**
* PolygonTrigger::ParsePolygonTriggersDataChunk - read a polygon triggers chunk.
* Format is the newer CHUNKY format.
* See PolygonTrigger::WritePolygonTriggersDataChunk for the writer.
* Input: DataChunkInput
*
*/
Bool PolygonTrigger::ParsePolygonTriggersDataChunk(DataChunkInput &file, DataChunkInfo *info, void *userData)
{
Int count;
Int numPoints;
Int triggerID;
Int maxTriggerId = 0;
Bool isWater;
Bool isRiver;
Int riverStart;
AsciiString triggerName;
AsciiString layerName;
// Remove any existing polygon triggers, if any.
PolygonTrigger::deleteTriggers(); // just in case.
PolygonTrigger *pPrevTrig = NULL;
ICoord3D loc;
count = file.readInt();
while (count>0) {
count--;
triggerName = file.readAsciiString();
if (info->version >= K_TRIGGERS_VERSION_4) {
layerName = file.readAsciiString();
}
triggerID = file.readInt();
isWater = false;
if (info->version >= K_TRIGGERS_VERSION_2) {
isWater = file.readByte();
}
isRiver = false;
riverStart = 0;
if (info->version >= K_TRIGGERS_VERSION_3) {
isRiver = file.readByte();
riverStart = file.readInt();
}
numPoints = file.readInt();
PolygonTrigger *pTrig = newInstance(PolygonTrigger)(numPoints+1);
pTrig->setTriggerName(triggerName);
if (info->version >= K_TRIGGERS_VERSION_4) {
pTrig->setLayerName(layerName);
}
pTrig->setWaterArea(isWater);
pTrig->setRiver(isRiver);
pTrig->setRiverStart(riverStart);
pTrig->m_triggerID = triggerID;
if (triggerID > maxTriggerId) {
maxTriggerId = triggerID;
}
Int i;
for (i=0; i<numPoints; i++) {
loc.x = file.readInt();
loc.y = file.readInt();
loc.z = file.readInt();
pTrig->addPoint(loc);
}
if (numPoints<2) {
DEBUG_LOG(("Deleting polygon trigger '%s' with %d points.\n",
pTrig->getTriggerName().str(), numPoints));
pTrig->deleteInstance();
continue;
}
if (pPrevTrig) {
pPrevTrig->setNextPoly(pTrig);
} else {
PolygonTrigger::addPolygonTrigger(pTrig);
}
pPrevTrig = pTrig;
}
if (info->version == K_TRIGGERS_VERSION_1)
{
// before water areas existed, so create a default one.
PolygonTrigger *pTrig = newInstance(PolygonTrigger)(4);
pTrig->setWaterArea(true);
#ifdef _DEBUG
pTrig->setTriggerName("AutoAddedWaterAreaTrigger");
#endif
pTrig->m_triggerID = maxTriggerId++;
loc.x = -30*MAP_XY_FACTOR;
loc.y = -30*MAP_XY_FACTOR;
loc.z = 7; // The old water position.
pTrig->addPoint(loc);
loc.x = 30*MAP_XY_FACTOR + TheGlobalData->m_waterExtentX;
pTrig->addPoint(loc);
loc.y = 30*MAP_XY_FACTOR + TheGlobalData->m_waterExtentY;
pTrig->addPoint(loc);
loc.x = -30*MAP_XY_FACTOR;
pTrig->addPoint(loc);
if (pPrevTrig) {
pPrevTrig->setNextPoly(pTrig);
} else {
PolygonTrigger::addPolygonTrigger(pTrig);
}
pPrevTrig = pTrig;
}
s_currentID = maxTriggerId+1;
DEBUG_ASSERTCRASH(file.atEndOfChunk(), ("Incorrect data file length."));
return true;
}
/**
* PolygonTrigger::WritePolygonTriggersDataChunk - Writes a Polygon triggers chunk.
* Format is the newer CHUNKY format.
* See PolygonTrigger::ParsePolygonTriggersDataChunk for the reader.
* Input: DataChunkInput
*
*/
void PolygonTrigger::WritePolygonTriggersDataChunk(DataChunkOutput &chunkWriter)
{
chunkWriter.openDataChunk("PolygonTriggers", K_TRIGGERS_VERSION_4);
PolygonTrigger *pTrig;
Int count = 0;
for (pTrig=PolygonTrigger::getFirstPolygonTrigger(); pTrig; pTrig = pTrig->getNext()) {
count++;
}
chunkWriter.writeInt(count);
for (pTrig=PolygonTrigger::getFirstPolygonTrigger(); pTrig; pTrig = pTrig->getNext()) {
chunkWriter.writeAsciiString(pTrig->getTriggerName());
chunkWriter.writeAsciiString(pTrig->getLayerName());
chunkWriter.writeInt(pTrig->getID());
chunkWriter.writeByte(pTrig->isWaterArea());
chunkWriter.writeByte(pTrig->isRiver());
chunkWriter.writeInt(pTrig->getRiverStart());
chunkWriter.writeInt(pTrig->getNumPoints());
Int i;
for (i=0; i<pTrig->getNumPoints(); i++) {
ICoord3D loc = *pTrig->getPoint(i);
chunkWriter.writeInt( loc.x);
chunkWriter.writeInt( loc.y);
chunkWriter.writeInt( loc.z);
}
}
chunkWriter.closeDataChunk();
}
/**
PolygonTrigger::updateBounds - Updates the bounds.
*/
void PolygonTrigger::updateBounds(void) const
{
const Int BIG_INT=0x7ffff0;
m_bounds.lo.x = m_bounds.lo.y = BIG_INT;
m_bounds.hi.x = m_bounds.hi.y = -BIG_INT;
Int i;
for (i=0; i<m_numPoints; i++) {
if (m_points[i].x < m_bounds.lo.x) m_bounds.lo.x = m_points[i].x;
if (m_points[i].y < m_bounds.lo.y) m_bounds.lo.y = m_points[i].y;
if (m_points[i].x > m_bounds.hi.x) m_bounds.hi.x = m_points[i].x;
if (m_points[i].y > m_bounds.hi.y) m_bounds.hi.y = m_points[i].y;
}
m_boundsNeedsUpdate = 0;
Real halfWidth = (m_bounds.hi.x - m_bounds.lo.x) / 2.0f;
Real halfHeight = (m_bounds.hi.y + m_bounds.lo.y) / 2.0f;
m_radius = sqrt(halfHeight*halfHeight + halfWidth*halfWidth);
}
/**
PolygonTrigger::addPolygonTrigger adds a trigger to the list of triggers.
*/
void PolygonTrigger::addPolygonTrigger(PolygonTrigger *pTrigger)
{
for (PolygonTrigger *pTrig=getFirstPolygonTrigger(); pTrig; pTrig = pTrig->getNext()) {
DEBUG_ASSERTCRASH(pTrig != pTrigger, ("Attempting to add trigger already in list."));
if (pTrig==pTrigger) return;
}
pTrigger->m_nextPolygonTrigger = ThePolygonTriggerListPtr;
ThePolygonTriggerListPtr = pTrigger;
}
/**
PolygonTrigger::removePolygonTrigger removes a trigger to the list of
triggers. note - does NOT delete pTrigger.
*/
void PolygonTrigger::removePolygonTrigger(PolygonTrigger *pTrigger)
{
PolygonTrigger *pPrev = NULL;
for (PolygonTrigger *pTrig=getFirstPolygonTrigger(); pTrig; pTrig = pTrig->getNext()) {
if (pTrig==pTrigger) break;
pPrev = pTrig;
}
DEBUG_ASSERTCRASH(pTrig, ("Attempting to remove a polygon not in the list."));
if (pTrig) {
if (pPrev) {
DEBUG_ASSERTCRASH(pTrigger==pPrev->m_nextPolygonTrigger, ("Logic errror. jba."));
pPrev->m_nextPolygonTrigger = pTrig->m_nextPolygonTrigger;
} else {
DEBUG_ASSERTCRASH(pTrigger==ThePolygonTriggerListPtr, ("Logic errror. jba."));
ThePolygonTriggerListPtr = pTrig->m_nextPolygonTrigger;
}
}
pTrigger->m_nextPolygonTrigger = NULL;
}
/**
PolygonTrigger::deleteTriggers Deletes list of triggers.
*/
void PolygonTrigger::deleteTriggers(void)
{
PolygonTrigger *pList = ThePolygonTriggerListPtr;
ThePolygonTriggerListPtr = NULL;
s_currentID = 1;
pList->deleteInstance();
}
/**
PolygonTrigger::addPoint adds a point at the end of the polygon.
NOTE: It is expected that this will only get called in the editor, as in the game
the poly triggers don't change.
*/
void PolygonTrigger::addPoint(const ICoord3D &point)
{
DEBUG_ASSERTCRASH(m_numPoints <= m_sizePoints, ("Invalid m_numPoints."));
if (m_numPoints == m_sizePoints) {
reallocate();
}
m_points[m_numPoints] = point;
m_numPoints++;
m_boundsNeedsUpdate = true;
}
/**
PolygonTrigger::setPoint sets the point at index ndx.
NOTE: It is expected that this will only get called in the editor, as in the game
the poly triggers don't change.
*/
void PolygonTrigger::setPoint(const ICoord3D &point, Int ndx)
{
DEBUG_ASSERTCRASH(ndx>=0 && ndx <= m_numPoints, ("Invalid ndx."));
if (ndx<0) return;
if (ndx == m_numPoints) { // we are setting first available unused point
addPoint(point);
return;
}
if (ndx>m_numPoints) { // Can't skip points.
return;
}
m_points[ndx] = point;
m_boundsNeedsUpdate = true;
}
/**
PolygonTrigger::insertPoint .
NOTE: It is expected that this will only get called in the editor, as in the game
the poly triggers don't change.
*/
void PolygonTrigger::insertPoint(const ICoord3D &point, Int ndx)
{
DEBUG_ASSERTCRASH(ndx>=0 && ndx <= m_numPoints, ("Invalid ndx."));
if (ndx<0) return;
if (ndx == m_numPoints) { // we are setting first available unused point
addPoint(point);
return;
}
if (m_numPoints == m_sizePoints) {
reallocate();
}
Int i;
for (i=m_numPoints; i>ndx; i--) {
m_points[i] = m_points[i-1];
}
m_points[ndx] = point;
m_numPoints++;
m_boundsNeedsUpdate = true;
}
/**
PolygonTrigger::deletePoint .
NOTE: It is expected that this will only get called in the editor, as in the game
the poly triggers don't change.
*/
void PolygonTrigger::deletePoint(Int ndx)
{
DEBUG_ASSERTCRASH(ndx>=0 && ndx < m_numPoints, ("Invalid ndx."));
if (ndx<0 || ndx>=m_numPoints) return;
Int i;
for (i=ndx; i<m_numPoints-1; i++) {
m_points[i] = m_points[i+1];
}
m_numPoints--;
m_boundsNeedsUpdate = true;
}
void PolygonTrigger::getCenterPoint(Coord3D* pOutCoord) const
{
DEBUG_ASSERTCRASH(pOutCoord != NULL, ("pOutCoord was null. Non-Fatal, but shouldn't happen."));
if (!pOutCoord) {
return;
}
if (m_boundsNeedsUpdate) {
updateBounds();
}
(*pOutCoord).x = (m_bounds.lo.x + m_bounds.hi.x) / 2.0f;
(*pOutCoord).y = (m_bounds.lo.y + m_bounds.hi.y) / 2.0f;
(*pOutCoord).z = TheTerrainLogic->getGroundHeight(pOutCoord->x, pOutCoord->y);
}
Real PolygonTrigger::getRadius(void) const
{
if (m_boundsNeedsUpdate) {
updateBounds();
}
return m_radius;
}
/**
PolygonTrigger - pointInTrigger.
*/
Bool PolygonTrigger::pointInTrigger(ICoord3D &point) const
{
if (m_boundsNeedsUpdate) {
updateBounds();
}
if (point.x < m_bounds.lo.x) return false;
if (point.y < m_bounds.lo.y) return false;
if (point.x > m_bounds.hi.x) return false;
if (point.y > m_bounds.hi.y) return false;
Bool inside = false;
Int i;
for (i=0; i<m_numPoints; i++) {
ICoord3D pt1 = m_points[i];
ICoord3D pt2;
if (i==m_numPoints-1) {
pt2 = m_points[0];
} else {
pt2 = m_points[i+1];
}
if (pt1.y == pt2.y) {
continue; // ignore horizontal lines.
}
if (pt1.y < point.y && pt2.y < point.y) continue;
if (pt1.y >= point.y && pt2.y >= point.y) continue;
if (pt1.x<point.x && pt2.x < point.x) continue;
// Line segment crosses ray from point x->infinity.
Int dy = pt2.y-pt1.y;
Int dx = pt2.x-pt1.x;
Real intersectionX = pt1.x + (dx * (point.y-pt1.y)) / ((Real)dy);
if (intersectionX >= point.x) {
inside = !inside;
}
}
return inside;
}
// ------------------------------------------------------------------------------------------------
const WaterHandle* PolygonTrigger::getWaterHandle(void) const
{
if( isWaterArea() )
return &m_waterHandle;
return NULL; // this polygon trigger is not a water area
}
Bool PolygonTrigger::isValid(void) const
{
if (m_numPoints == 0) {
return FALSE;
}
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void PolygonTrigger::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void PolygonTrigger::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// number of data points
xfer->xferInt( &m_numPoints );
// xfer all data points
ICoord3D *point;
for( Int i = 0; i < m_numPoints; ++i )
{
// get this point
point = &m_points[ i ];
// xfer point
xfer->xferICoord3D( point );
} // end for, i
// bounds
xfer->xferIRegion2D( &m_bounds );
// radius
xfer->xferReal( &m_radius );
// bounds need update
xfer->xferBool( &m_boundsNeedsUpdate );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void PolygonTrigger::loadPostProcess( void )
{
} // end loadPostProcess

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,153 @@
/*
** 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: ArmorTemplate.cpp ///////////////////////////////////////////////////////////////////////////////
// Author: Steven Johnson, November 2001
// Desc: ArmorTemplate descriptions
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/INI.h"
#include "Common/ThingFactory.h"
#include "GameLogic/Armor.h"
#include "GameLogic/Damage.h"
///////////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC DATA ////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
ArmorStore* TheArmorStore = NULL; ///< the ArmorTemplate store definition
///////////////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE DATA ///////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
ArmorTemplate::ArmorTemplate()
{
clear();
}
//-------------------------------------------------------------------------------------------------
void ArmorTemplate::clear()
{
for (int i = 0; i < DAMAGE_NUM_TYPES; i++)
{
m_damageCoefficient[i] = 1.0f;
}
}
//-------------------------------------------------------------------------------------------------
Real ArmorTemplate::adjustDamage(DamageType t, Real damage) const
{
if (t == DAMAGE_UNRESISTABLE)
return damage;
if (t == DAMAGE_SUBDUAL_UNRESISTABLE)
return damage;
damage *= m_damageCoefficient[t];
if (damage < 0.0f)
damage = 0.0f;
return damage;
}
//-------------------------------------------------------------------------------------------Static
/*static*/ void ArmorTemplate::parseArmorCoefficients( INI* ini, void *instance, void* /* store */, const void* userData )
{
ArmorTemplate* self = (ArmorTemplate*) instance;
const char* damageName = ini->getNextToken();
Real pct = INI::scanPercentToReal(ini->getNextToken());
if (stricmp(damageName, "Default") == 0)
{
for (Int i = 0; i < DAMAGE_NUM_TYPES; i++)
{
self->m_damageCoefficient[i] = pct;
}
return;
}
DamageType dt = (DamageType)DamageTypeFlags::getSingleBitFromName(damageName);
self->m_damageCoefficient[dt] = pct;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ArmorStore::ArmorStore()
{
m_armorTemplates.clear();
}
//-------------------------------------------------------------------------------------------------
ArmorStore::~ArmorStore()
{
m_armorTemplates.clear();
}
//-------------------------------------------------------------------------------------------------
const ArmorTemplate* ArmorStore::findArmorTemplate(AsciiString name) const
{
NameKeyType namekey = TheNameKeyGenerator->nameToKey(name);
ArmorTemplateMap::const_iterator it = m_armorTemplates.find(namekey);
if (it == m_armorTemplates.end())
{
return NULL;
}
else
{
return &(*it).second;
}
}
//-------------------------------------------------------------------------------------------------
/*static */ void ArmorStore::parseArmorDefinition(INI *ini)
{
static const FieldParse myFieldParse[] =
{
{ "Armor", ArmorTemplate::parseArmorCoefficients, NULL, 0 }
};
const char *c = ini->getNextToken();
NameKeyType key = TheNameKeyGenerator->nameToKey(c);
ArmorTemplate& armorTmpl = TheArmorStore->m_armorTemplates[key];
armorTmpl.clear();
ini->initFromINI(&armorTmpl, myFieldParse);
}
//-------------------------------------------------------------------------------------------------
/*static*/ void INI::parseArmorDefinition(INI *ini)
{
ArmorStore::parseArmorDefinition(ini);
}

View File

@@ -0,0 +1,381 @@
/*
** 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: AutoHealBehavior.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/Player.h"
#include "Common/Xfer.h"
#include "GameClient/ParticleSys.h"
#include "GameClient/Anim2D.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/Module/AutoHealBehavior.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
struct AutoHealPlayerScanHelper
{
KindOfMaskType m_kindOfToTest;
KindOfMaskType m_forbiddenKindOf;
Object *m_theHealer;
ObjectPointerList *m_objectList;
Bool m_skipSelfForHealing;
};
static void checkForAutoHeal( Object *testObj, void *userData )
{
AutoHealPlayerScanHelper *helper = (AutoHealPlayerScanHelper*)userData;
ObjectPointerList *listToAddTo = helper->m_objectList;
if( testObj->isEffectivelyDead() )
return;
if( testObj->getControllingPlayer() != helper->m_theHealer->getControllingPlayer() )
return;
if( testObj->isOffMap() )
return;
if( helper->m_skipSelfForHealing && testObj == helper->m_theHealer )
return;
if( !testObj->isAnyKindOf(helper->m_kindOfToTest) )
return;
if( testObj->isAnyKindOf( helper->m_forbiddenKindOf ) )
return;
if( testObj->getBodyModule()->getHealth() >= testObj->getBodyModule()->getMaxHealth() )
return;
listToAddTo->push_back(testObj);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
AutoHealBehavior::AutoHealBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
{
const AutoHealBehaviorModuleData *d = getAutoHealBehaviorModuleData();
m_radiusParticleSystemID = INVALID_PARTICLE_SYSTEM_ID;
m_soonestHealFrame = 0;
m_stopped = false;
Object *obj = getObject();
{
if( d->m_radiusParticleSystemTmpl )
{
ParticleSystem *particleSystem;
particleSystem = TheParticleSystemManager->createParticleSystem( d->m_radiusParticleSystemTmpl );
if( particleSystem )
{
particleSystem->setPosition( obj->getPosition() );
m_radiusParticleSystemID = particleSystem->getSystemID();
}
}
}
if (d->m_initiallyActive)
{
giveSelfUpgrade();
// start these guys with random phasings so that we don't
// have all of 'em check on the same frame.
UnsignedInt delay = getAutoHealBehaviorModuleData()->m_healingDelay;
setWakeFrame(getObject(), UPDATE_SLEEP(GameLogicRandomValue(1, delay)));
}
else
{
setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER);
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
AutoHealBehavior::~AutoHealBehavior( void )
{
if( m_radiusParticleSystemID != INVALID_PARTICLE_SYSTEM_ID )
TheParticleSystemManager->destroyParticleSystemByID( m_radiusParticleSystemID );
}
//-------------------------------------------------------------------------------------------------
void AutoHealBehavior::stopHealing()
{
m_stopped = true;
m_soonestHealFrame = FOREVER;
setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER);
}
//-------------------------------------------------------------------------------------------------
void AutoHealBehavior::undoUpgrade()
{
m_soonestHealFrame = 0;
setUpgradeExecuted( FALSE );
}
//-------------------------------------------------------------------------------------------------
/** Damage has been dealt, this is an opportunity to reach to that damage */
//-------------------------------------------------------------------------------------------------
void AutoHealBehavior::onDamage( DamageInfo *damageInfo )
{
if (m_stopped)
return;
const AutoHealBehaviorModuleData *d = getAutoHealBehaviorModuleData();
if (isUpgradeActive() && d->m_radius == 0.0f)
{
// if this is nonzero, getting damaged resets our healing process. so go to
// sleep for this long.
if (d->m_startHealingDelay > 0)
{
setWakeFrame(getObject(), UPDATE_SLEEP(d->m_startHealingDelay));
}
else if( TheGameLogic->getFrame() > m_soonestHealFrame )
{
// We can only force an immediate wake if we are ready to heal. Otherwise we will
// heal on a timer AND at every damage input.
setWakeFrame(getObject(), UPDATE_SLEEP_NONE);
}
}
}
//-------------------------------------------------------------------------------------------------
/** The update callback. */
//-------------------------------------------------------------------------------------------------
UpdateSleepTime AutoHealBehavior::update( void )
{
if (m_stopped)
return UPDATE_SLEEP_FOREVER;
Object *obj = getObject();
const AutoHealBehaviorModuleData *d = getAutoHealBehaviorModuleData();
// do not heal if our status bit is not on.
// do not heal if our status is effectively dead. There ain't no coming back, man!
if (!isUpgradeActive() || obj->isEffectivelyDead())
{
DEBUG_ASSERTCRASH(isUpgradeActive(), ("hmm, this should not be possible"));
return UPDATE_SLEEP_FOREVER;
}
//DEBUG_LOG(("doing auto heal %d\n",TheGameLogic->getFrame()));
if( d->m_affectsWholePlayer )
{
// Even newer system, I can ignore radius and iterate objects on the owning player. Faster than scanning range 10,000,000
ObjectPointerList objectsToHeal;
Player *owningPlayer = getObject()->getControllingPlayer();
if( owningPlayer )
{
AutoHealPlayerScanHelper helper;
helper.m_kindOfToTest = d->m_kindOf;
helper.m_forbiddenKindOf = d->m_forbiddenKindOf;
helper.m_objectList = &objectsToHeal;
helper.m_theHealer = getObject();
helper.m_skipSelfForHealing = d->m_skipSelfForHealing;
// Smack all objects with this function, and we will end up with a list of Objects deserving of pulseHealObject
owningPlayer->iterateObjects( checkForAutoHeal, &helper );
for( ObjectPointerListIterator iter = objectsToHeal.begin(); iter != objectsToHeal.end(); ++iter )
{
pulseHealObject(*iter);
}
objectsToHeal.clear();
}
return UPDATE_SLEEP(d->m_healingDelay);
}
else if( d->m_radius == 0.0f )
{
//ORIGINAL SYSTEM -- JUST HEAL SELF!
// do not heal if we are at max health already
BodyModuleInterface *body = obj->getBodyModule();
if( body->getHealth() < body->getMaxHealth() )
{
pulseHealObject( obj );
return UPDATE_SLEEP(d->m_healingDelay);
}
else
{
// go to sleep forever -- we'll wake back up when we are damaged again
return UPDATE_SLEEP_FOREVER;
}
}
else
{
//EXPANDED SYSTEM -- HEAL FRIENDLIES IN RADIUS
// setup scan filters
PartitionFilterRelationship relationship( obj, PartitionFilterRelationship::ALLOW_ALLIES );
PartitionFilterSameMapStatus filterMapStatus(obj);
PartitionFilterAlive filterAlive;
PartitionFilter *filters[] = { &relationship, &filterAlive, &filterMapStatus, NULL };
// scan objects in our region
ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( obj->getPosition(), d->m_radius, FROM_CENTER_2D, filters );
MemoryPoolObjectHolder hold( iter );
for( obj = iter->first(); obj; obj = iter->next() )
{
// do not heal if we are at max health already
BodyModuleInterface *body = obj->getBodyModule();
if( body->getHealth() < body->getMaxHealth() )
{
if( obj->isAnyKindOf( d->m_kindOf ) && !obj->isAnyKindOf( d->m_forbiddenKindOf ) )
{
if( !d->m_skipSelfForHealing || obj != getObject() )
{
pulseHealObject( obj );
if( d->m_singleBurst && TheGameLogic->getDrawIconUI() )
{
if( TheAnim2DCollection && TheGlobalData->m_getHealedAnimationName.isEmpty() == FALSE )
{
Anim2DTemplate *animTemplate = TheAnim2DCollection->findTemplate( TheGlobalData->m_getHealedAnimationName );
if ( animTemplate )
{
Coord3D iconPosition;
iconPosition.set(obj->getPosition()->x,
obj->getPosition()->y,
obj->getPosition()->z + obj->getGeometryInfo().getMaxHeightAbovePosition() );
TheInGameUI->addWorldAnimation( animTemplate, &iconPosition, WORLD_ANIM_FADE_ON_EXPIRE,
TheGlobalData->m_getHealedAnimationDisplayTimeInSeconds,
TheGlobalData->m_getHealedAnimationZRisePerSecond);
}
}
}
}
}
}
} // end for obj
return UPDATE_SLEEP( d->m_singleBurst ? UPDATE_SLEEP_FOREVER : d->m_healingDelay );
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void AutoHealBehavior::pulseHealObject( Object *obj )
{
if (m_stopped)
return;
const AutoHealBehaviorModuleData *data = getAutoHealBehaviorModuleData();
if ( data->m_radius == 0.0f )
obj->attemptHealing(data->m_healingAmount, getObject());
else
obj->attemptHealingFromSoleBenefactor( data->m_healingAmount, getObject(), data->m_healingDelay );
if( data->m_unitHealPulseParticleSystemTmpl )
{
ParticleSystem *system = TheParticleSystemManager->createParticleSystem( data->m_unitHealPulseParticleSystemTmpl );
if( system )
{
system->setPosition( obj->getPosition() );
}
}
m_soonestHealFrame = TheGameLogic->getFrame() + data->m_healingDelay;// In case onDamage tries to wake us up early
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void AutoHealBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
// extend base class
UpgradeMux::upgradeMuxCRC( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void AutoHealBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// extend base class
UpgradeMux::upgradeMuxXfer( xfer );
// particle system id
xfer->xferUser( &m_radiusParticleSystemID, sizeof( ParticleSystemID ) );
// Timer safety
xfer->xferUnsignedInt( &m_soonestHealFrame );
// stopped
xfer->xferBool( &m_stopped );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void AutoHealBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
// extend base class
UpgradeMux::upgradeMuxLoadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,326 @@
/*
** 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: BattleBusSlowDeathBehavior.cpp /////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, June 2003
// Desc: Death for the Battle Bus. Can do a fake slow death before the real one if triggered intentionally
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameLogic/Module/BattleBusSlowDeathBehavior.h"
#include "Common/ModelState.h"
#include "GameClient/FXList.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectCreationList.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
enum
{
GROUND_CHECK_DELAY = 10, ///< Check for colliding with the ground only after this long, to prevent hitting on the way up
EMPTY_HULK_CHECK_DELAY = 15 ///< Check for empty hulk every this often instead of every frame
};
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
BattleBusSlowDeathBehaviorModuleData::BattleBusSlowDeathBehaviorModuleData( void )
{
m_fxStartUndeath = NULL;
m_oclStartUndeath = NULL;
m_fxHitGround = NULL;
m_oclHitGround = NULL;
m_throwForce = 1.0f;
m_percentDamageToPassengers = 0.0f;
m_emptyHulkDestructionDelay = 0;
} // end BattleBusSlowDeathBehaviorModuleData
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void BattleBusSlowDeathBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
SlowDeathBehaviorModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "FXStartUndeath", INI::parseFXList, NULL, offsetof( BattleBusSlowDeathBehaviorModuleData, m_fxStartUndeath ) },
{ "OCLStartUndeath", INI::parseObjectCreationList, NULL, offsetof( BattleBusSlowDeathBehaviorModuleData, m_oclStartUndeath ) },
{ "FXHitGround", INI::parseFXList, NULL, offsetof( BattleBusSlowDeathBehaviorModuleData, m_fxHitGround ) },
{ "OCLHitGround", INI::parseObjectCreationList, NULL, offsetof( BattleBusSlowDeathBehaviorModuleData, m_oclHitGround ) },
{ "ThrowForce", INI::parseReal, NULL, offsetof( BattleBusSlowDeathBehaviorModuleData, m_throwForce ) },
{ "PercentDamageToPassengers", INI::parsePercentToReal, NULL, offsetof( BattleBusSlowDeathBehaviorModuleData, m_percentDamageToPassengers ) },
{ "EmptyHulkDestructionDelay", INI::parseDurationUnsignedInt, NULL, offsetof( BattleBusSlowDeathBehaviorModuleData, m_emptyHulkDestructionDelay ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
BattleBusSlowDeathBehavior::BattleBusSlowDeathBehavior( Thing *thing, const ModuleData *moduleData )
: SlowDeathBehavior( thing, moduleData )
{
m_isRealDeath = FALSE;
m_isInFirstDeath = FALSE;
m_groundCheckFrame = 0;
m_penaltyDeathFrame = 0;
} // end BattleBusSlowDeathBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
BattleBusSlowDeathBehavior::~BattleBusSlowDeathBehavior( void )
{
} // end ~BattleBusSlowDeathBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void BattleBusSlowDeathBehavior::onDie( const DamageInfo *damageInfo )
{
m_isRealDeath = TRUE;// Set beforehand because onDie could result in picking us to beginSlowDeath
m_isInFirstDeath = FALSE; // and clear this incase we died while in the alternate death
SlowDeathBehavior::onDie(damageInfo);
} // end onDie
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void BattleBusSlowDeathBehavior::beginSlowDeath( const DamageInfo *damageInfo )
{
Object *me = getObject();
const BattleBusSlowDeathBehaviorModuleData * data = getBattleBusSlowDeathBehaviorModuleData();
if( !m_isRealDeath )
{
// We can intercept and do our extra slow death if this is not from a real onDie
m_isInFirstDeath = TRUE;
m_groundCheckFrame = TheGameLogic->getFrame() + GROUND_CHECK_DELAY;
// First do the special effects
FXList::doFXObj(data->m_fxStartUndeath, me );
ObjectCreationList::create(data->m_oclStartUndeath, me, NULL );
if( me->getAI() )
{
// Then stop what we were doing
me->getAI()->aiIdle(CMD_FROM_AI);
}
if( me->getPhysics() )
{
// Then stop physically
me->getPhysics()->clearAcceleration();
me->getPhysics()->scrubVelocity2D(0);
// Then get chucked in the air
Coord3D throwForce;
throwForce.x = 0;
throwForce.y = 0;
throwForce.z = data->m_throwForce;
me->getPhysics()->applyShock(&throwForce);
me->getPhysics()->applyRandomRotation();
}
// And finally hit those inside for some damage
if( me->getContain() )
me->getContain()->processDamageToContained(data->m_percentDamageToPassengers);
setWakeFrame(getObject(), UPDATE_SLEEP_NONE);
}
else
{
// If a real death, we aren't allowed to do anything
SlowDeathBehavior::beginSlowDeath( damageInfo );
}
} // end beginSlowDeath
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime BattleBusSlowDeathBehavior::update( void )
{
Object *me = getObject();
const BattleBusSlowDeathBehaviorModuleData * data = getBattleBusSlowDeathBehaviorModuleData();
if( m_isInFirstDeath )
{
// First death means we are doing our throw up in the air conversion
if( (m_groundCheckFrame< TheGameLogic->getFrame() ) && !me->isAboveTerrain() )
{
// We're done since we hit the ground
m_isInFirstDeath = FALSE;
// Do the special FX
FXList::doFXObj(data->m_fxHitGround, me );
ObjectCreationList::create(data->m_oclHitGround, me, NULL );
me->setModelConditionState(MODELCONDITION_SECOND_LIFE);
// And stop us forever
if( me->getAI() )
{
// Stop doing anything again (could try to move while in air)
me->getAI()->aiIdle(CMD_FROM_AI);
}
if( me->getPhysics() )
{
// And stop physically again
me->getPhysics()->clearAcceleration();
me->getPhysics()->scrubVelocity2D(0);
}
me->setDisabled(DISABLED_HELD);
// We can only sleep if we don't have to watch out for being empty.
if( data->m_emptyHulkDestructionDelay == 0 )
return UPDATE_SLEEP_FOREVER;
else
return UPDATE_SLEEP_NONE; // None, so we check for empty as soon as possible
}
return UPDATE_SLEEP_NONE;// None, since we are waiting to be able to check for ground collision
}
else if( m_isRealDeath )
{
// If a real death, we aren't allowed to do anything
return SlowDeathBehavior::update();
}
else
{
// If neither death is active, we must be awake to check for emptiness
const ContainModuleInterface *contain = me->getContain();
// Safety, no need to be awake if no special case to wait for
if( contain == NULL )
return UPDATE_SLEEP_FOREVER;
if( m_penaltyDeathFrame != 0 )
{
// We have been timed for death, see if we have re-filled first
if( contain->getContainCount() > 0 )
{
m_penaltyDeathFrame = 0;
return UPDATE_SLEEP(EMPTY_HULK_CHECK_DELAY);
}
else if( TheGameLogic->getFrame() > m_penaltyDeathFrame )
{
// No salvation, see if the timer is up to be killed. Penalty prevents effects, Extra 4 prevents this die module from recalling.
// Can't use Suicided death because a Terrorist actually inflicts Suicide deaths on others.
me->kill(DAMAGE_PENALTY, DEATH_EXTRA_4);
return UPDATE_SLEEP_FOREVER;// Forever, since we are dead and some other death module is in charge
}
else
{
return UPDATE_SLEEP(EMPTY_HULK_CHECK_DELAY);
}
}
else
{
// We are not marked for death, so see if we should be
if( contain->getContainCount() == 0 )
{
m_penaltyDeathFrame = TheGameLogic->getFrame() + data->m_emptyHulkDestructionDelay;
}
return UPDATE_SLEEP(EMPTY_HULK_CHECK_DELAY);// Stay awake regardless
}
}
} // end update
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void BattleBusSlowDeathBehavior::crc( Xfer *xfer )
{
// extend base class
SlowDeathBehavior::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void BattleBusSlowDeathBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
SlowDeathBehavior::xfer( xfer );
xfer->xferBool( &m_isRealDeath );
xfer->xferBool( &m_isInFirstDeath );
xfer->xferUnsignedInt( &m_groundCheckFrame );
xfer->xferUnsignedInt( &m_penaltyDeathFrame );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void BattleBusSlowDeathBehavior::loadPostProcess( void )
{
// extend base class
SlowDeathBehavior::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,73 @@
/*
** 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: BehaviorModule.cpp ///////////////////////////////////////////////////////////////////////
// Author: Colin Day, September 2002
// Desc: Implementaion for anything in the base BehaviorModule
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/BehaviorModule.h"
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void BehaviorModule::crc( Xfer *xfer )
{
// call base class
ObjectModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void BehaviorModule::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// call base class
ObjectModule::xfer( xfer );
} // xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void BehaviorModule::loadPostProcess( void )
{
// call base class
ObjectModule::loadPostProcess();
} // end loadPostProcess

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,362 @@
/*
** 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: BridgeScaffoldBehavior.cpp ///////////////////////////////////////////////////////////////
// Author: Colin Day, September 2002
// Desc: Bridge scaffold
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/Xfer.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/BridgeScaffoldBehavior.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
BridgeScaffoldBehavior::BridgeScaffoldBehavior( Thing *thing, const ModuleData *moduleData )
: UpdateModule( thing, moduleData )
{
m_targetMotion = STM_STILL;
m_createPos.zero();
m_riseToPos.zero();
m_buildPos.zero();
m_targetPos.zero();
m_lateralSpeed = 1.0f;
m_verticalSpeed = 1.0f;
} // end BridgeScaffoldBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
BridgeScaffoldBehavior::~BridgeScaffoldBehavior( void )
{
} // end ~BridgeScaffoldBehavior
// ------------------------------------------------------------------------------------------------
/** Set all of the target positions that we're care about as a moving scaffold object */
// ------------------------------------------------------------------------------------------------
void BridgeScaffoldBehavior::setPositions( const Coord3D *createPos,
const Coord3D *riseToPos,
const Coord3D *buildPos )
{
m_createPos = *createPos;
m_riseToPos = *riseToPos;
m_buildPos = *buildPos;
} // end setPositions
// ------------------------------------------------------------------------------------------------
/** Set us moving to the right target position for the requested motion type */
// ------------------------------------------------------------------------------------------------
void BridgeScaffoldBehavior::setMotion( ScaffoldTargetMotion targetMotion )
{
// save the target motion type
m_targetMotion = targetMotion;
// given the target motion, pick a destination target
switch( m_targetMotion )
{
// --------------------------------------------------------------------------------------------
case STM_RISE:
case STM_TEAR_DOWN_ACROSS:
m_targetPos = m_riseToPos;
break;
// --------------------------------------------------------------------------------------------
case STM_BUILD_ACROSS:
m_targetPos = m_buildPos;
break;
// --------------------------------------------------------------------------------------------
case STM_SINK:
m_targetPos = m_createPos;
break;
} // end switch
} // end setMotion
// ------------------------------------------------------------------------------------------------
/** Whatever our current state of motion is, reverse it */
// ------------------------------------------------------------------------------------------------
void BridgeScaffoldBehavior::reverseMotion( void )
{
switch( m_targetMotion )
{
case STM_STILL:
setMotion( STM_TEAR_DOWN_ACROSS );
break;
case STM_RISE:
setMotion( STM_SINK );
break;
case STM_BUILD_ACROSS:
setMotion( STM_TEAR_DOWN_ACROSS );
break;
case STM_TEAR_DOWN_ACROSS:
setMotion( STM_BUILD_ACROSS );
break;
case STM_SINK:
setMotion( STM_RISE );
break;
} // end switch
} // end reverseMotion
// ------------------------------------------------------------------------------------------------
/** The update method */
// ------------------------------------------------------------------------------------------------
UpdateSleepTime BridgeScaffoldBehavior::update( void )
{
// do nothing if we're not in motion
if( m_targetMotion == STM_STILL )
return UPDATE_SLEEP_NONE;
// get our info
Object *us = getObject();
const Coord3D *ourPos = us->getPosition();
// compute direction vector from our position to the target position
Coord3D dirV;
dirV.x = m_targetPos.x - ourPos->x;
dirV.y = m_targetPos.y - ourPos->y;
dirV.z = m_targetPos.z - ourPos->z;
// use normalized direction vector "v" to do the pulling movement
Coord3D v = dirV;
v.normalize();
// depending on our motion type, we move at different speeds
Real topSpeed = 1.0f;
Coord3D *start, *end;
switch( m_targetMotion )
{
case STM_RISE:
topSpeed = m_verticalSpeed;
start = &m_createPos;
end = &m_riseToPos;
break;
case STM_SINK:
topSpeed = m_verticalSpeed;
start = &m_riseToPos;
end = &m_createPos;
break;
case STM_BUILD_ACROSS:
topSpeed = m_lateralSpeed;
start = &m_riseToPos;
end = &m_buildPos;
break;
case STM_TEAR_DOWN_ACROSS:
topSpeed = m_lateralSpeed;
start = &m_buildPos;
end = &m_riseToPos;
break;
} // end switch
// adjust speed so it's slower at the end of motion
Coord3D speedVector;
speedVector.x = end->x - start->x;
speedVector.y = end->y - start->y;
speedVector.z = end->z - start->z;
Real totalDistance = speedVector.length() * 0.25f;
speedVector.x = end->x - ourPos->x;
speedVector.y = end->y - ourPos->y;
speedVector.z = end->z - ourPos->z;
Real ourDistance = speedVector.length();
Real speed = (ourDistance / totalDistance) * topSpeed;
Real minSpeed = topSpeed * 0.08f;
if( speed < minSpeed )
speed = minSpeed;
if( speed > topSpeed )
speed = topSpeed;
//
// make sure that speed can't get so incredibly small that we never finish our
// movement no matter what the speed and distance are
//
if( speed < 0.001f )
speed = 0.001f;
// compute the new position given the speed
Coord3D newPos;
newPos.x = v.x * speed + ourPos->x;
newPos.y = v.y * speed + ourPos->y;
newPos.z = v.z * speed + ourPos->z;
//
// will this new position push us beyond our target destination, we will take the vector
// from the new position to the destination and the vector from our current present position
// tot he destination and dot them togehter ... if the result is < 0 then we have will
// overshoot the distance if we use the new position
//
Coord3D tooFarVector;
tooFarVector.x = m_targetPos.x - newPos.x;
tooFarVector.y = m_targetPos.y - newPos.y;
tooFarVector.z = m_targetPos.z - newPos.z;
if( tooFarVector.x * dirV.x + tooFarVector.y * dirV.y + tooFarVector.z * dirV.z <= 0.0f )
{
// use the destination position
newPos = m_targetPos;
//
// we have reached our target position, switch motion to the next position in
// the chain (which may be stay still and don't move anymore)
//
switch( m_targetMotion )
{
case STM_RISE: setMotion( STM_BUILD_ACROSS ); break;
case STM_BUILD_ACROSS: setMotion( STM_STILL ); break;
case STM_TEAR_DOWN_ACROSS: setMotion( STM_SINK ); break;
case STM_SINK:
{
// we are done with a sinking motion, destroy the scaffold object as our job is done
TheGameLogic->destroyObject( us );
break;
} // end case
} // end switch
} // end if
// set the new position
us->setPosition( &newPos );
// do not sleep
return UPDATE_SLEEP_NONE;
} // end update
// ------------------------------------------------------------------------------------------------
/** STATIC MEMBER:
* Helper function to retrieve a bridge scaffold interface from an object if one is present */
// ------------------------------------------------------------------------------------------------
BridgeScaffoldBehaviorInterface *BridgeScaffoldBehavior::getBridgeScaffoldBehaviorInterfaceFromObject( Object *obj )
{
// santiy
if( obj == NULL )
return NULL;
// get the bridge tower behavior interface
BridgeScaffoldBehaviorInterface *bridgeScaffoldInterface = NULL;
BehaviorModule **bmi;
for( bmi = obj->getBehaviorModules(); *bmi; ++bmi )
{
bridgeScaffoldInterface = (*bmi)->getBridgeScaffoldBehaviorInterface();
if( bridgeScaffoldInterface )
return bridgeScaffoldInterface;
} // end for bmi
// interface not found
return NULL;
} // end getBridgeScaffoldBehaviorInterfaceFromObject
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void BridgeScaffoldBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void BridgeScaffoldBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// target motion
xfer->xferUser( &m_targetMotion, sizeof( ScaffoldTargetMotion ) );
// create pos
xfer->xferCoord3D( &m_createPos );
// rise to pos
xfer->xferCoord3D( &m_riseToPos );
// build pos
xfer->xferCoord3D( &m_buildPos );
// lateral speed
xfer->xferReal( &m_lateralSpeed );
// vertical speed
xfer->xferReal( &m_verticalSpeed );
// current target pos
xfer->xferCoord3D( &m_targetPos );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void BridgeScaffoldBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,335 @@
/*
** 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: BridgeTowerBehavior.cpp //////////////////////////////////////////////////////////////////
// Author: Colin Day, July 2002
// Desc: Behavior module for the towers attached to bridges that can be targeted
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/BridgeBehavior.h"
#include "GameLogic/Module/BridgeTowerBehavior.h"
#include "GameLogic/TerrainLogic.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
BridgeTowerBehavior::BridgeTowerBehavior( Thing *thing, const ModuleData *moduleData )
: BehaviorModule( thing, moduleData )
{
m_bridgeID = INVALID_ID;
} // end BridgeTowerBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
BridgeTowerBehavior::~BridgeTowerBehavior( void )
{
} // end ~BridgeTowerBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void BridgeTowerBehavior::setBridge( Object *bridge )
{
if( bridge == NULL )
m_bridgeID = INVALID_ID;
else
m_bridgeID = bridge->getID();
} // end setBridge
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
ObjectID BridgeTowerBehavior::getBridgeID( void )
{
return m_bridgeID;
} // end getBridge
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void BridgeTowerBehavior::setTowerType( BridgeTowerType type )
{
m_type = type;
} // end setTowerType
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void BridgeTowerBehavior::onDamage( DamageInfo *damageInfo )
{
Object *bridge = TheGameLogic->findObjectByID( getBridgeID() );
// sanity
if( bridge == NULL )
return;
//
// get our body info so we now how much damage percent is being done to us ... we need this
// so that we can propagate the same damage percentage amont the towers and the bridge
//
BodyModuleInterface *body = getObject()->getBodyModule();
Real damagePercentage = damageInfo->in.m_amount / body->getMaxHealth();
// get the bridge behavior module for our bridge
BehaviorModule **bmi;
BridgeBehaviorInterface *bridgeInterface = NULL;
for( bmi = bridge->getBehaviorModules(); *bmi; ++bmi )
{
bridgeInterface = (*bmi)->getBridgeBehaviorInterface();
if( bridgeInterface )
break;
} // end for bmi
DEBUG_ASSERTCRASH( bridgeInterface != NULL, ("BridgeTowerBehavior::onDamage - no 'BridgeBehaviorInterface' found\n") );
if( bridgeInterface )
{
//
// damage each of the other towers if the source of this damage isn't from the bridge
// or other towers
//
Object *source = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID );
if( source == NULL ||
(source->isKindOf( KINDOF_BRIDGE ) == FALSE &&
source->isKindOf( KINDOF_BRIDGE_TOWER ) == FALSE) )
{
for( Int i = 0; i < BRIDGE_MAX_TOWERS; ++i )
{
Object *tower;
tower = TheGameLogic->findObjectByID( bridgeInterface->getTowerID( (BridgeTowerType)i ) );
if( tower && tower != getObject() )
{
BodyModuleInterface *towerBody = tower->getBodyModule();
DamageInfo towerDamage;
towerDamage.in.m_amount = damagePercentage * towerBody->getMaxHealth();
towerDamage.in.m_sourceID = getObject()->getID(); // we're now the source
towerDamage.in.m_damageType = damageInfo->in.m_damageType;
towerDamage.in.m_deathType = damageInfo->in.m_deathType;
tower->attemptDamage( &towerDamage );
} // end if
} // end for i
//
// damage bridge object, but make sure it's done through the bridge interface
// so that it doesn't automatically propagate that damage to the towers
//
BodyModuleInterface *bridgeBody = bridge->getBodyModule();
DamageInfo bridgeDamage;
bridgeDamage.in.m_amount = damagePercentage * bridgeBody->getMaxHealth();
bridgeDamage.in.m_sourceID = getObject()->getID(); // we're now the source
bridgeDamage.in.m_damageType = damageInfo->in.m_damageType;
bridgeDamage.in.m_deathType = damageInfo->in.m_deathType;
bridge->attemptDamage( &bridgeDamage );
} // end if
} // end if
} // end onDamage
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void BridgeTowerBehavior::onHealing( DamageInfo *damageInfo )
{
Object *bridge = TheGameLogic->findObjectByID( getBridgeID() );
// sanity
if( bridge == NULL )
return;
//
// get our body info so we now how much healing percent is being done to us ... we need this
// so that we can propagate the same healing percentage amont the towers and the bridge
//
BodyModuleInterface *body = getObject()->getBodyModule();
Real healingPercentage = damageInfo->in.m_amount / body->getMaxHealth();
// get the bridge behavior module for our bridge
BehaviorModule **bmi;
BridgeBehaviorInterface *bridgeInterface = NULL;
for( bmi = bridge->getBehaviorModules(); *bmi; ++bmi )
{
bridgeInterface = (*bmi)->getBridgeBehaviorInterface();
if( bridgeInterface )
break;
} // end for bmi
DEBUG_ASSERTCRASH( bridgeInterface != NULL, ("BridgeTowerBehavior::onHealing - no 'BridgeBehaviorInterface' found\n") );
if( bridgeInterface )
{
//
// heal each of the other towers if the source of this healing isn't from the bridge
// or other towers
//
Object *source = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID );
if( source == NULL ||
(source->isKindOf( KINDOF_BRIDGE ) == FALSE &&
source->isKindOf( KINDOF_BRIDGE_TOWER ) == FALSE) )
{
for( Int i = 0; i < BRIDGE_MAX_TOWERS; ++i )
{
Object *tower;
tower = TheGameLogic->findObjectByID( bridgeInterface->getTowerID( (BridgeTowerType)i ) );
if( tower && tower != getObject() )
{
BodyModuleInterface *towerBody = tower->getBodyModule();
tower->attemptHealing(healingPercentage * towerBody->getMaxHealth(), getObject());
} // end if
} // end for i
//
// heal bridge object, but make sure it's done through the bridge interface
// so that it doesn't automatically propagate that healing to the towers.
//
BodyModuleInterface *bridgeBody = bridge->getBodyModule();
bridge->attemptHealing(healingPercentage * bridgeBody->getMaxHealth(), getObject());
} // end if
} // end if
} // end onHealing
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void BridgeTowerBehavior::onBodyDamageStateChange( const DamageInfo* damageInfo,
BodyDamageType oldState,
BodyDamageType newState )
{
} // end onBodyDamageStateChange
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void BridgeTowerBehavior::onDie( const DamageInfo *damageInfo )
{
// kill the bridge object, this will kill all the towers
Object *bridge = TheGameLogic->findObjectByID( getBridgeID() );
if( bridge )
bridge->kill();
} // end onDie
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
/** Given an object, return a bridge tower interface if that object has one */
// ------------------------------------------------------------------------------------------------
BridgeTowerBehaviorInterface *BridgeTowerBehavior::getBridgeTowerBehaviorInterfaceFromObject( Object *obj )
{
// sanity
if( obj == NULL || obj->isKindOf( KINDOF_BRIDGE_TOWER ) == FALSE )
return NULL;
BehaviorModule **bmi;
BridgeTowerBehaviorInterface *bridgeTowerInterface = NULL;
for( bmi = obj->getBehaviorModules(); *bmi; ++bmi )
{
bridgeTowerInterface = (*bmi)->getBridgeTowerBehaviorInterface();
if( bridgeTowerInterface )
return bridgeTowerInterface;
} // end for bmi
// interface not found
return NULL;
} // getBridgeTowerBehaviorInterfaceFromObject
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void BridgeTowerBehavior::crc( Xfer *xfer )
{
// extend base class
BehaviorModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void BridgeTowerBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
BehaviorModule::xfer( xfer );
// xfer bridge object ID
xfer->xferObjectID( &m_bridgeID );
// xfer tower type
xfer->xferUser( &m_type, sizeof( BridgeTowerType ) );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void BridgeTowerBehavior::loadPostProcess( void )
{
// extend base class
BehaviorModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,297 @@
/*
** 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: BunkerBusterBehavior.cpp ////////////////////////////////////////////////////////////////
// Author: Mark Lorenzen, June 2003
// Desc: Behavior module for Bunker Buster... it kills garrisoned objects
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/Upgrade.h"
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/BunkerBusterBehavior.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Weapon.h"
#include "GameClient/TerrainVisual.h"//Seismic simulations!
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
static DomeStyleSeismicFilter bunkerBusterHeavingEarthSeismicFilter;
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
BunkerBusterBehaviorModuleData::BunkerBusterBehaviorModuleData( void )
{
m_upgradeRequired = NULL;
m_detonationFX = NULL;
m_crashThroughBunkerFX = NULL;
m_crashThroughBunkerFXFrequency = 4;
m_seismicEffectRadius = 140.0f;
m_seismicEffectMagnitude = 6.0f;
m_shockwaveWeaponTemplate = NULL;
m_occupantDamageWeaponTemplate = NULL;
} // end BunkerBusterBehaviorModuleData
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void BunkerBusterBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
UpdateModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "UpgradeRequired", INI::parseAsciiString, NULL, offsetof( BunkerBusterBehaviorModuleData, m_upgradeRequired ) },
{ "DetonationFX", INI::parseFXList, NULL, offsetof( BunkerBusterBehaviorModuleData, m_detonationFX ) },
{ "CrashThroughBunkerFX", INI::parseFXList, NULL, offsetof( BunkerBusterBehaviorModuleData, m_crashThroughBunkerFX ) },
{ "CrashThroughBunkerFXFrequency", INI::parseDurationUnsignedInt, NULL, offsetof( BunkerBusterBehaviorModuleData, m_crashThroughBunkerFXFrequency ) },
{ "SeismicEffectRadius", INI::parseReal, NULL, offsetof( BunkerBusterBehaviorModuleData, m_seismicEffectRadius ) },
{ "SeismicEffectMagnitude", INI::parseReal, NULL, offsetof( BunkerBusterBehaviorModuleData, m_seismicEffectMagnitude ) },
{ "ShockwaveWeaponTemplate", INI::parseWeaponTemplate, NULL, offsetof( BunkerBusterBehaviorModuleData, m_shockwaveWeaponTemplate ) },
{ "OccupantDamageWeaponTemplate", INI::parseWeaponTemplate, NULL, offsetof( BunkerBusterBehaviorModuleData, m_occupantDamageWeaponTemplate ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
BunkerBusterBehavior::BunkerBusterBehavior( Thing *thing, const ModuleData *modData )
: UpdateModule( thing, modData )
{
// THIS HAS AN UPDATE... BECAUSE I FORSEE THE NEED FOR ONE, BUT RIGHT NOW IT DOES NOTHING
setWakeFrame( getObject(), UPDATE_SLEEP_NONE );
m_victimID = INVALID_ID;
m_upgradeRequired = NULL;
} // end BunkerBusterBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
BunkerBusterBehavior::~BunkerBusterBehavior( void )
{
} // end ~BunkerBusterBehavior
void BunkerBusterBehavior::onObjectCreated( void )
{
const BunkerBusterBehaviorModuleData *modData = getBunkerBusterBehaviorModuleData();
// convert module upgrade name to a pointer
m_upgradeRequired = TheUpgradeCenter->findUpgrade( modData->m_upgradeRequired );
} // end onObjectCreated
// ------------------------------------------------------------------------------------------------
/** The update callback */
// ------------------------------------------------------------------------------------------------
UpdateSleepTime BunkerBusterBehavior::update( void )
{
const BunkerBusterBehaviorModuleData *modData = getBunkerBusterBehaviorModuleData();
AIUpdateInterface *ai = getObject()->getAI();
if ( ai )// is this a SMART bomb?
{
if ( m_victimID == INVALID_ID )
{
Object *victim = ai->getCurrentVictim();
if ( victim )
m_victimID = victim->getID();
DEBUG_ASSERTCRASH( victim, ("BunkerBusterBehavior::update... AIUpdateInterface reports no victim." ) );
}
DEBUG_ASSERTCRASH( ai, ("BunkerBusterBehavior::update could not find an AIUpdateInterface." ) );
if ( TheGameLogic->getFrame()%modData->m_crashThroughBunkerFXFrequency == 1 )// not too much
{
const FXList *crashFX = modData->m_crashThroughBunkerFX;
if ( getObject()->testStatus( OBJECT_STATUS_MISSILE_KILLING_SELF ) && crashFX )
FXList::doFXObj( crashFX, getObject() );// CrashFX done on the missile/bomb
}
}
return UPDATE_SLEEP_NONE;
} // end update
// ------------------------------------------------------------------------------------------------
/** The death callback */
// ------------------------------------------------------------------------------------------------
void BunkerBusterBehavior::onDie( const DamageInfo *damageInfo )
{
// do what we came here to do!
bustTheBunker();
}
// ------------------------------------------------------------------------------------------------
/** The bunker-busting effect callback */
// ------------------------------------------------------------------------------------------------
void BunkerBusterBehavior::bustTheBunker( void )
{
const BunkerBusterBehaviorModuleData *modData = getBunkerBusterBehaviorModuleData();
if ( m_upgradeRequired != NULL )
{
Bool weaponUpgraded = getObject()->getControllingPlayer()->hasUpgradeComplete( m_upgradeRequired );
if ( ! weaponUpgraded )
return;
}
// here is where we kill everyone inside any targeted garrisoned buildings
// AIUpdateInterface *ai = getObject()->getAI();
Object *target = TheGameLogic->findObjectByID( m_victimID );
Object *objectForFX = getObject();
if ( target ) // Was the pilot aiming at an object?
{
objectForFX = target;
ContainModuleInterface *contain = target->getContain();
if ( contain && contain->isBustable() ) // Was that object something that bunkerbusters bust?
{
if ( modData->m_occupantDamageWeaponTemplate )
{
DamageInfo damageInfo;
damageInfo.in.m_damageType = modData->m_occupantDamageWeaponTemplate->getDamageType();
damageInfo.in.m_deathType = modData->m_occupantDamageWeaponTemplate->getDeathType();
damageInfo.in.m_sourceID = getObject()->getID();
damageInfo.in.m_sourcePlayerMask = getObject()->getControllingPlayer()->getPlayerMask();
damageInfo.in.m_amount = 100.0f;
contain->harmAndForceExitAllContained( &damageInfo ); // Ouch!
}
else
contain->killAllContained();
}
}
const FXList *detonationFX = modData->m_detonationFX;
if ( detonationFX )
FXList::doFXObj( detonationFX, objectForFX );//DetonationFX done on the building
#ifdef DO_SEISMIC_SIMULATIONS
// Okay, the right proper way to do this is to add SeismicSim support to FXList...
// But until that day, I'm just gonna do it here, sorry, M Lorenzen 6/26/03
SeismicSimulationNode sim(
objectForFX->getPosition(),
modData->m_seismicEffectRadius,
modData->m_seismicEffectMagnitude,
&bunkerBusterHeavingEarthSeismicFilter );
TheTerrainVisual->addSeismicSimulation( sim );
#endif
if ( modData->m_shockwaveWeaponTemplate )
TheWeaponStore->createAndFireTempWeapon(modData->m_shockwaveWeaponTemplate, objectForFX, objectForFX->getPosition());
} // end onDie
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void BunkerBusterBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void BunkerBusterBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void BunkerBusterBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,418 @@
/*
** 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) 2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: CountermeasuresBehavior.cpp //////////////////////////////////////////////////////////////
// Author: Kris Morness, April 2003
// Desc: Handles countermeasure firing when under missile threat, and responsible
// for diverting missiles to the flares.
///////////////////////////////////////////////////////////////////////////////////////////////////
// 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/Player.h"
#include "Common/ThingFactory.h"
#include "Common/Xfer.h"
#include "GameClient/ParticleSys.h"
#include "GameClient/Anim2D.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/Module/CountermeasuresBehavior.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
struct CountermeasuresPlayerScanHelper
{
KindOfMaskType m_kindOfToTest;
Object *m_theHealer;
ObjectPointerList *m_objectList;
};
static void checkForCountermeasures( Object *testObj, void *userData )
{
CountermeasuresPlayerScanHelper *helper = (CountermeasuresPlayerScanHelper*)userData;
ObjectPointerList *listToAddTo = helper->m_objectList;
if( testObj->isEffectivelyDead() )
return;
if( testObj->getControllingPlayer() != helper->m_theHealer->getControllingPlayer() )
return;
if( testObj->isOffMap() )
return;
if( !testObj->isAnyKindOf(helper->m_kindOfToTest) )
return;
if( testObj->getBodyModule()->getHealth() >= testObj->getBodyModule()->getMaxHealth() )
return;
listToAddTo->push_back(testObj);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CountermeasuresBehavior::CountermeasuresBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
{
const CountermeasuresBehaviorModuleData *data = getCountermeasuresBehaviorModuleData();
m_availableCountermeasures = data->m_numberOfVolleys * data->m_volleySize;
m_reactionFrame = 0;
m_activeCountermeasures = 0;
m_divertedMissiles = 0;
m_incomingMissiles = 0;
m_nextVolleyFrame = 0;
setWakeFrame( getObject(), UPDATE_SLEEP_NONE );
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CountermeasuresBehavior::~CountermeasuresBehavior( void )
{
}
// ------------------------------------------------------------------------------------------------
void CountermeasuresBehavior::reportMissileForCountermeasures( Object *missile )
{
if( !missile )
{
return;
}
//Record the number of missiles that have been fired at us
m_incomingMissiles++;
if( m_availableCountermeasures + m_activeCountermeasures > 0 )
{
//We have countermeasures we can use. Determine now whether or not the incoming missile will
//be diverted.
const CountermeasuresBehaviorModuleData *data = getCountermeasuresBehaviorModuleData();
if( GameLogicRandomValueReal( 0.0f, 1.0f ) < data->m_evasionRate )
{
//This missile will be diverted!
ProjectileUpdateInterface* pui = NULL;
for( BehaviorModule** u = missile->getBehaviorModules(); *u; ++u )
{
if( (pui = (*u)->getProjectileUpdateInterface()) != NULL )
{
//Make sure the missile diverts after a delay. The delay needs to be larger than
//the countermeasure reaction time or else the missile won't have a countermeasure to divert to!
DEBUG_ASSERTCRASH( data->m_countermeasureReactionFrames < data->m_missileDecoyFrames,
("MissileDecoyDelay needs to be less than CountermeasureReactionTime in order to function properly.") );
pui->setFramesTillCountermeasureDiversionOccurs( data->m_missileDecoyFrames );
m_divertedMissiles++;
if( m_activeCountermeasures == 0 && m_reactionFrame == 0 )
{
//We need to launch our first volley of countermeasures, but we can't do it now. If we
//do, it'll look too artificial. Instead, we need to set up a timer to fake a reaction
//delay.
m_reactionFrame = TheGameLogic->getFrame() + data->m_countermeasureReactionFrames;
}
break;
}
}
}
}
}
//-------------------------------------------------------------------------------------------------
ObjectID CountermeasuresBehavior::calculateCountermeasureToDivertTo( const Object& victim )
{
const CountermeasuresBehaviorModuleData *data = getCountermeasuresBehaviorModuleData();
//Flares are pushed to the front of the list, but we only want to acquire the "newest" of the flares, therefore
//stop iterating after we've reached size of a single volley.
Int iteratorMax = MAX( data->m_volleySize, 1 );
Real closestDist = 1e15f;
Object *closestFlare = NULL;
//Start at the end of the list and go towards the beginning.
CountermeasuresVec::iterator it = m_counterMeasures.end();
//end is actually the end so advance the iterator.
if( it )
{
--it;
while( iteratorMax-- )
{
Object *obj = TheGameLogic->findObjectByID( *it );
if( obj )
{
Real dist = ThePartitionManager->getDistanceSquared( obj, getObject(), FROM_CENTER_2D );
if( dist < closestDist )
{
closestDist = dist;
closestFlare = obj;
}
}
else
{
--it;
}
}
}
if( closestFlare )
{
return closestFlare->getID();
}
return INVALID_ID;
}
//-------------------------------------------------------------------------------------------------
Bool CountermeasuresBehavior::isActive() const
{
return isUpgradeActive();
}
//-------------------------------------------------------------------------------------------------
/** The update callback. */
//-------------------------------------------------------------------------------------------------
UpdateSleepTime CountermeasuresBehavior::update( void )
{
UnsignedInt now = TheGameLogic->getFrame();
const CountermeasuresBehaviorModuleData *data = getCountermeasuresBehaviorModuleData();
Object *obj = getObject();
if( obj->isEffectivelyDead() )
{
return UPDATE_SLEEP_FOREVER;
}
if( !isUpgradeActive() )
{
return UPDATE_SLEEP_FOREVER;
}
//Validate all existing flares, and clean them up as needed.
for (CountermeasuresVec::iterator it = m_counterMeasures.begin(); it != m_counterMeasures.end(); /*nothing*/ )
{
Object *obj = TheGameLogic->findObjectByID( *it );
if( !obj )
{
it = m_counterMeasures.erase( it );
m_activeCountermeasures--;
}
else
{
++it;
}
}
if( obj->isAirborneTarget() )
{
//Handle flare volley launching (initial reaction, and continuation firing).
if( m_availableCountermeasures )
{
//Deal with the initial volley, but wait until we are permitted to react.
if( m_reactionFrame )
{
if( m_reactionFrame == now )
{
//We have been shot at and now that the reaction timer has expired, fire a full volley of
//countermeasures.
launchVolley();
m_nextVolleyFrame = now + data->m_framesBetweenVolleys;
m_reactionFrame = 0;
}
}
//Handle subsequent volley launching.
if( m_nextVolleyFrame == now )
{
launchVolley();
m_nextVolleyFrame = now + data->m_framesBetweenVolleys;
}
}
}
//Handle auto-reloading (data->m_reloadFrames of zero means it's not possible to auto-reload).
//Aircraft that don't auto-reload require landing at an airfield for resupply.
if( !m_availableCountermeasures && data->m_reloadFrames )
{
if( m_reloadFrame != 0 )
{
if( m_reloadFrame <= now )
{
//We've successfully reloaded automatically.
reloadCountermeasures();
}
}
else
{
//We just started reloading, so set the frame it'll be ready.
m_reloadFrame = now + data->m_reloadFrames;
}
}
return UPDATE_SLEEP( UPDATE_SLEEP_NONE );
}
//-------------------------------------------------------------------------------------------------
void CountermeasuresBehavior::reloadCountermeasures()
{
const CountermeasuresBehaviorModuleData *data = getCountermeasuresBehaviorModuleData();
m_availableCountermeasures = data->m_numberOfVolleys * data->m_volleySize;
m_reloadFrame = 0;
}
//-------------------------------------------------------------------------------------------------
void CountermeasuresBehavior::launchVolley()
{
const CountermeasuresBehaviorModuleData *data = getCountermeasuresBehaviorModuleData();
Object *obj = getObject();
Real volleySize = (Real)data->m_volleySize;
for( int i = 0; i < data->m_volleySize; i++ )
{
//Each flare in a volley will calculate a different vector to fly out. We have a +/- angle to
//spread out equally. With only one flare, it'll come straight out the back. Two flares will
//launch at the extreme positive and negative angle. Three flares will launch at extreme angles
//plus straight back. Four or more will divy it up equally.
Real currentVolley = (Real)i;
Real ratio = 0.0f;
if( volleySize != 1.0f )
{
//ratio between -1.0 and +1.0f
ratio = currentVolley / (volleySize - 1.0f) * 2.0f - 1.0f;
}
//Now calculate the angle. Simply multiply it by the ratio!
Real angle = ratio * data->m_volleyArcAngle;
Coord3D vel;
PhysicsBehavior *physics = obj->getPhysics();
//Calculate the angle to fire the flare by taking the facing angle and rotating it
//and then scaling it by it's velocity (if it's moving).
obj->getUnitDirectionVector3D( vel );
Vector2 flareVector;
flareVector.X = vel.x;
flareVector.Y = vel.y;
flareVector.Normalize();
flareVector.Rotate( angle );
//Give it back to the Coord3D
vel.x = flareVector.X;
vel.y = flareVector.Y;
vel.z = 0.0f;
Real velocity = physics->getVelocityMagnitude();
if( velocity < 1.0f )
{
velocity = -10.0f;
}
vel.scale( velocity * data->m_volleyVelocityFactor );
const ThingTemplate *thing = TheThingFactory->findTemplate( data->m_flareTemplateName );
if( thing )
{
Object *flare = TheThingFactory->newObject( thing, obj->getControllingPlayer()->getDefaultTeam() );
flare->setPosition( obj->getPosition() );
flare->setOrientation( obj->getOrientation() );
physics->transferVelocityTo( flare->getPhysics() );
flare->getPhysics()->applyMotiveForce( &vel );
m_activeCountermeasures++;
m_availableCountermeasures--;
m_counterMeasures.push_back( flare->getID() );
}
}
}
//------------------------------------------------------------------------------------------------
/** CRC */
//------------------------------------------------------------------------------------------------
void CountermeasuresBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
// extend base class
UpgradeMux::upgradeMuxCRC( xfer );
} // end crc
//------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
//------------------------------------------------------------------------------------------------
void CountermeasuresBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 2;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// extend base class
UpgradeMux::upgradeMuxXfer( xfer );
if( currentVersion >= 2 )
{
xfer->xferSTLObjectIDVector( &m_counterMeasures );
xfer->xferUnsignedInt( &m_availableCountermeasures );
xfer->xferUnsignedInt( &m_activeCountermeasures );
xfer->xferUnsignedInt( &m_divertedMissiles );
xfer->xferUnsignedInt( &m_incomingMissiles );
xfer->xferUnsignedInt( &m_reactionFrame );
xfer->xferUnsignedInt( &m_nextVolleyFrame );
}
} // end xfer
//------------------------------------------------------------------------------------------------
/** Load post process */
//------------------------------------------------------------------------------------------------
void CountermeasuresBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
// extend base class
UpgradeMux::upgradeMuxLoadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,799 @@
/*
** 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: DumbProjectileBehavior.cpp
// Author: Steven Johnson, July 2002
// Desc:
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#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/DumbProjectileBehavior.h"
#include "GameLogic/Module/MissileAIUpdate.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#include "GameLogic/Weapon.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
const Int DEFAULT_MAX_LIFESPAN = 10 * LOGICFRAMES_PER_SECOND;
//-----------------------------------------------------------------------------
DumbProjectileBehaviorModuleData::DumbProjectileBehaviorModuleData() :
m_maxLifespan(DEFAULT_MAX_LIFESPAN),
m_detonateCallsKill(FALSE),
m_orientToFlightPath(TRUE),
m_tumbleRandomly(FALSE),
m_firstHeight(0.0f),
m_secondHeight(0.0f),
m_firstPercentIndent(0.0f),
m_secondPercentIndent(0.0f),
m_garrisonHitKillCount(0),
m_garrisonHitKillFX(NULL),
m_flightPathAdjustDistPerFrame(0.0f)
{
}
//-----------------------------------------------------------------------------
void DumbProjectileBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p)
{
UpdateModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "MaxLifespan", INI::parseDurationUnsignedInt, NULL, offsetof( DumbProjectileBehaviorModuleData, m_maxLifespan ) },
{ "TumbleRandomly", INI::parseBool, NULL, offsetof( DumbProjectileBehaviorModuleData, m_tumbleRandomly ) },
{ "DetonateCallsKill", INI::parseBool, NULL, offsetof( DumbProjectileBehaviorModuleData, m_detonateCallsKill ) },
{ "OrientToFlightPath", INI::parseBool, NULL, offsetof( DumbProjectileBehaviorModuleData, m_orientToFlightPath ) },
{ "FirstHeight", INI::parseReal, NULL, offsetof( DumbProjectileBehaviorModuleData, m_firstHeight ) },
{ "SecondHeight", INI::parseReal, NULL, offsetof( DumbProjectileBehaviorModuleData, m_secondHeight ) },
{ "FirstPercentIndent", INI::parsePercentToReal, NULL, offsetof( DumbProjectileBehaviorModuleData, m_firstPercentIndent ) },
{ "SecondPercentIndent", INI::parsePercentToReal, NULL, offsetof( DumbProjectileBehaviorModuleData, m_secondPercentIndent ) },
{ "GarrisonHitKillRequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( DumbProjectileBehaviorModuleData, m_garrisonHitKillKindof ) },
{ "GarrisonHitKillForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( DumbProjectileBehaviorModuleData, m_garrisonHitKillKindofNot ) },
{ "GarrisonHitKillCount", INI::parseUnsignedInt, NULL, offsetof( DumbProjectileBehaviorModuleData, m_garrisonHitKillCount ) },
{ "GarrisonHitKillFX", INI::parseFXList, NULL, offsetof( DumbProjectileBehaviorModuleData, m_garrisonHitKillFX ) },
{ "FlightPathAdjustDistPerSecond", INI::parseVelocityReal, NULL, offsetof( DumbProjectileBehaviorModuleData, m_flightPathAdjustDistPerFrame ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
DumbProjectileBehavior::DumbProjectileBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
{
m_launcherID = INVALID_ID;
m_victimID = INVALID_ID;
m_detonationWeaponTmpl = NULL;
m_lifespanFrame = 0;
m_flightPath.clear();
m_flightPathSegments = 0;
m_flightPathSpeed = 0;
m_flightPathStart.zero();
m_flightPathEnd.zero();
m_currentFlightPathStep = 0;
m_extraBonusFlags = 0;
m_hasDetonated = FALSE;
}
//-------------------------------------------------------------------------------------------------
DumbProjectileBehavior::~DumbProjectileBehavior()
{
}
//-------------------------------------------------------------------------------------------------
inline Bool within(Real min, Real val, Real max)
{
return min <= val && val <= max;
}
//-------------------------------------------------------------------------------------------------
inline Bool notWithin(Real min, Real val, Real max)
{
return val < min || val > max;
}
#ifdef NOT_IN_USE
#define NO_ONLY_RETURN_CLIPPED_PITCHES
//-------------------------------------------------------------------------------------------------
static Bool calcTrajectory(
const Coord3D& start, // in: where the projectile starts
const Coord3D& end, // in: where the projectile wants to end up
Real velocity, // in: the initial speed of the projectile
Real minPitch, // in: min pitch (-PI/2)
Real maxPitch, // in: max pitch (-PI/2)
Bool preferShortPitch, // in: prefer the shorter or longer path?
Real& angle, // out: the angle to aim
Real& pitch // out: the pitch to aim for
)
{
Bool exactTarget = false;
angle = 0.0f;
pitch = 0.0f;
if (velocity <= 0.0f)
{
DEBUG_CRASH(("cant get there from here (1)"));
return false;
}
Real dx = end.x - start.x;
Real dy = end.y - start.y;
Real dz = end.z - start.z;
// calculating the angle is trivial.
angle = atan2(dy, dx);
// calculating the pitch requires a bit more effort.
Real horizDistSqr = sqr(dx) + sqr(dy);
Real horizDist = sqrt(horizDistSqr);
// calc the two possible pitches that will cover the given horizontal range.
// (this is actually only true if dz==0, but is a good first guess)
Real gravity = fabs(TheGlobalData->m_gravity);
Real gravityTwoDZ = gravity * 2.0f * dz;
// let's start by aiming directly for it. we know this isn't right (unless gravity
// is zero, which it's not) but is a good starting point...
Real theta = atan2(dz, horizDist);
// if the angle isn't pretty shallow, we can get a better initial guess by using
// the code below...
const Real SHALLOW_ANGLE = 0.5f * PI / 180.0f;
if (fabs(theta) > SHALLOW_ANGLE)
{
Real t = horizDist / velocity;
Real vz = (dz/t + 0.5f*gravity*t);
Real sineOfAngle = clamp(-1.0f, vz / velocity, 1.0f);
theta = ASin(sineOfAngle)*0.5f;
}
/*
this is, in theory, the "right" formula for dz==0, but I
get better results with the stuff above:
Real sineOfAngle = (gravity * horizDist) / sqr(velocity);
if (sineOfAngle > 1.0f)
{
return false;
}
Real theta = ASin(sineOfAngle)*0.5f;
*/
Real pitches[2];
Real cosPitches[2];
Real sinPitches[2];
Real theta_min = max(minPitch, -PI/2);
Real theta_max = min(maxPitch, PI/2);
const Real MIN_ANGLE_DIFF = (PI/(180.0f*16.0f)); // 1/16th of a degree. yes, we need that accuracy.
//Int numLoops = 0;
while (theta_max > theta_min + MIN_ANGLE_DIFF)
{
//++numLoops;
pitches[0] = theta; // shallower angle
pitches[1] = (theta >= 0.0) ? (PI/2 - theta) : (-PI/2 - theta); // steeper angle
DEBUG_ASSERTCRASH(pitches[0]<=PI/2&&pitches[0]>=-PI/2,("bad pitches[0] %f\n",rad2deg(pitches[0])));
DEBUG_ASSERTCRASH(pitches[1]<=PI/2&&pitches[1]>=-PI/2,("bad pitches[1] %f\n",rad2deg(pitches[1])));
// calc the horiz-speed & time for each.
// note that time can only be negative for 90<angle<270, and since we
// ruled those out above, we're gold.
sinPitches[0] = Sin(pitches[0]);
sinPitches[1] = Sin(pitches[1]);
cosPitches[0] = Cos(pitches[0]);
cosPitches[1] = Cos(pitches[1]);
Real t0 = (horizDist / (velocity * cosPitches[0]));
Real t1 = (horizDist / (velocity * cosPitches[1]));
t0 = MAX(0,t0);
t1 = MAX(0,t1);
DEBUG_ASSERTCRASH(t0>=0&&t1>=0,("neg time"));
Int preferred = ((t0 < t1) == (preferShortPitch)) ? 0 : 1;
// ok, NOW... since dz is virtually NEVER zero, do a little approximation
// to get a better guess. (solving the equations directly are hard and
// it's simpler to approximate)
Bool tooClose = false;
Real vx, vz, root;
vz = velocity*sinPitches[preferred];
root = sqr(vz) - gravityTwoDZ;
if (root < 0.0f)
{
// oops, no solution for our preferred pitch. try the other one.
if (preferred == 0)
tooClose = true; // if this fails for the shallow case, it's 'cuz the result is too close
preferred = 1 - preferred;
vz = velocity*sinPitches[preferred];
root = sqr(vz) - gravityTwoDZ;
if (root < 0.0f)
{
DEBUG_CRASH(("cant get there from here (2)"));
return false;
}
}
#ifdef ONLY_RETURN_CLIPPED_PITCHES
// if we get this far, we have some plausible solution...
// it actually may be off, but let's go ahead and set the result
// (along with the "exact" flag) so that the caller will want to
// try to use the result. (we'll probably iterate and overwrite
// this result with a more precise one.)
if (within(minPitch, pitches[preferred], maxPitch))
{
pitch = pitches[preferred];
exactTarget = true;
}
#else
// if we get this far, we have some plausible solution...
// it actually may be off, but let's go ahead and set the result
// (along with the "exact" flag) so that the caller will want to
// try to use the result. (we'll probably iterate and overwrite
// this result with a more precise one.) NOTE however that we
// don't validate it's within the proper range... caller must do that!
pitch = pitches[preferred];
exactTarget = true;
#endif
vx = velocity*cosPitches[preferred];
Real actualRange = (vx*(vz + sqrt(root)))/gravity;
const Real CLOSE_ENOUGH_RANGE = 5.0f;
if (tooClose || (actualRange < horizDist - CLOSE_ENOUGH_RANGE))
{
theta_min = theta;
theta = (theta + theta_max) * 0.5f;
}
else if (actualRange > horizDist + CLOSE_ENOUGH_RANGE)
{
theta_max = theta;
theta = (theta + theta_min) * 0.5f;
}
else
{
break;
}
}
//DEBUG_LOG(("took %d loops to find a match\n",numLoops));
if (exactTarget)
return true;
return false;
}
#endif NOT_IN_USE
//-------------------------------------------------------------------------------------------------
// Prepares the missile for launch via proper weapon-system channels.
//-------------------------------------------------------------------------------------------------
void DumbProjectileBehavior::projectileLaunchAtObjectOrPosition(
const Object* victim,
const Coord3D* victimPos,
const Object* launcher,
WeaponSlotType wslot,
Int specificBarrelToUse,
const WeaponTemplate* detWeap,
const ParticleSystemTemplate* exhaustSysOverride
)
{
const DumbProjectileBehaviorModuleData* d = getDumbProjectileBehaviorModuleData();
DEBUG_ASSERTCRASH(specificBarrelToUse>=0, ("specificBarrelToUse must now be explicit"));
m_launcherID = launcher ? launcher->getID() : INVALID_ID;
m_extraBonusFlags = launcher ? launcher->getWeaponBonusCondition() : 0;
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 DumbProjectileBehavior::projectileFireAtObjectOrPosition( const Object *victim, const Coord3D *victimPos, const WeaponTemplate *detWeap, const ParticleSystemTemplate* exhaustSysOverride )
{
const DumbProjectileBehaviorModuleData* d = getDumbProjectileBehaviorModuleData();
Object* projectile = getObject();
Real weaponSpeed = detWeap ? detWeap->getWeaponSpeed() : 0.0f;
Real minWeaponSpeed = detWeap ? detWeap->getMinWeaponSpeed() : 0.0f;
// if an object, aim at the center, not the ground part
Coord3D victimPosToUse;
if (victim)
victim->getGeometryInfo().getCenterPosition(*victim->getPosition(), victimPosToUse);
else
victimPosToUse = *victimPos;
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);
m_flightPathSpeed = (rangeRatio * (weaponSpeed - minWeaponSpeed)) + minWeaponSpeed;
}
else
{
m_flightPathSpeed = weaponSpeed;
}
PhysicsBehavior* physics = projectile->getPhysics();
if ( d->m_tumbleRandomly && physics)
{
physics->setPitchRate( 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 ) );
}
m_flightPathStart = *getObject()->getPosition();
m_flightPathEnd = victimPosToUse;
if (!calcFlightPath(true))
{
//Can only fail if wildly incorrect points
TheGameLogic->destroyObject( projectile );
return;
}
m_currentFlightPathStep = 0;// We are at the first point, because the launching put us there
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool DumbProjectileBehavior::calcFlightPath(Bool recalcNumSegments)
{
const DumbProjectileBehaviorModuleData* d = getDumbProjectileBehaviorModuleData();
Coord3D controlPoints[4];
//First point is us, last point is them
controlPoints[0] = m_flightPathStart;
controlPoints[3] = m_flightPathEnd;
Real highestInterveningTerrain;
Bool onMap = ThePartitionManager->estimateTerrainExtremesAlongLine( controlPoints[0], controlPoints[3], NULL, &highestInterveningTerrain, NULL, NULL );
if( !onMap )
{
return false;
}
// X and Y for inner points are along the line between us, so normalize and scale a vector between us, but
// only use the x and y of the result
Vector3 targetVector;// 0 origin vector between me and him
targetVector.X = controlPoints[3].x - controlPoints[0].x;
targetVector.Y = controlPoints[3].y - controlPoints[0].y;
targetVector.Z = controlPoints[3].z - controlPoints[0].z;
Real targetDistance = targetVector.Length();
targetVector.Normalize();
Vector3 firstPointAlongLine = targetVector * (targetDistance * d->m_firstPercentIndent );
Vector3 secondPointAlongLine = targetVector * (targetDistance * d->m_secondPercentIndent );
controlPoints[1].x = firstPointAlongLine.X + controlPoints[0].x;// add world start to offset along the origin based vector
controlPoints[1].y = firstPointAlongLine.Y + controlPoints[0].y;
controlPoints[2].x = secondPointAlongLine.X + controlPoints[0].x;
controlPoints[2].y = secondPointAlongLine.Y + controlPoints[0].y;
// Z's are determined using the highest intervening height so they won't hit hills, low end bounded by current Zs
highestInterveningTerrain = max( highestInterveningTerrain, controlPoints[0].z );
highestInterveningTerrain = max( highestInterveningTerrain, controlPoints[3].z );
controlPoints[1].z = highestInterveningTerrain + d->m_firstHeight;
controlPoints[2].z = highestInterveningTerrain + d->m_secondHeight;
// With four control points, we have a curve. We will decide how many frames we want to take to get to the target,
// and fill our vector with those curve points.
BezierSegment flightCurve( controlPoints );
if (recalcNumSegments)
{
Real flightDistance = flightCurve.getApproximateLength();
m_flightPathSegments = ceil( flightDistance / m_flightPathSpeed );
}
flightCurve.getSegmentPoints( m_flightPathSegments, &m_flightPath );
DEBUG_ASSERTCRASH(m_flightPathSegments == m_flightPath.size(), ("m_flightPathSegments mismatch"));
#if defined(_DEBUG) || defined(_INTERNAL)
if( TheGlobalData->m_debugProjectilePath )
displayFlightPath();
#endif
return true;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool DumbProjectileBehavior::projectileHandleCollision( Object *other )
{
const DumbProjectileBehaviorModuleData* d = getDumbProjectileBehaviorModuleData();
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 != NULL && 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);
getObject()->getControllingPlayer()->getAcademyStats()->recordClearedGarrisonedBuilding();
// don't do the normal explosion; just destroy ourselves & return
TheGameLogic->destroyObject(getObject());
return true;
}
} // if a garrisonable thing
}
}
// 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 DumbProjectileBehavior::detonate()
{
if ( m_hasDetonated )
return;
Object* obj = getObject();
if (m_detonationWeaponTmpl)
{
TheWeaponStore->handleProjectileDetonation(m_detonationWeaponTmpl, obj, obj->getPosition(), m_extraBonusFlags);
if ( getDumbProjectileBehaviorModuleData()->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 DumbProjectileBehavior::update()
{
const DumbProjectileBehaviorModuleData* d = getDumbProjectileBehaviorModuleData();
if (m_lifespanFrame != 0 && TheGameLogic->getFrame() >= m_lifespanFrame)
{
// lifetime demands detonation
detonate();
return UPDATE_SLEEP_NONE;
}
if( m_currentFlightPathStep >= m_flightPath.size() )
{
// No more steps to use. Would go out of bounds on vector, so have to do something.
// We could allow physics to take over and make us fall, but the point of this whole task
// is to guarentee where the shell explodes. This way, it _will_ explode at the target point.
detonate();
return UPDATE_SLEEP_NONE;
}
if (m_victimID != INVALID_ID && d->m_flightPathAdjustDistPerFrame > 0.0f)
{
Object* victim = TheGameLogic->findObjectByID(m_victimID);
if (victim)
{
Coord3D newVictimPos;
victim->getGeometryInfo().getCenterPosition(*victim->getPosition(), newVictimPos);
Coord3D delta;
delta.x = newVictimPos.x - m_flightPathEnd.x;
delta.y = newVictimPos.y - m_flightPathEnd.y;
delta.z = newVictimPos.z - m_flightPathEnd.z;
Real distVictimMovedSqr = sqr(delta.x) + sqr(delta.y) + sqr(delta.z);
if (distVictimMovedSqr > 0.1f)
{
Real distVictimMoved = sqrtf(distVictimMovedSqr);
if (distVictimMoved > d->m_flightPathAdjustDistPerFrame)
distVictimMoved = d->m_flightPathAdjustDistPerFrame;
delta.normalize();
m_flightPathEnd.x += distVictimMoved * delta.x;
m_flightPathEnd.y += distVictimMoved * delta.y;
m_flightPathEnd.z += distVictimMoved * delta.z;
if (!calcFlightPath(false))
{
DEBUG_CRASH(("Hmm, recalc of flight path returned false... should this happen?"));
detonate();
return UPDATE_SLEEP_NONE;
}
}
}
}
//Otherwise, continue to force the flight path
Coord3D flightStep = m_flightPath[m_currentFlightPathStep];
if (d->m_orientToFlightPath && (!d->m_tumbleRandomly) )
{
if ( m_currentFlightPathStep > 0)
{
// this seems reasonable; however, if this object has a PhysicsBehavior on it, this calc will be wrong,
// since Physics is applying gravity, which we duly ignore, but the prevPos won't be what we expect.
// get it from the flight path instead. (srj)
//Coord3D prevPos = *getObject()->getPosition();
Coord3D prevPos = m_flightPath[m_currentFlightPathStep - 1];
Vector3 curDir(flightStep.x - prevPos.x, flightStep.y - prevPos.y, flightStep.z - prevPos.z);
curDir.Normalize(); // buildTransformMatrix wants it this way
Matrix3D orientMtx;
orientMtx.buildTransformMatrix(Vector3(flightStep.x, flightStep.y, flightStep.z), curDir);
getObject()->setTransformMatrix(&orientMtx);
}
else // oops! how do we orient the projectile on the zeroeth frame? This didn't matter until we started using the
//long, blurry projectile graphics which look badly oriented on step 0 of the flight path
// so lets orient it the same as if it were on frame 1!
{
Coord3D prevPos = m_flightPath[0];
Coord3D curPos = m_flightPath[1];
Vector3 curDir(curPos.x - prevPos.x, curPos.y - prevPos.y, curPos.z - prevPos.z);
curDir.Normalize(); // buildTransformMatrix wants it this way
Matrix3D orientMtx;
orientMtx.buildTransformMatrix(Vector3(flightStep.x, flightStep.y, flightStep.z), curDir);
getObject()->setTransformMatrix(&orientMtx);
}
}
else
{
getObject()->setPosition(&flightStep);
}
// note that we want to use getHighestLayerForDestination() here, so that anything even slightly
// below the bridge translates into GROUND. (getLayerForDestination just does a "closest" check)
PathfindLayerEnum oldLayer = getObject()->getLayer();
PathfindLayerEnum newLayer = TheTerrainLogic->getHighestLayerForDestination(getObject()->getPosition());
getObject()->setLayer(newLayer);
if (oldLayer != LAYER_GROUND && newLayer == LAYER_GROUND)
{
// see if we' still in the bridge's xy area
Coord3D tmp = *getObject()->getPosition();
tmp.z = 9999.0f;
PathfindLayerEnum testLayer = TheTerrainLogic->getHighestLayerForDestination(&tmp);
if (testLayer == oldLayer)
{
// ensure we are slightly above the bridge, to account for fudge & sloppy art
const Real FUDGE = 2.0f;
tmp.z = TheTerrainLogic->getLayerHeight(tmp.x, tmp.y, testLayer) + FUDGE;
getObject()->setPosition(&tmp);
// blow'd up!
detonate();
return UPDATE_SLEEP_NONE;
}
}
++m_currentFlightPathStep;
return UPDATE_SLEEP_NONE;//This no longer flys with physics, so it needs to not sleep
}
// ------------------------------------------------------------------------------------------------
/** displayFlightPath for debugging */
// ------------------------------------------------------------------------------------------------
#if defined(_DEBUG) || defined(_INTERNAL)
void DumbProjectileBehavior::displayFlightPath()
{
extern void addIcon(const Coord3D *pos, Real width, Int numFramesDuration, RGBColor color);
for( Int pointIndex = 0; pointIndex < m_flightPath.size(); ++pointIndex )
{
addIcon(&m_flightPath[pointIndex], TheGlobalData->m_debugProjectileTileWidth,
TheGlobalData->m_debugProjectileTileDuration,
TheGlobalData->m_debugProjectileTileColor);
}
}
#endif
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void DumbProjectileBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void DumbProjectileBehavior::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 );
xfer->xferInt( &m_flightPathSegments );
xfer->xferReal( &m_flightPathSpeed );
xfer->xferCoord3D( &m_flightPathStart );
xfer->xferCoord3D( &m_flightPathEnd );
// 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(( "DumbProjectileBehavior::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 DumbProjectileBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,365 @@
/*
** 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: FireWeaponWhenDamagedBehavior.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/RandomValue.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/FireWeaponWhenDamagedBehavior.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectCreationList.h"
#include "GameLogic/Weapon.h"
#include "GameClient/Drawable.h"
const Int MAX_IDX = 32;
const Real BEGIN_MIDPOINT_RATIO = 0.35f;
const Real END_MIDPOINT_RATIO = 0.65f;
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
FireWeaponWhenDamagedBehavior::FireWeaponWhenDamagedBehavior( Thing *thing, const ModuleData* moduleData ) :
UpdateModule( thing, moduleData ),
m_reactionWeaponPristine( NULL ),
m_reactionWeaponDamaged( NULL ),
m_reactionWeaponReallyDamaged( NULL ),
m_reactionWeaponRubble( NULL ),
m_continuousWeaponPristine( NULL ),
m_continuousWeaponDamaged( NULL ),
m_continuousWeaponReallyDamaged( NULL ),
m_continuousWeaponRubble( NULL )
{
const FireWeaponWhenDamagedBehaviorModuleData *d = getFireWeaponWhenDamagedBehaviorModuleData();
const Object* obj = getObject();
if ( d->m_reactionWeaponPristine )
{
m_reactionWeaponPristine = TheWeaponStore->allocateNewWeapon(
d->m_reactionWeaponPristine, PRIMARY_WEAPON);
m_reactionWeaponPristine->reloadAmmo( obj );
}
if ( d->m_reactionWeaponDamaged )
{
m_reactionWeaponDamaged = TheWeaponStore->allocateNewWeapon(
d->m_reactionWeaponDamaged, PRIMARY_WEAPON);
m_reactionWeaponDamaged->reloadAmmo( obj );
}
if ( d->m_reactionWeaponReallyDamaged )
{
m_reactionWeaponReallyDamaged = TheWeaponStore->allocateNewWeapon(
d->m_reactionWeaponReallyDamaged, PRIMARY_WEAPON);
m_reactionWeaponReallyDamaged->reloadAmmo( obj );
}
if ( d->m_reactionWeaponRubble )
{
m_reactionWeaponRubble = TheWeaponStore->allocateNewWeapon(
d->m_reactionWeaponRubble, PRIMARY_WEAPON);
m_reactionWeaponRubble->reloadAmmo( obj );
}
if ( d->m_continuousWeaponPristine )
{
m_continuousWeaponPristine = TheWeaponStore->allocateNewWeapon(
d->m_continuousWeaponPristine, PRIMARY_WEAPON);
m_continuousWeaponPristine->reloadAmmo( obj );
}
if ( d->m_continuousWeaponDamaged )
{
m_continuousWeaponDamaged = TheWeaponStore->allocateNewWeapon(
d->m_continuousWeaponDamaged, PRIMARY_WEAPON);
m_continuousWeaponDamaged->reloadAmmo( obj );
}
if ( d->m_continuousWeaponReallyDamaged )
{
m_continuousWeaponReallyDamaged = TheWeaponStore->allocateNewWeapon(
d->m_continuousWeaponReallyDamaged, PRIMARY_WEAPON);
m_continuousWeaponReallyDamaged->reloadAmmo( obj );
}
if ( d->m_continuousWeaponRubble )
{
m_continuousWeaponRubble = TheWeaponStore->allocateNewWeapon(
d->m_continuousWeaponRubble, PRIMARY_WEAPON);
m_continuousWeaponRubble->reloadAmmo( obj );
}
if (d->m_initiallyActive)
{
giveSelfUpgrade();
}
if (isUpgradeActive() &&
(d->m_continuousWeaponPristine != NULL ||
d->m_continuousWeaponDamaged != NULL ||
d->m_continuousWeaponReallyDamaged != NULL ||
d->m_continuousWeaponRubble != NULL))
{
setWakeFrame(getObject(), UPDATE_SLEEP_NONE);
}
else
{
setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER);
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
FireWeaponWhenDamagedBehavior::~FireWeaponWhenDamagedBehavior( void )
{
if (m_reactionWeaponPristine)
m_reactionWeaponPristine->deleteInstance();
if (m_reactionWeaponDamaged)
m_reactionWeaponDamaged->deleteInstance();
if (m_reactionWeaponReallyDamaged)
m_reactionWeaponReallyDamaged->deleteInstance();
if (m_reactionWeaponRubble)
m_reactionWeaponRubble->deleteInstance();
if (m_continuousWeaponPristine)
m_continuousWeaponPristine->deleteInstance();
if (m_continuousWeaponDamaged)
m_continuousWeaponDamaged->deleteInstance();
if (m_continuousWeaponReallyDamaged)
m_continuousWeaponReallyDamaged->deleteInstance();
if (m_continuousWeaponRubble)
m_continuousWeaponRubble->deleteInstance();
}
//-------------------------------------------------------------------------------------------------
/** Damage has been dealt, this is an opportunity to reach to that damage */
//-------------------------------------------------------------------------------------------------
void FireWeaponWhenDamagedBehavior::onDamage( DamageInfo *damageInfo )
{
if (!isUpgradeActive())
return;
const FireWeaponWhenDamagedBehaviorModuleData* d = getFireWeaponWhenDamagedBehaviorModuleData();
// right type?
if (!getDamageTypeFlag(d->m_damageTypes, damageInfo->in.m_damageType))
return;
// right amount? (use actual [post-armor] damage dealt)
if (damageInfo->out.m_actualDamageDealt < d->m_damageAmount)
return;
const Object *obj = getObject();
BodyDamageType bdt = obj->getBodyModule()->getDamageState();
if ( bdt == BODY_RUBBLE )
{
if( m_reactionWeaponRubble && m_reactionWeaponRubble->getStatus() == READY_TO_FIRE )
{
m_reactionWeaponRubble->forceFireWeapon( obj, obj->getPosition() );
}
}
else if ( bdt == BODY_REALLYDAMAGED )
{
if( m_reactionWeaponReallyDamaged && m_reactionWeaponReallyDamaged->getStatus() == READY_TO_FIRE )
{
m_reactionWeaponReallyDamaged->forceFireWeapon( obj, obj->getPosition() );
}
}
else if ( bdt == BODY_DAMAGED )
{
if( m_reactionWeaponDamaged && m_reactionWeaponDamaged->getStatus() == READY_TO_FIRE )
{
m_reactionWeaponDamaged->forceFireWeapon( obj, obj->getPosition() );
}
}
else // not damaged yet
{
if( m_reactionWeaponPristine && m_reactionWeaponPristine->getStatus() == READY_TO_FIRE )
{
m_reactionWeaponPristine->forceFireWeapon( obj, obj->getPosition() );
}
}
}
//-------------------------------------------------------------------------------------------------
/** if object fires weapon constantly, figure out which one and do it */
//-------------------------------------------------------------------------------------------------
UpdateSleepTime FireWeaponWhenDamagedBehavior::update( void )
{
if (!isUpgradeActive())
{
DEBUG_ASSERTCRASH(isUpgradeActive(), ("hmm, this should not be possible"));
return UPDATE_SLEEP_FOREVER;
}
const Object *obj = getObject();
BodyDamageType bdt = obj->getBodyModule()->getDamageState();
if ( bdt == BODY_RUBBLE )
{
if( m_continuousWeaponRubble && m_continuousWeaponRubble->getStatus() == READY_TO_FIRE )
{
m_continuousWeaponRubble->forceFireWeapon( obj, obj->getPosition() );
}
}
else if ( bdt == BODY_REALLYDAMAGED )
{
if( m_continuousWeaponReallyDamaged && m_continuousWeaponReallyDamaged->getStatus() == READY_TO_FIRE )
{
m_continuousWeaponReallyDamaged->forceFireWeapon( obj, obj->getPosition() );
}
}
else if ( bdt == BODY_DAMAGED )
{
if( m_continuousWeaponDamaged && m_continuousWeaponDamaged->getStatus() == READY_TO_FIRE )
{
m_continuousWeaponDamaged->forceFireWeapon( obj, obj->getPosition() );
}
}
else // not damaged yet
{
if( m_continuousWeaponPristine && m_continuousWeaponPristine->getStatus() == READY_TO_FIRE )
{
m_continuousWeaponPristine->forceFireWeapon( obj, obj->getPosition() );
}
}
return UPDATE_SLEEP_NONE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void FireWeaponWhenDamagedBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
// extend upgrade mux
UpgradeMux::upgradeMuxCRC( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void FireWeaponWhenDamagedBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// extend upgrade mux
UpgradeMux::upgradeMuxXfer( xfer );
Bool weaponPresent;
// reaction pristine
weaponPresent = m_reactionWeaponPristine ? TRUE : FALSE;
xfer->xferBool( &weaponPresent );
if( weaponPresent )
xfer->xferSnapshot( m_reactionWeaponPristine );
// reaction damaged
weaponPresent = m_reactionWeaponDamaged ? TRUE : FALSE;
xfer->xferBool( &weaponPresent );
if( weaponPresent )
xfer->xferSnapshot( m_reactionWeaponDamaged );
// reaction really damaged
weaponPresent = m_reactionWeaponReallyDamaged ? TRUE : FALSE;
xfer->xferBool( &weaponPresent );
if( weaponPresent )
xfer->xferSnapshot( m_reactionWeaponReallyDamaged );
// reaction rubble
weaponPresent = m_reactionWeaponRubble ? TRUE : FALSE;
xfer->xferBool( &weaponPresent );
if( weaponPresent )
xfer->xferSnapshot( m_reactionWeaponRubble );
// continuous pristine
weaponPresent = m_continuousWeaponPristine ? TRUE : FALSE;
xfer->xferBool( &weaponPresent );
if( weaponPresent )
xfer->xferSnapshot( m_continuousWeaponPristine );
// continuous damaged
weaponPresent = m_continuousWeaponDamaged ? TRUE : FALSE;
xfer->xferBool( &weaponPresent );
if( weaponPresent )
xfer->xferSnapshot( m_continuousWeaponDamaged );
// continuous really damaged
weaponPresent = m_continuousWeaponReallyDamaged ? TRUE : FALSE;
xfer->xferBool( &weaponPresent );
if( weaponPresent )
xfer->xferSnapshot( m_continuousWeaponReallyDamaged );
// continuous rubble
weaponPresent = m_continuousWeaponRubble ? TRUE : FALSE;
xfer->xferBool( &weaponPresent );
if( weaponPresent )
xfer->xferSnapshot( m_continuousWeaponRubble );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void FireWeaponWhenDamagedBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
// extend upgrade mux
UpgradeMux::upgradeMuxLoadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,168 @@
/*
** 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: FireWeaponWhenDeadBehavior.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/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/FireWeaponWhenDeadBehavior.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectCreationList.h"
#include "GameLogic/Weapon.h"
#include "GameClient/Drawable.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
const Int MAX_IDX = 32;
const Real BEGIN_MIDPOINT_RATIO = 0.35f;
const Real END_MIDPOINT_RATIO = 0.65f;
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
FireWeaponWhenDeadBehavior::FireWeaponWhenDeadBehavior( Thing *thing, const ModuleData* moduleData ) :
BehaviorModule( thing, moduleData )
{
if (getFireWeaponWhenDeadBehaviorModuleData()->m_initiallyActive)
{
giveSelfUpgrade();
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
FireWeaponWhenDeadBehavior::~FireWeaponWhenDeadBehavior( void )
{
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void FireWeaponWhenDeadBehavior::onDie( const DamageInfo *damageInfo )
{
const FireWeaponWhenDeadBehaviorModuleData* d = getFireWeaponWhenDeadBehaviorModuleData();
Object *obj = getObject();
if (!isUpgradeActive())
return;
// right type?
if (!d->m_dieMuxData.isDieApplicable(getObject(), damageInfo))
return;
// This will never apply until built. Otherwise canceling construction sets it off, and killing
// a one hitpoint one percent building will too.
if( obj->getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) )
return;
UpgradeMaskType activation, conflicting;
getUpgradeActivationMasks( activation, conflicting );
if( obj->getObjectCompletedUpgradeMask().testForAny( conflicting ) )
{
return;
}
if( obj->getControllingPlayer() && obj->getControllingPlayer()->getCompletedUpgradeMask().testForAny( conflicting ) )
{
return;
}
if (d->m_deathWeapon)
{
// fire the default weapon
TheWeaponStore->createAndFireTempWeapon(d->m_deathWeapon, obj, obj->getPosition());
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void FireWeaponWhenDeadBehavior::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 FireWeaponWhenDeadBehavior::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 );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void FireWeaponWhenDeadBehavior::loadPostProcess( void )
{
// extend base class
BehaviorModule::loadPostProcess();
// extend upgrade mux
UpgradeMux::upgradeMuxLoadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,589 @@
/*
** 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: GenerateMinefieldBehavior.cpp ///////////////////////////////////////////////////////////////////////
// Author:
// Desc:
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_SLOWDEATHPHASE_NAMES
#include "Common/GlobalData.h"
#include "Common/Thing.h"
#include "Common/ThingFactory.h"
#include "Common/ThingTemplate.h"
#include "Common/INI.h"
#include "Common/Player.h"
#include "Common/RandomValue.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/GenerateMinefieldBehavior.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectIter.h"
#include "GameLogic/ObjectCreationList.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/Weapon.h"
#include "GameClient/Drawable.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
GenerateMinefieldBehaviorModuleData::GenerateMinefieldBehaviorModuleData()
{
m_mineName.clear();
m_mineNameUpgraded.clear();
m_mineUpgradeTrigger.clear();
m_genFX = NULL;
m_distanceAroundObject = TheGlobalData->m_standardMinefieldDistance;
m_minesPerSquareFoot = TheGlobalData->m_standardMinefieldDensity;
m_onDeath = false;
m_borderOnly = true;
m_alwaysCircular = false;
m_upgradable = false;
m_smartBorder = false;
m_smartBorderSkipInterior = true;
m_randomJitter = 0.0f;
m_skipIfThisMuchUnderStructure = 0.33f;
}
//-------------------------------------------------------------------------------------------------
/*static*/ void GenerateMinefieldBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p)
{
static const FieldParse dataFieldParse[] =
{
{ "MineName", INI::parseAsciiString, NULL, offsetof( GenerateMinefieldBehaviorModuleData, m_mineName ) },
{ "UpgradedMineName", INI::parseAsciiString, NULL, offsetof( GenerateMinefieldBehaviorModuleData, m_mineNameUpgraded ) },
{ "UpgradedTriggeredBy", INI::parseAsciiString, NULL, offsetof( GenerateMinefieldBehaviorModuleData, m_mineUpgradeTrigger ) },
{ "GenerationFX", INI::parseFXList, NULL, offsetof( GenerateMinefieldBehaviorModuleData, m_genFX ) },
{ "DistanceAroundObject", INI::parseReal, NULL, offsetof( GenerateMinefieldBehaviorModuleData, m_distanceAroundObject ) },
{ "MinesPerSquareFoot", INI::parseReal, NULL, offsetof( GenerateMinefieldBehaviorModuleData, m_minesPerSquareFoot ) },
{ "GenerateOnlyOnDeath", INI::parseBool, NULL, offsetof(GenerateMinefieldBehaviorModuleData, m_onDeath) },
{ "BorderOnly", INI::parseBool, NULL, offsetof(GenerateMinefieldBehaviorModuleData, m_borderOnly) },
{ "SmartBorder", INI::parseBool, NULL, offsetof(GenerateMinefieldBehaviorModuleData, m_smartBorder) },
{ "SmartBorderSkipInterior", INI::parseBool, NULL, offsetof(GenerateMinefieldBehaviorModuleData, m_smartBorderSkipInterior) },
{ "AlwaysCircular", INI::parseBool, NULL, offsetof(GenerateMinefieldBehaviorModuleData, m_alwaysCircular) },
{ "Upgradable", INI::parseBool, NULL, offsetof(GenerateMinefieldBehaviorModuleData, m_upgradable) },
{ "RandomJitter", INI::parsePercentToReal, NULL, offsetof(GenerateMinefieldBehaviorModuleData, m_randomJitter) },
{ "SkipIfThisMuchUnderStructure", INI::parsePercentToReal, NULL, offsetof(GenerateMinefieldBehaviorModuleData, m_skipIfThisMuchUnderStructure) },
{ 0, 0, 0, 0 }
};
BehaviorModuleData::buildFieldParse(p);
p.add(dataFieldParse);
p.add(UpgradeMuxData::getFieldParse(), offsetof( GenerateMinefieldBehaviorModuleData, m_upgradeMuxData ));
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
GenerateMinefieldBehavior::GenerateMinefieldBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
{
m_target.zero();
m_generated = false;
m_hasTarget = false;
m_upgraded = false;
m_mineList.clear();
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
GenerateMinefieldBehavior::~GenerateMinefieldBehavior( void )
{
m_mineList.clear();
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::upgradeImplementation()
{
placeMines();
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::onDie( const DamageInfo *damageInfo )
{
const GenerateMinefieldBehaviorModuleData* d = getGenerateMinefieldBehaviorModuleData();
if (d->m_onDeath)
{
placeMines();
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::setMinefieldTarget(const Coord3D* pos)
{
if (pos)
{
m_hasTarget = true;
m_target = *pos;
}
else
{
m_hasTarget = false;
m_target.zero();
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
const Coord3D* GenerateMinefieldBehavior::getMinefieldTarget() const
{
return m_hasTarget ? &m_target : getObject()->getPosition();
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
static Bool isAnythingTooClose2D(const std::vector<Object*>& v, const Coord3D& pos, Real minDistSqr)
{
for (std::vector<Object*>::const_iterator it = v.begin(); it != v.end(); ++it)
{
const Coord3D* p = (*it)->getPosition();
Real distSqr = sqr(p->x - pos.x) + sqr(p->y - pos.y);
if (distSqr < minDistSqr)
return true;
}
return false;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
static void offsetBySmallRandomAmount(Coord3D& pt, Real maxAmt)
{
pt.x += GameLogicRandomValueReal(-maxAmt, maxAmt);
pt.y += GameLogicRandomValueReal(-maxAmt, maxAmt);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Object* GenerateMinefieldBehavior::placeMineAt(const Coord3D& pt, const ThingTemplate* mineTemplate, Team* team, const Object* producer)
{
Coord3D tmp = pt;
tmp.z = 99999.0f;
PathfindLayerEnum layer = TheTerrainLogic->getHighestLayerForDestination(&tmp);
if (layer == LAYER_GROUND && TheTerrainLogic->isUnderwater(pt.x, pt.y))
return NULL;
if (layer == LAYER_GROUND && TheTerrainLogic->isCliffCell(pt.x, pt.y))
return NULL;
Real orient = GameLogicRandomValueReal(-PI, PI);
// if the mine will be "mostly" under a structure, don't place it.
// for now, "mostly" means "central third of radius would overlap"
const GenerateMinefieldBehaviorModuleData* d = getGenerateMinefieldBehaviorModuleData();
GeometryInfo geom = mineTemplate->getTemplateGeometryInfo();
Real mineRadius = mineTemplate->getTemplateGeometryInfo().getBoundingCircleRadius();
geom.expandFootprint(mineRadius * -(1.0f - d->m_skipIfThisMuchUnderStructure));
ObjectIterator *iter = ThePartitionManager->iteratePotentialCollisions( &pt, geom, orient );
MemoryPoolObjectHolder hold(iter);
for (Object* them = iter->first(); them; them = iter->next())
{
if (them->isKindOf(KINDOF_STRUCTURE))
return NULL;
}
Object* mine = TheThingFactory->newObject(mineTemplate, team);
mine->setPosition(&pt);
mine->setOrientation(orient);
mine->setProducer(producer);
for (BehaviorModule** bmi = mine->getBehaviorModules(); *bmi; ++bmi)
{
LandMineInterface* lmi = (*bmi)->getLandMineInterface();
if (lmi)
{
lmi->setScootParms(*producer->getPosition(), pt);
break;
}
}
// Keep track of the mines
if (mine && d->m_upgradable)
{
m_mineList.push_back(mine->getID());
}
return mine;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::placeMinesAlongLine(const Coord3D& posStart, const Coord3D& posEnd, const ThingTemplate* mineTemplate, Bool skipOneAtStart)
{
const Object* obj = getObject();
const GenerateMinefieldBehaviorModuleData* d = getGenerateMinefieldBehaviorModuleData();
Team* team = obj->getControllingPlayer()->getDefaultTeam();
Real dx = posEnd.x - posStart.x;
Real dy = posEnd.y - posStart.y;
Real len = sqrt(sqr(dx) + sqr(dy));
Real mineRadius = mineTemplate->getTemplateGeometryInfo().getBoundingCircleRadius();
Real mineDiameter = mineRadius * 2.0f;
Real mineJitter = mineRadius*d->m_randomJitter;
Int numMines = REAL_TO_INT_CEIL(len / mineDiameter);
if (numMines < 1)
numMines = 1;
Real inc = len/numMines;
for (Real place = skipOneAtStart ? inc : 0; place <= len; place += inc)
{
Coord3D pt;
pt.x = posStart.x + place * dx / len;
pt.y = posStart.y + place * dy / len;
pt.z = TheTerrainLogic->getGroundHeight( pt.x, pt.y );
offsetBySmallRandomAmount(pt, mineJitter);
placeMineAt(pt, mineTemplate, team, obj);
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
static void makeCorner(const Coord3D& pos, Real majorRadius, Real minorRadius, const Matrix3D& mtx, Coord3D& corner)
{
Vector3 tmp;
tmp.X = majorRadius;
tmp.Y = minorRadius;
tmp.Z = 0;
Matrix3D::Transform_Vector(mtx, tmp, &tmp);
corner.x = tmp.X;
corner.y = tmp.Y;
corner.z = tmp.Z;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::placeMinesAroundRect(const Coord3D& pos, Real majorRadius, Real minorRadius, const ThingTemplate* mineTemplate)
{
const Object* obj = getObject();
const Matrix3D* mtx = obj->getTransformMatrix();
Coord3D pt[4];
makeCorner(pos, majorRadius, minorRadius, *mtx, pt[0]);
makeCorner(pos, -majorRadius, minorRadius, *mtx, pt[1]);
makeCorner(pos, -majorRadius, -minorRadius, *mtx, pt[2]);
makeCorner(pos, majorRadius, -minorRadius, *mtx, pt[3]);
placeMinesAlongLine(pt[0], pt[1], mineTemplate, true);
placeMinesAlongLine(pt[1], pt[2], mineTemplate, true);
placeMinesAlongLine(pt[2], pt[3], mineTemplate, true);
placeMinesAlongLine(pt[3], pt[0], mineTemplate, true);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::placeMinesAroundCircle(const Coord3D& pos, Real radius, const ThingTemplate* mineTemplate)
{
const Object* obj = getObject();
const GenerateMinefieldBehaviorModuleData* d = getGenerateMinefieldBehaviorModuleData();
Team* team = obj->getControllingPlayer()->getDefaultTeam();
Real circum = 2.0f * PI * radius;
Real mineRadius = mineTemplate->getTemplateGeometryInfo().getBoundingCircleRadius();
Real mineDiameter = mineRadius * 2.0f;
Real mineJitter = mineRadius*d->m_randomJitter;
Int numMines = REAL_TO_INT_CEIL(circum / mineDiameter);
if (numMines < 1)
numMines = 1;
Real angleInc = (2*PI)/numMines;
Real angleLim = (2*PI) - angleInc*0.5f;
for (Real angle = 0; angle < angleLim; angle += angleInc)
{
Coord3D pt;
pt.x = pos.x + radius * Cos(angle);
pt.y = pos.y + radius * Sin(angle);
pt.z = TheTerrainLogic->getGroundHeight( pt.x, pt.y );
offsetBySmallRandomAmount(pt, mineJitter);
placeMineAt(pt, mineTemplate, team, obj);
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::placeMinesInFootprint(const GeometryInfo& geom, const ThingTemplate* mineTemplate)
{
const Object* obj = getObject();
const GenerateMinefieldBehaviorModuleData* d = getGenerateMinefieldBehaviorModuleData();
Team* team = obj->getControllingPlayer()->getDefaultTeam();
Real area = geom.getFootprintArea();
Int numMines = REAL_TO_INT_CEIL(d->m_minesPerSquareFoot * area);
if (numMines < 1)
numMines = 1;
const Coord3D* target = getMinefieldTarget();
std::vector<Object*> minesCreatedSoFar;
Real minDistSqr = sqr(mineTemplate->getTemplateGeometryInfo().getBoundingCircleRadius() * 2.0f);
for (int i = 0; i < numMines; ++i)
{
Coord3D pt;
Int maxRetry = 100;
do
{
geom.makeRandomOffsetWithinFootprint(pt);
pt.x += target->x;
pt.y += target->y;
pt.z += target->z;
--maxRetry;
} while (isAnythingTooClose2D(minesCreatedSoFar, pt, minDistSqr) && maxRetry > 0);
DEBUG_ASSERTCRASH(maxRetry>0,("ran out of retries %f",minDistSqr));
if (getObject()->getGeometryInfo().isPointInFootprint(*target, pt))
continue;
Object* mine = placeMineAt(pt, mineTemplate, team, obj); // can return null.
if (mine)
minesCreatedSoFar.push_back(mine);
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::placeMines()
{
if (m_generated)
return;
m_generated = true;
const Object* obj = getObject();
const GenerateMinefieldBehaviorModuleData* d = getGenerateMinefieldBehaviorModuleData();
const ThingTemplate* mineTemplate = 0;
if (m_upgraded)
mineTemplate = TheThingFactory->findTemplate(d->m_mineNameUpgraded);
else
mineTemplate = TheThingFactory->findTemplate(d->m_mineName);
if (!mineTemplate)
{
DEBUG_CRASH(("mine %s not found\n",d->m_mineName.str()));
return;
}
const Coord3D* target = getMinefieldTarget();
if (d->m_smartBorder)
{
GeometryInfo geom = obj->getGeometryInfo();
if (!d->m_smartBorderSkipInterior)
{
geom = mineTemplate->getTemplateGeometryInfo();
placeMineAt(*target, mineTemplate, obj->getControllingPlayer()->getDefaultTeam(), obj);
}
if (d->m_alwaysCircular)
geom.set(GEOMETRY_CYLINDER, false, 1, geom.getBoundingCircleRadius(), geom.getBoundingCircleRadius());
Real mineRadius = mineTemplate->getTemplateGeometryInfo().getBoundingCircleRadius();
Real mineDiameter = mineRadius * 2.0f;
geom.expandFootprint(mineRadius);
do
{
if (geom.getGeomType() == GEOMETRY_BOX && !d->m_alwaysCircular)
{
placeMinesAroundRect(*target, geom.getMajorRadius(), geom.getMinorRadius(), mineTemplate);
}
else
{
placeMinesAroundCircle(*target, geom.getMajorRadius(), mineTemplate);
}
geom.expandFootprint(mineDiameter);
} while (geom.getBoundingCircleRadius() < d->m_distanceAroundObject);
}
else if (d->m_borderOnly)
{
GeometryInfo geom = obj->getGeometryInfo();
geom.expandFootprint(d->m_distanceAroundObject);
if (geom.getGeomType() == GEOMETRY_BOX && !d->m_alwaysCircular)
{
placeMinesAroundRect(*target, geom.getMajorRadius(), geom.getMinorRadius(), mineTemplate);
}
else
{
placeMinesAroundCircle(*target, geom.getMajorRadius(), mineTemplate);
}
}
else
{
GeometryInfo geom = obj->getGeometryInfo();
geom.expandFootprint(d->m_distanceAroundObject);
if (d->m_alwaysCircular)
geom.set(GEOMETRY_CYLINDER, false, 1, geom.getBoundingCircleRadius(), geom.getBoundingCircleRadius());
placeMinesInFootprint(geom, mineTemplate);
}
FXList::doFXObj(d->m_genFX, obj);
}
// ------------------------------------------------------------------------------------------------
UpdateSleepTime GenerateMinefieldBehavior::update()
{
// Test to see if we need to replace the current mines with upgraded ones
if (!m_upgraded && getGenerateMinefieldBehaviorModuleData()->m_upgradable)
{
if (m_generated)
{
// Upgraded minefield to next level for China Player
const UpgradeTemplate *upgradeTemplate = TheUpgradeCenter->findUpgrade( "Upgrade_ChinaEMPMines" );
if (upgradeTemplate)
{
UpgradeMaskType upgradeMask = upgradeTemplate->getUpgradeMask();
UpgradeMaskType objMask = getObject()->getObjectCompletedUpgradeMask();
if (objMask.testForAny(upgradeMask))
{
m_upgraded = TRUE;
// Remove all old mine objects if present
for (std::list<ObjectID>::iterator it = m_mineList.begin(); it != m_mineList.end(); ++it)
{
ObjectID objID = *it;
Object *obj = TheGameLogic->findObjectByID(objID);
if (obj)
{
TheGameLogic->destroyObject(obj);
}
}
m_mineList.clear();
// Place new mines down (Replace old ones that we removed
m_generated = false;
placeMines();
}
}
}
return UPDATE_SLEEP_NONE;
}
return UPDATE_SLEEP_FOREVER;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::crc( Xfer *xfer )
{
// extend base class
BehaviorModule::crc( xfer );
// extend base class
UpgradeMux::upgradeMuxCRC( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// base class
BehaviorModule::xfer( xfer );
// mux "base class"
UpgradeMux::upgradeMuxXfer( xfer );
// generated
xfer->xferBool( &m_generated );
xfer->xferBool( &m_hasTarget );
xfer->xferBool( &m_upgraded );
xfer->xferCoord3D( &m_target );
// spaces info count and objectID data
UnsignedByte spacesCount = m_mineList.size();
xfer->xferUnsignedByte( &spacesCount );
if( xfer->getXferMode() == XFER_SAVE )
{
// save all elements
std::list<ObjectID>::iterator it;
for( it = m_mineList.begin(); it != m_mineList.end(); ++it )
{
// object in this space
xfer->xferObjectID( &(*it) );
} // end for, it
} // end if, save
else if( xfer->getXferMode() == XFER_LOAD )
{
ObjectID objectID;
m_mineList.clear();
// read all elements
std::list<ObjectID>::iterator it;
it = m_mineList.begin();
for(int i = 0; i < spacesCount; ++i )
{
// read object id
xfer->xferObjectID( &objectID );
m_mineList.push_back(objectID);
} // end for, i
} // end else, load
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void GenerateMinefieldBehavior::loadPostProcess( void )
{
// extend base class
BehaviorModule::loadPostProcess();
// extend base class
UpgradeMux::upgradeMuxLoadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,255 @@
/*
** 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: GrantStealthBehavior.cpp ///////////////////////////////////////////////////////////////////////
// Author: Lorenzen
///////////////////////////////////////////////////////////////////////////////////////////////////
// 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/Player.h"
#include "Common/Xfer.h"
#include "GameClient/ParticleSys.h"
#include "GameClient/Anim2D.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Module/GrantStealthBehavior.h"
#include "GameLogic/Module/StealthUpdate.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
struct GrantStealthPlayerScanHelper
{
KindOfMaskType m_kindOfToTest;
Object *m_theGrantor;
ObjectPointerList *m_objectList;
};
static void checkForGrantStealth( Object *testObj, void *userData )
{
GrantStealthPlayerScanHelper *helper = (GrantStealthPlayerScanHelper*)userData;
ObjectPointerList *listToAddTo = helper->m_objectList;
if( testObj->isEffectivelyDead() )
return;
if( testObj->getControllingPlayer() != helper->m_theGrantor->getControllingPlayer() )
return;
if( testObj->isOffMap() )
return;
if( !testObj->isAnyKindOf(helper->m_kindOfToTest) )
return;
listToAddTo->push_back(testObj);
if( testObj->getContain() )
{
// have to tag visible riders too, or they will float around and look silly.
Object *rider = (Object*)testObj->getContain()->friend_getRider();
if( rider )
{
listToAddTo->push_back(rider);
}
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
GrantStealthBehavior::GrantStealthBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
{
const GrantStealthBehaviorModuleData *d = getGrantStealthBehaviorModuleData();
m_radiusParticleSystemID = INVALID_PARTICLE_SYSTEM_ID;
m_currentScanRadius = d->m_startRadius;
Object *obj = getObject();
{
if( d->m_radiusParticleSystemTmpl )
{
ParticleSystem *particleSystem;
particleSystem = TheParticleSystemManager->createParticleSystem( d->m_radiusParticleSystemTmpl );
if( particleSystem )
{
particleSystem->setPosition( obj->getPosition() );
m_radiusParticleSystemID = particleSystem->getSystemID();
}
}
}
setWakeFrame( getObject(), UPDATE_SLEEP_NONE );
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
GrantStealthBehavior::~GrantStealthBehavior( void )
{
if( m_radiusParticleSystemID != INVALID_PARTICLE_SYSTEM_ID )
TheParticleSystemManager->destroyParticleSystemByID( m_radiusParticleSystemID );
}
//-------------------------------------------------------------------------------------------------
/** The update callback. */
//-------------------------------------------------------------------------------------------------
UpdateSleepTime GrantStealthBehavior::update( void )
{
Object *self = getObject();
if ( self->isEffectivelyDead())
return UPDATE_SLEEP_FOREVER;
const GrantStealthBehaviorModuleData *d = getGrantStealthBehaviorModuleData();
// setup scan filters
PartitionFilterRelationship relationship( self, PartitionFilterRelationship::ALLOW_ALLIES );
PartitionFilterSameMapStatus filterMapStatus( self );
PartitionFilterAlive filterAlive;
PartitionFilter *filters[] = { &relationship, &filterAlive, &filterMapStatus, NULL };
m_currentScanRadius += d->m_radiusGrowRate;
Bool thisIsFinalScan = FALSE;
if ( m_currentScanRadius >= d->m_finalRadius )
{
m_currentScanRadius = d->m_finalRadius;
thisIsFinalScan = TRUE;
}
// scan objects in our region
ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( self->getPosition(), m_currentScanRadius, FROM_CENTER_2D, filters );
MemoryPoolObjectHolder hold( iter );
// GRANT STEALTH TO FRIENDLIES IN RADIUS
for( Object *obj = iter->first(); obj; obj = iter->next() )
grantStealthToObject( obj );
if ( thisIsFinalScan )
{
TheGameLogic->destroyObject( self );
return UPDATE_SLEEP_FOREVER;
}
return UPDATE_SLEEP_NONE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void GrantStealthBehavior::grantStealthToObject( Object *obj )
{
if ( obj == getObject() )
return;
const GrantStealthBehaviorModuleData *d = getGrantStealthBehaviorModuleData();
if ( ! obj->isAnyKindOf( d->m_kindOf ) )
return;
StealthUpdate* stealth = obj->getStealth();
if( stealth )
{
stealth->receiveGrant();
Drawable *draw = obj->getDrawable();
if ( draw )
{
draw->flashAsSelected();
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void GrantStealthBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void GrantStealthBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// particle system id
xfer->xferUser( &m_radiusParticleSystemID, sizeof( ParticleSystemID ) );
// Timer safety
xfer->xferReal( &m_currentScanRadius );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void GrantStealthBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,215 @@
/*
** 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: InstantDeathBehavior.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/RandomValue.h"
#include "Common/GameLOD.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/InstantDeathBehavior.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectCreationList.h"
#include "GameLogic/Weapon.h"
#include "GameClient/Drawable.h"
//-------------------------------------------------------------------------------------------------
InstantDeathBehaviorModuleData::InstantDeathBehaviorModuleData()
{
// redundant.
//m_fx.clear();
//m_ocls.clear();
//m_weapons.clear();
}
//-------------------------------------------------------------------------------------------------
static void parseFX( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ )
{
InstantDeathBehaviorModuleData* self = (InstantDeathBehaviorModuleData*)instance;
for (const char* token = ini->getNextToken(); token != NULL; token = ini->getNextTokenOrNull())
{
const FXList *fxl = TheFXListStore->findFXList((token)); // could be null! this is OK!
self->m_fx.push_back(fxl);
}
}
//-------------------------------------------------------------------------------------------------
static void parseOCL( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ )
{
InstantDeathBehaviorModuleData* self = (InstantDeathBehaviorModuleData*)instance;
for (const char* token = ini->getNextToken(); token != NULL; token = ini->getNextTokenOrNull())
{
const ObjectCreationList *ocl = TheObjectCreationListStore->findObjectCreationList(token); // could be null! this is OK!
self->m_ocls.push_back(ocl);
}
}
//-------------------------------------------------------------------------------------------------
static void parseWeapon( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ )
{
InstantDeathBehaviorModuleData* self = (InstantDeathBehaviorModuleData*)instance;
for (const char* token = ini->getNextToken(); token != NULL; token = ini->getNextTokenOrNull())
{
const WeaponTemplate *wt = TheWeaponStore->findWeaponTemplate(token); // could be null! this is OK!
self->m_weapons.push_back(wt);
}
}
//-------------------------------------------------------------------------------------------------
/*static*/ void InstantDeathBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p)
{
DieModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "FX", parseFX, NULL, 0 },
{ "OCL", parseOCL, NULL, 0 },
{ "Weapon", parseWeapon, NULL, 0 },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
InstantDeathBehavior::InstantDeathBehavior( Thing *thing, const ModuleData* moduleData ) : DieModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
InstantDeathBehavior::~InstantDeathBehavior( void )
{
}
//-------------------------------------------------------------------------------------------------
void InstantDeathBehavior::onDie( const DamageInfo *damageInfo )
{
if (!isDieApplicable(damageInfo))
return;
AIUpdateInterface* ai = getObject()->getAIUpdateInterface();
if (ai)
{
// has another AI already handled us. (hopefully another InstantDeathBehavior)
if (ai->isAiInDeadState())
return;
ai->markAsDead();
}
const InstantDeathBehaviorModuleData* d = getInstantDeathBehaviorModuleData();
Int idx, listSize;
listSize = d->m_fx.size();
if (listSize > 0)
{
idx = GameLogicRandomValue(0, listSize-1);
const FXListVec& v = d->m_fx;
DEBUG_ASSERTCRASH(idx>=0&&idx<v.size(),("bad idx"));
const FXList* fxl = v[idx];
FXList::doFXObj(fxl, getObject(), NULL);
}
listSize = d->m_ocls.size();
if (listSize > 0)
{
idx = GameLogicRandomValue(0, listSize-1);
const OCLVec& v = d->m_ocls;
DEBUG_ASSERTCRASH(idx>=0&&idx<v.size(),("bad idx"));
const ObjectCreationList* ocl = v[idx];
ObjectCreationList::create(ocl, getObject(), NULL);
}
listSize = d->m_weapons.size();
if (listSize > 0)
{
idx = GameLogicRandomValue(0, listSize-1);
const WeaponTemplateVec& v = d->m_weapons;
DEBUG_ASSERTCRASH(idx>=0&&idx<v.size(),("bad idx"));
const WeaponTemplate* wt = v[idx];
if (wt)
{
TheWeaponStore->createAndFireTempWeapon(wt, getObject(), getObject()->getPosition());
}
}
TheGameLogic->destroyObject(getObject());
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void InstantDeathBehavior::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void InstantDeathBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
DieModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void InstantDeathBehavior::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,393 @@
/*
** 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: JetSlowDeathBehavior.cpp /////////////////////////////////////////////////////////////////
// Author: Colin Day
// Desc: Death sequence for jets
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GlobalData.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/FXList.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Locomotor.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/JetSlowDeathBehavior.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectCreationList.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
JetSlowDeathBehaviorModuleData::JetSlowDeathBehaviorModuleData( void )
{
m_fxOnGroundDeath = NULL;
m_oclOnGroundDeath = NULL;
m_fxInitialDeath = NULL;
m_oclInitialDeath = NULL;
m_delaySecondaryFromInitialDeath = 0;
m_fxSecondary = NULL;
m_oclSecondary = NULL;
m_fxHitGround = NULL;
m_oclHitGround = NULL;
m_delayFinalBlowUpFromHitGround = 0;
m_fxFinalBlowUp = NULL;
m_oclFinalBlowUp = NULL;
m_rollRate = 0.0f;
m_rollRateDelta = 1.0f;
m_pitchRate = 0.0f;
m_fallHowFast = 0.0f;
} // end JetSlowDeathBehaviorModuleData
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void JetSlowDeathBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
SlowDeathBehaviorModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "FXOnGroundDeath", INI::parseFXList, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_fxOnGroundDeath ) },
{ "OCLOnGroundDeath", INI::parseObjectCreationList, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_oclOnGroundDeath ) },
{ "FXInitialDeath", INI::parseFXList, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_fxInitialDeath ) },
{ "OCLInitialDeath", INI::parseObjectCreationList, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_oclInitialDeath ) },
{ "DelaySecondaryFromInitialDeath", INI::parseDurationUnsignedInt, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_delaySecondaryFromInitialDeath ) },
{ "FXSecondary", INI::parseFXList, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_fxSecondary ) },
{ "OCLSecondary", INI::parseObjectCreationList, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_oclSecondary ) },
{ "FXHitGround", INI::parseFXList, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_fxHitGround ) },
{ "OCLHitGround", INI::parseObjectCreationList, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_oclHitGround ) },
{ "DelayFinalBlowUpFromHitGround", INI::parseDurationUnsignedInt, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_delayFinalBlowUpFromHitGround ) },
{ "FXFinalBlowUp", INI::parseFXList, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_fxFinalBlowUp ) },
{ "OCLFinalBlowUp", INI::parseObjectCreationList, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_oclFinalBlowUp ) },
{ "DeathLoopSound", INI::parseAudioEventRTS, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_deathLoopSound ) },
// @todo srj -- RollRate and RollRateDelta and PitchRate should use parseAngularVelocityReal
{ "RollRate", INI::parseReal, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_rollRate ) },
{ "RollRateDelta", INI::parsePercentToReal, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_rollRateDelta ) },
{ "PitchRate", INI::parseReal, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_pitchRate ) },
{ "FallHowFast", INI::parsePercentToReal, NULL, offsetof( JetSlowDeathBehaviorModuleData, m_fallHowFast ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
JetSlowDeathBehavior::JetSlowDeathBehavior( Thing *thing, const ModuleData *moduleData )
: SlowDeathBehavior( thing, moduleData )
{
m_timerDeathFrame = 0;
m_timerOnGroundFrame = 0;
m_rollRate = 0.0f;
} // end JetSlowDeathBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
JetSlowDeathBehavior::~JetSlowDeathBehavior( void )
{
} // end ~JetSlowDeathBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void JetSlowDeathBehavior::onDie( const DamageInfo *damageInfo )
{
Object *us = getObject();
// if the jet is on the ground we do just our ground fx death
if( us->isSignificantlyAboveTerrain() == FALSE || us->getStatusBits().test( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) )
{
const JetSlowDeathBehaviorModuleData *modData = getJetSlowDeathBehaviorModuleData();
// execute fx
FXList::doFXObj( modData->m_fxOnGroundDeath, us );
// execute ocl
ObjectCreationList::create( modData->m_oclOnGroundDeath, us, NULL );
// destroy object
TheGameLogic->destroyObject( us );
} // end if
else
{
// extend base class for slow death and begin the slow death behavior
SlowDeathBehavior::onDie( damageInfo );
} // end else
getObject()->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_DECK_HEIGHT_OFFSET ) );
} // end onDie
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void JetSlowDeathBehavior::beginSlowDeath( const DamageInfo *damageInfo )
{
// extend functionality
SlowDeathBehavior::beginSlowDeath( damageInfo );
// get our info
Object *us = getObject();
const JetSlowDeathBehaviorModuleData *modData = getJetSlowDeathBehaviorModuleData();
// record the frame we died on
m_timerDeathFrame = TheGameLogic->getFrame();
// do some effects
FXList::doFXObj( modData->m_fxInitialDeath, us );
ObjectCreationList::create( modData->m_oclInitialDeath, us, NULL );
// start audio loop playing
m_deathLoopSound = modData->m_deathLoopSound;
if( m_deathLoopSound.getEventName().isEmpty() == FALSE )
{
m_deathLoopSound.setObjectID( us->getID() );
m_deathLoopSound.setPlayingHandle( TheAudio->addAudioEvent( &m_deathLoopSound ) );
} // end if
// initialize our roll rate to that defined as the initial value in the module data
m_rollRate = modData->m_rollRate;
// set the locomotor so that the plane starts falling
Locomotor *locomotor = us->getAIUpdateInterface()->getCurLocomotor();
locomotor->setMaxLift( -TheGlobalData->m_gravity * (1.0f - modData->m_fallHowFast) );
// do not allow the jet to turn anymore
locomotor->setMaxTurnRate( 0.0f );
} // end beginSlowDeath
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime JetSlowDeathBehavior::update( void )
{
// extend functionality of base class
SlowDeathBehavior::update();
// if the death is not activated, do nothing else
if( isSlowDeathActivated() == FALSE )
return UPDATE_SLEEP_NONE;
// get object info
Object *us = getObject();
const JetSlowDeathBehaviorModuleData *modData = getJetSlowDeathBehaviorModuleData();
// roll us around in the air
PhysicsBehavior *physics = us->getPhysics();
DEBUG_ASSERTCRASH( physics, ("JetSlowDeathBehavior::beginSlowDeath - '%s' has no physics\n",
us->getTemplate()->getName().str()) );
if( physics )
physics->setRollRate( m_rollRate );
// adjust the roll rate over time
m_rollRate *= modData->m_rollRateDelta;
// do effects for death while in the air
if( m_timerOnGroundFrame == 0 )
{
PathfindLayerEnum layer = TheTerrainLogic->getLayerForDestination(us->getPosition());
us->setLayer(layer);
Real height;
if (layer == LAYER_GROUND)
{
// (this is more efficient than getGroundHeight because the info is cached)
height = us->getHeightAboveTerrain();
}
else
{
Real layerHeight = TheTerrainLogic->getLayerHeight( us->getPosition()->x, us->getPosition()->y, layer );
height = us->getPosition()->z - layerHeight;
// slop a little bit for bridges, since we tend to end up fractionally
// above 'em, and it's easier to just slop it here
if (height >= 0.0f && height <= 1.0f)
height = 0.0f;
}
Bool hitATree = FALSE;
// Here we want to make sure we crash if we collide with a tree on the way down
PhysicsBehavior *phys = us->getPhysics();
if ( m_timerOnGroundFrame == 0 && phys )
{
ObjectID treeID = phys->getLastCollidee();
Object *tree = TheGameLogic->findObjectByID( treeID );
if ( tree )
{
if (tree->isKindOf( KINDOF_SHRUBBERY ) )
hitATree = TRUE;
}
}
// when we've hit the ground, we're totally done
if( height <= 0.0f || hitATree )
{
// stop the death looping sound at the right time
TheAudio->removeAudioEvent( m_deathLoopSound.getPlayingHandle() );
// do some effects
FXList::doFXObj( modData->m_fxHitGround, us );
ObjectCreationList::create( modData->m_oclHitGround, us, NULL );
// we are now on the ground
m_timerOnGroundFrame = TheGameLogic->getFrame();
// start us rolling on another axis too
if( physics )
physics->setPitchRate( modData->m_pitchRate );
} // end if
// timers for the secondary effect
if( m_timerDeathFrame != 0 &&
TheGameLogic->getFrame() - m_timerDeathFrame >= modData->m_delaySecondaryFromInitialDeath )
{
// do some effects
FXList::doFXObj( modData->m_fxSecondary, us );
ObjectCreationList::create( modData->m_oclSecondary, us, NULL );
// clear the death frame timer since we've already executed the event now
m_timerDeathFrame = 0;
} //end if
} // end if
else
{
// we are on the ground, pay attention to the final explosion timers
if( TheGameLogic->getFrame() - m_timerOnGroundFrame >= modData->m_delayFinalBlowUpFromHitGround )
{
// do some effects
FXList::doFXObj( modData->m_fxFinalBlowUp, us );
ObjectCreationList::create( modData->m_oclFinalBlowUp, us, NULL );
// we're all done now
TheGameLogic->destroyObject( us );
} // end if
} // end else
return UPDATE_SLEEP_NONE;
} // end update
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void JetSlowDeathBehavior::crc( Xfer *xfer )
{
// extend base class
SlowDeathBehavior::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void JetSlowDeathBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
SlowDeathBehavior::xfer( xfer );
// timer death frame
xfer->xferUnsignedInt( &m_timerDeathFrame );
// on ground frame
xfer->xferUnsignedInt( &m_timerOnGroundFrame );
// roll rate
xfer->xferReal( &m_rollRate );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void JetSlowDeathBehavior::loadPostProcess( void )
{
// extend base class
SlowDeathBehavior::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,707 @@
/*
** 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: MinefieldBehavior.cpp //////////////////////////////////////////////////////////////////
// Author: Steven Johnson, June 2002
// Desc: Minefield behavior
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_RELATIONSHIP_NAMES
#include "Common/GameState.h"
#include "Common/RandomValue.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#include "GameLogic/Module/MinefieldBehavior.h"
#include "GameLogic/Module/AutoHealBehavior.h"
#include "GameLogic/Weapon.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// detonation never puts our health below this, since we probably auto-regen
const Real MIN_HEALTH = 0.1f;
//-------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
MinefieldBehaviorModuleData::MinefieldBehaviorModuleData()
{
m_detonationWeapon = NULL;
m_detonatedBy = (1 << ENEMIES) | (1 << NEUTRAL);
m_stopsRegenAfterCreatorDies = true;
m_regenerates = false;
m_workersDetonate = false;
m_creatorDeathCheckRate = LOGICFRAMES_PER_SECOND;
m_scootFromStartingPointTime = 0;
m_repeatDetonateMoveThresh = 1.0f;
m_numVirtualMines = 1;
m_healthPercentToDrainPerSecond = 0.0f;
m_ocl = 0;
}
//-------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void MinefieldBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
UpdateModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "DetonationWeapon", INI::parseWeaponTemplate, NULL, offsetof( MinefieldBehaviorModuleData, m_detonationWeapon ) },
{ "DetonatedBy", INI::parseBitString32, TheRelationshipNames, offsetof( MinefieldBehaviorModuleData, m_detonatedBy ) },
{ "StopsRegenAfterCreatorDies", INI::parseBool, NULL, offsetof( MinefieldBehaviorModuleData, m_stopsRegenAfterCreatorDies ) },
{ "Regenerates", INI::parseBool, NULL, offsetof( MinefieldBehaviorModuleData, m_regenerates ) },
{ "WorkersDetonate", INI::parseBool, NULL, offsetof( MinefieldBehaviorModuleData, m_workersDetonate ) },
{ "CreatorDeathCheckRate", INI::parseDurationUnsignedInt, NULL, offsetof( MinefieldBehaviorModuleData, m_creatorDeathCheckRate ) },
{ "ScootFromStartingPointTime", INI::parseDurationUnsignedInt, NULL, offsetof( MinefieldBehaviorModuleData, m_scootFromStartingPointTime ) },
{ "NumVirtualMines", INI::parseUnsignedInt, NULL, offsetof( MinefieldBehaviorModuleData, m_numVirtualMines ) },
{ "RepeatDetonateMoveThresh", INI::parseReal, NULL, offsetof( MinefieldBehaviorModuleData, m_repeatDetonateMoveThresh ) },
{ "DegenPercentPerSecondAfterCreatorDies", INI::parsePercentToReal, NULL, offsetof( MinefieldBehaviorModuleData, m_healthPercentToDrainPerSecond ) },
{ "CreationList", INI::parseObjectCreationList, NULL, offsetof( MinefieldBehaviorModuleData, m_ocl ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
}
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
MinefieldBehavior::MinefieldBehavior( Thing *thing, const ModuleData* moduleData )
: UpdateModule( thing, moduleData )
{
const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
m_nextDeathCheckFrame = 0;
m_scootFramesLeft = 0;
m_scootVel.zero();
m_scootAccel.zero();
m_detonators.clear();
m_ignoreDamage = false;
m_regenerates = d->m_regenerates;
m_draining = false;
m_virtualMinesRemaining = d->m_numVirtualMines;
for (Int i = 0; i < MAX_IMMUNITY; ++i)
{
m_immunes[i].id = INVALID_ID;
m_immunes[i].collideTime = 0;
}
// start off awake, and we will calcSleepTime from here on
setWakeFrame( getObject(), UPDATE_SLEEP_NONE );
// mines aren't auto-acquirable
getObject()->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_ATTACK_FROM_AI ) );
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
MinefieldBehavior::~MinefieldBehavior()
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime MinefieldBehavior::calcSleepTime()
{
const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
// if we're draining we have to update every frame
if (m_draining)
return UPDATE_SLEEP_NONE;
// if we're scooting we need to update every frame
if( m_scootFramesLeft > 0 )
return UPDATE_SLEEP_NONE;
// if there is anybody in our immulity monitoring we need to update every frame
for( Int i = 0; i < MAX_IMMUNITY; ++i )
if( m_immunes[ i ].id != INVALID_ID )
return UPDATE_SLEEP_NONE;
UnsignedInt sleepTime = FOREVER;
UnsignedInt now = TheGameLogic->getFrame();
//
// sleep until the next death check frame we already have figured outif we care
// about it (that is, when our creator dies)
//
if (m_regenerates && d->m_stopsRegenAfterCreatorDies)
sleepTime = min( sleepTime, m_nextDeathCheckFrame - now );
// if we don't want to sleep forever, prevent 0 frame sleeps
if( sleepTime == 0 )
sleepTime = 1;
// sleep forever
return UPDATE_SLEEP( sleepTime );
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UpdateSleepTime MinefieldBehavior::update()
{
Object* obj = getObject();
const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
UnsignedInt now = TheGameLogic->getFrame();
if (m_scootFramesLeft > 0)
{
Coord3D pt = *obj->getPosition();
m_scootVel.x += m_scootAccel.x;
m_scootVel.y += m_scootAccel.y;
m_scootVel.z += m_scootAccel.z;
pt.x += m_scootVel.x;
pt.y += m_scootVel.y;
pt.z += m_scootVel.z;
// srj sez: scooting mines always go on the highest layer.
Coord3D tmp = pt;
tmp.z = 99999.0f;
PathfindLayerEnum newLayer = TheTerrainLogic->getHighestLayerForDestination(&tmp);
obj->setLayer(newLayer);
Real ground = TheTerrainLogic->getLayerHeight( pt.x, pt.y, newLayer );
if (newLayer != LAYER_GROUND)
{
// ensure we are slightly above the bridge, to account for fudge & sloppy art
const Real FUDGE = 1.0f;
ground += FUDGE;
}
if (pt.z < ground || m_scootFramesLeft <= 1)
pt.z = ground;
obj->setPosition(&pt);
--m_scootFramesLeft;
}
// check for expired immunities.
for (Int i = 0; i < MAX_IMMUNITY; ++i)
{
if (m_immunes[i].id == INVALID_ID)
continue;
if (TheGameLogic->findObjectByID(m_immunes[i].id) == NULL ||
now > m_immunes[i].collideTime + 2)
{
//DEBUG_LOG(("expiring an immunity %d\n",m_immunes[i].id));
m_immunes[i].id = INVALID_ID; // he's dead, jim.
m_immunes[i].collideTime = 0;
}
}
if (now >= m_nextDeathCheckFrame)
{
// check to see if there is an enemy building on me... since enemy buildings can be build on top of me
// check to see if the building that made me is gone, and whether therefore, I should go
if (m_regenerates && d->m_stopsRegenAfterCreatorDies)
{
m_nextDeathCheckFrame = now + d->m_creatorDeathCheckRate;
ObjectID producerID = getObject()->getProducerID();
if (producerID != INVALID_ID)
{
Object* producer = TheGameLogic->findObjectByID(producerID);
if (producer == NULL || producer->isEffectivelyDead())
{
m_regenerates = false;
m_draining = true;
static const NameKeyType key_AutoHealBehavior = NAMEKEY("AutoHealBehavior");
AutoHealBehavior* ahb = (AutoHealBehavior*)obj->findUpdateModule( key_AutoHealBehavior );
if (ahb)
ahb->stopHealing();
}
}
}
}
if (m_draining)
{
DamageInfo damageInfo;
damageInfo.in.m_amount = (obj->getBodyModule()->getMaxHealth() * d->m_healthPercentToDrainPerSecond) / LOGICFRAMES_PER_SECOND;
damageInfo.in.m_sourceID = obj->getID();
damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
damageInfo.in.m_deathType = DEATH_NORMAL;
obj->attemptDamage( &damageInfo );
}
return calcSleepTime();
}
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void MinefieldBehavior::detonateOnce(const Coord3D& position)
{
const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
if (d->m_detonationWeapon)
{
Object* obj = getObject();
TheWeaponStore->createAndFireTempWeapon(d->m_detonationWeapon, obj, &position);
}
if (m_virtualMinesRemaining > 0)
--m_virtualMinesRemaining;
if (!m_regenerates && m_virtualMinesRemaining == 0)
{
TheGameLogic->destroyObject(getObject());
}
else
{
Real percent = (Real)m_virtualMinesRemaining / (Real)d->m_numVirtualMines;
BodyModuleInterface* body = getObject()->getBodyModule();
Real health = body->getHealth();
Real desired = percent * body->getMaxHealth();
if (desired < MIN_HEALTH)
desired = MIN_HEALTH;
Real amount = health - desired;
if (amount > 0.0f)
{
m_ignoreDamage = true;
//body->internalChangeHealth(desired - health);
//can't use this, AutoHeal won't work unless we go thru normal damage stuff
DamageInfo extraDamageInfo;
extraDamageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
extraDamageInfo.in.m_deathType = DEATH_NONE;
extraDamageInfo.in.m_sourceID = getObject()->getID();
extraDamageInfo.in.m_amount = amount;
getObject()->attemptDamage(&extraDamageInfo);
m_ignoreDamage = false;
}
}
if (m_virtualMinesRemaining == 0)
{
getObject()->setModelConditionState(MODELCONDITION_RUBBLE);
getObject()->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) );
}
else
{
getObject()->clearModelConditionState(MODELCONDITION_RUBBLE);
getObject()->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) );
}
if (d->m_ocl)
{
ObjectCreationList::create(d->m_ocl, getObject(), getObject());
}
}
//-----------------------------------------------------------------------------
static Real calcDistSquared(const Coord3D& a, const Coord3D& b)
{
return sqr(a.x - b.x) + sqr(a.y - b.y) + sqr(a.z - b.z);
}
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void MinefieldBehavior::onCollide( Object *other, const Coord3D *loc, const Coord3D *normal )
{
if (other == NULL || other->isEffectivelyDead())
return;
if (m_virtualMinesRemaining == 0)
return;
Object* obj = getObject();
const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
UnsignedInt now = TheGameLogic->getFrame();
// is this guy in our immune list?
// NOTE NOTE NOTE, must always do this check FIRST so that 'collideTime' is updated...
for (Int i = 0; i < MAX_IMMUNITY; ++i)
{
if (m_immunes[i].id == other->getID())
{
//DEBUG_LOG(("ignoring due to immunity %d\n",m_immunes[i].id));
m_immunes[i].collideTime = now;
return;
}
}
if (!d->m_workersDetonate)
{
// infantry+dozer=worker.
if (other->isKindOf(KINDOF_INFANTRY) && other->isKindOf(KINDOF_DOZER))
return;
}
Int requiredMask = 0;
Relationship r = obj->getRelationship(other);
if (r == ALLIES) requiredMask = (1 << ALLIES);
else if (r == ENEMIES) requiredMask = (1 << ENEMIES);
else if (r == NEUTRAL) requiredMask = (1 << NEUTRAL);
if ((d->m_detonatedBy & requiredMask) == 0)
return;
// are we active?
if (m_scootFramesLeft > 0)
return;
// things that are in the process of clearing mines are immune to mine detonation,
// even if we aren't the specific mine they are trying to clear. (however, they must
// have a real mine they area trying to clear... it's possible they could be trying to
// clear a position where there is no mine, in which case we grant them no immunity, muwahahaha)
AIUpdateInterface* otherAI = other->getAI();
if (otherAI && otherAI->isClearingMines() && otherAI->getGoalObject() != NULL)
{
// mine-clearers are granted immunity to us for as long as they continuously
// collide, even if no longer clearing mines. (this prevents the problem
// of a guy who touches two close-together mines while clearing, then puts up his
// detector and is blown to smithereens by the other one.)
for (Int i = 0; i < MAX_IMMUNITY; ++i)
{
if (m_immunes[i].id == INVALID_ID || m_immunes[i].id == other->getID())
{
//DEBUG_LOG(("add/update immunity %d\n",m_immunes[i].id));
m_immunes[i].id = other->getID();
m_immunes[i].collideTime = now;
// wake up
setWakeFrame( obj, calcSleepTime() );
break;
}
}
return;
}
// if we detonated another one nearby, we have to move a little bit to detonate another one.
Bool found = false;
for (std::vector<DetonatorInfo>::iterator it = m_detonators.begin(); it != m_detonators.end(); ++it)
{
if (other->getID() == it->id)
{
found = TRUE;
Real distSqr = calcDistSquared(*other->getPosition(), it->where);
if (distSqr <= sqr(d->m_repeatDetonateMoveThresh))
{
// too close. punt for now.
return;
}
else
{
// far enough. update the loc, then break out and blow up.
it->where = *other->getPosition();
break;
}
}
}
if (!found)
{
// add him to the list.
DetonatorInfo detInfo;
detInfo.id = other->getID();
detInfo.where = *other->getPosition();
m_detonators.push_back(detInfo);
}
Coord3D detPt = *other->getPosition();
obj->getGeometryInfo().clipPointToFootprint(*obj->getPosition(), detPt);
detonateOnce(detPt);
}
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void MinefieldBehavior::onDamage( DamageInfo *damageInfo )
{
if (m_ignoreDamage)
return;
const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
// detonate as many times as neccessary for our virtual mine count to match our health
BodyModuleInterface* body = getObject()->getBodyModule();
for (;;)
{
Real virtualMinesExpectedF = ((Real)d->m_numVirtualMines * body->getHealth() / body->getMaxHealth());
Int virtualMinesExpected =
damageInfo->in.m_damageType == DAMAGE_HEALING ?
REAL_TO_INT_FLOOR(virtualMinesExpectedF) :
REAL_TO_INT_CEIL(virtualMinesExpectedF);
if (virtualMinesExpected > d->m_numVirtualMines)
virtualMinesExpected = d->m_numVirtualMines;
if (m_virtualMinesRemaining < virtualMinesExpected)
{
m_virtualMinesRemaining = virtualMinesExpected;
}
else if (m_virtualMinesRemaining > virtualMinesExpected)
{
if (m_draining &&
damageInfo->in.m_sourceID == getObject()->getID() &&
damageInfo->in.m_damageType == DAMAGE_UNRESISTABLE)
{
// don't detonate.... just ditch a mine
--m_virtualMinesRemaining;
}
else
{
detonateOnce(*getObject()->getPosition());
}
}
else
{
break;
}
}
if (m_virtualMinesRemaining == 0)
{
// oops, if someone did weapon damage they may have nuked our health to zero,
// which would be bad if we regen. prevent this. (srj)
if (m_regenerates && body->getHealth() < MIN_HEALTH)
{
body->internalChangeHealth(MIN_HEALTH - body->getHealth());
}
getObject()->setModelConditionState(MODELCONDITION_RUBBLE);
getObject()->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) );
}
else
{
getObject()->clearModelConditionState(MODELCONDITION_RUBBLE);
getObject()->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) );
}
}
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void MinefieldBehavior::onHealing( DamageInfo *damageInfo )
{
onDamage(damageInfo);
}
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void MinefieldBehavior::onDie( const DamageInfo *damageInfo )
{
TheGameLogic->destroyObject(getObject());
}
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void MinefieldBehavior::disarm()
{
if (!m_regenerates)
{
TheGameLogic->destroyObject(getObject());
return;
}
// detonation never puts our health below this, since we probably auto-regen
const Real MIN_HEALTH = 0.1f;
BodyModuleInterface* body = getObject()->getBodyModule();
Real desired = MIN_HEALTH;
Real amount = body->getHealth() - desired;
m_ignoreDamage = true;
//body->internalChangeHealth(desired - health);
//can't use this, AutoHeal won't work unless we go thru normal damage stuff
DamageInfo extraDamageInfo;
extraDamageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
extraDamageInfo.in.m_deathType = DEATH_NONE;
extraDamageInfo.in.m_sourceID = getObject()->getID();
extraDamageInfo.in.m_amount = amount;
getObject()->attemptDamage(&extraDamageInfo);
m_ignoreDamage = false;
m_virtualMinesRemaining = 0;
getObject()->setModelConditionState(MODELCONDITION_RUBBLE);
getObject()->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_MASKED ) );
}
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void MinefieldBehavior::setScootParms(const Coord3D& start, const Coord3D& end)
{
Object* obj = getObject();
const MinefieldBehaviorModuleData* d = getMinefieldBehaviorModuleData();
UnsignedInt scootFromStartingPointTime = d->m_scootFromStartingPointTime;
Coord3D endOnGround = end;
endOnGround.z = TheTerrainLogic->getGroundHeight( endOnGround.x, endOnGround.y );
if (start.z > endOnGround.z)
{
// figure out how long it will take to fall, and replace scoot time with that
UnsignedInt fallingTime = REAL_TO_INT_CEIL(sqrtf(2.0f * (start.z - endOnGround.z) / fabs(TheGlobalData->m_gravity)));
// we can scoot after we land, but don't want to stop scooting before we land
if (scootFromStartingPointTime < fallingTime)
scootFromStartingPointTime = fallingTime;
}
if (scootFromStartingPointTime == 0)
{
obj->setPosition(&endOnGround);
m_scootFramesLeft = 0;
}
else
{
// x = x0 + vt + 0.5at^2
// thus 2(dx - vt)/t^2 = a
Real dx = endOnGround.x - start.x;
Real dy = endOnGround.y - start.y;
Real dz = endOnGround.z - start.z;
Real dist = sqrt(sqr(dx) + sqr(dy));
if (dist <= 0.1f && fabs(dz) <= 0.1f)
{
obj->setPosition(&endOnGround);
m_scootFramesLeft = 0;
}
else
{
Real t = (Real)scootFromStartingPointTime;
Real scootFromStartingPointSpeed = dist / t;
Real accelMag = fabs(2.0f * (dist - scootFromStartingPointSpeed*t)/sqr(t));
Real dxNorm = (dist <= 0.1f) ? 0.0f : (dx / dist);
Real dyNorm = (dist <= 0.1f) ? 0.0f : (dy / dist);
m_scootVel.x = dxNorm * scootFromStartingPointSpeed;
m_scootVel.y = dyNorm * scootFromStartingPointSpeed;
m_scootAccel.x = -dxNorm * accelMag;
m_scootAccel.y = -dyNorm * accelMag;
m_scootAccel.z = TheGlobalData->m_gravity;
obj->setPosition(&start);
m_scootFramesLeft = scootFromStartingPointTime;
// we need to wake ourselves up because we could be lying here sleeping forever
setWakeFrame( obj, calcSleepTime() );
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void MinefieldBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void MinefieldBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// mines remaining
/// @todo srj -- ensure health, appearance, etc are correct for save/reload! post-MP!
xfer->xferUnsignedInt( &m_virtualMinesRemaining );
// next death check frame
xfer->xferUnsignedInt( &m_nextDeathCheckFrame );
// scoot frames left
xfer->xferUnsignedInt( &m_scootFramesLeft );
// scoot velocity
xfer->xferCoord3D( &m_scootVel );
// scoot acceleration
xfer->xferCoord3D( &m_scootAccel );
xfer->xferBool( &m_ignoreDamage );
xfer->xferBool( &m_regenerates );
xfer->xferBool( &m_draining );
// immunities
UnsignedByte maxImmunity = MAX_IMMUNITY;
xfer->xferUnsignedByte( &maxImmunity );
if( maxImmunity != MAX_IMMUNITY )
{
DEBUG_CRASH(( "MinefieldBehavior::xfer - MAX_IMMUNITY has changed size, you must version this code and then you can remove this error message\n" ));
throw SC_INVALID_DATA;
} // end if
for( UnsignedByte i = 0; i < maxImmunity; ++i )
{
// object id
xfer->xferObjectID( &m_immunes[ i ].id );
// collide time
xfer->xferUnsignedInt( &m_immunes[ i ].collideTime );
} // end for, i
if( xfer->getXferMode() == XFER_LOAD )
m_detonators.clear();
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void MinefieldBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,204 @@
/*
** 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: NeutronBlastBehavior.cpp ///////////////////////////////////////////////////////////////////////
// Author: Daniel Teh
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameLogic/Module/NeutronBlastBehavior.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameClient/Drawable.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
NeutronBlastBehavior::NeutronBlastBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
{
setWakeFrame( getObject(), UPDATE_SLEEP_FOREVER );
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
NeutronBlastBehavior::~NeutronBlastBehavior()
{
// GAME STUFF DOES NOT GO IN THE DESTRUCTOR
// (Crash if end game with Neutron shell in air)
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void NeutronBlastBehavior::onDie( const DamageInfo *damageInfo )
{
// On death, perform the Neutron Blast!!
Object *self = getObject();
if (!self)
return;
const NeutronBlastBehaviorModuleData *data = getNeutronBlastBehaviorModuleData();
Real blastRadius = data->m_blastRadius;
Bool hitAir = data->m_isAffectAirborne;
// setup scan filters
PartitionFilterSameMapStatus filterMapStatus( self );
PartitionFilterAlive filterAlive;
PartitionFilter *filters[] = { &filterAlive, &filterMapStatus, NULL };
// scan objects in our region
ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( self->getPosition(), blastRadius, FROM_CENTER_2D, filters );
MemoryPoolObjectHolder hold( iter );
// Apply neutron blast to object
for( Object *obj = iter->first(); obj; obj = iter->next() )
{
if( hitAir || ( !obj->isKindOf(KINDOF_AIRCRAFT) && !obj->isAirborneTarget() ) )
{
neutronBlastToObject( obj );
}
}
}
//-------------------------------------------------------------------------------------------------
/** The update callback. */
//-------------------------------------------------------------------------------------------------
UpdateSleepTime NeutronBlastBehavior::update( void )
{
return UPDATE_SLEEP_FOREVER;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void NeutronBlastBehavior::neutronBlastToObject( Object *obj )
{
// early exit check
if ( !obj || obj == getObject() )
return;
// Check for allies and quick exit if we are not suppose to hurt our own.
const NeutronBlastBehaviorModuleData *data = getNeutronBlastBehaviorModuleData();
if (!data->m_affectAllies && getObject()->getRelationship( obj ) == ALLIES)
{
return;
}
// Kill if object is infantry
if (obj->isKindOf(KINDOF_INFANTRY))
{
obj->kill();
}
// Kill all contained if it is a container
ContainModuleInterface *contain = obj->getContain();
if( contain )
{
contain->killAllContained();
}
// Kill pilots of vehicles
if( obj->isKindOf( KINDOF_VEHICLE ) && !obj->isKindOf( KINDOF_DRONE ) )
{
// If the vehicle is a combat bike, kill the whole thing
if ( obj->isKindOf( KINDOF_CLIFF_JUMPER ) )
{
obj->kill();
}
// Just kill the pilot of the vehicle
else
{
// Make it unmanned, so units can easily check the ability to "take control of it"
obj->setDisabled( DISABLED_UNMANNED );
if ( obj->getAI() )
obj->getAI()->aiIdle( CMD_FROM_AI );
TheGameLogic->deselectObject(obj, PLAYERMASK_ALL, TRUE);
// Clear any terrain decals here
Drawable* draw = obj->getDrawable();
if (draw)
draw->setTerrainDecal(TERRAIN_DECAL_NONE);
// Convert it to the neutral team so it renders gray giving visual representation that it is unmanned.
obj->setTeam( ThePlayerList->getNeutralPlayer()->getDefaultTeam() );
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void NeutronBlastBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void NeutronBlastBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void NeutronBlastBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,328 @@
/*
** 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: OverchargeBehavior.cpp ///////////////////////////////////////////////////////////////////
// Author: Colin Day, June 2002
// Desc: Objects with this behavior module will get the ability to produce more power
// for a short amount of time, during this "overcharge" state object health is
// slowly reduced
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/Radar.h"
#include "Common/Xfer.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/OverchargeBehavior.h"
#include "GameLogic/Module/PowerPlantUpdate.h"
#include "GameClient/InGameUI.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
OverchargeBehaviorModuleData::OverchargeBehaviorModuleData( void )
{
m_healthPercentToDrainPerSecond = 0.0f;
m_notAllowedWhenHealthBelowPercent = 0.0f;
} // end OverchargeBehaviorModuleData
//-------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void OverchargeBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
UpdateModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "HealthPercentToDrainPerSecond", INI::parsePercentToReal, NULL, offsetof( OverchargeBehaviorModuleData, m_healthPercentToDrainPerSecond ) },
{ "NotAllowedWhenHealthBelowPercent", INI::parsePercentToReal, NULL, offsetof( OverchargeBehaviorModuleData, m_notAllowedWhenHealthBelowPercent ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
OverchargeBehavior::OverchargeBehavior( Thing *thing, const ModuleData* moduleData )
: UpdateModule( thing, moduleData )
{
m_overchargeActive = FALSE;
// start off sleeping forever until we become active
setWakeFrame( getObject(), UPDATE_SLEEP_FOREVER );
} // end OverchargeBehavior
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
OverchargeBehavior::~OverchargeBehavior( void )
{
} // end ~OverchargeBehavior
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UpdateSleepTime OverchargeBehavior::update( void )
{
// if the overcharge is active we need to take away some life
if( m_overchargeActive )
{
Object *us = getObject();
// get mod data
const OverchargeBehaviorModuleData *modData = getOverchargeBehaviorModuleData();
// do some damage
BodyModuleInterface *body = us->getBodyModule();
DamageInfo damageInfo;
damageInfo.in.m_amount = (body->getMaxHealth() * modData->m_healthPercentToDrainPerSecond) / LOGICFRAMES_PER_SECOND;
damageInfo.in.m_sourceID = us->getID();
damageInfo.in.m_damageType = DAMAGE_PENALTY;
damageInfo.in.m_deathType = DEATH_NORMAL;
us->attemptDamage( &damageInfo );
// see if our health is below the allowable threshold
if( body->getHealth() < body->getMaxHealth() * modData->m_notAllowedWhenHealthBelowPercent )
{
// turn off the overcharge
enable( FALSE );
// do some UI info for the local user if this is theirs
if( ThePlayerList->getLocalPlayer() == us->getControllingPlayer() )
{
// print msg
TheInGameUI->message( "GUI:OverchargeExhausted" );
// do radar event
TheRadar->createEvent( us->getPosition(), RADAR_EVENT_INFORMATION );
} // end of
// do nothing else
return UPDATE_SLEEP_NONE;
} // end if
} // end if
return UPDATE_SLEEP_NONE;
} // end update
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void OverchargeBehavior::onDamage( DamageInfo *damageInfo )
{
} // end onDie
// ------------------------------------------------------------------------------------------------
/** Flip the state of our 'overcharge-ness' */
// ------------------------------------------------------------------------------------------------
void OverchargeBehavior::toggle( void )
{
// just toggle using enable()
enable( !m_overchargeActive );
} // end toggle
// ------------------------------------------------------------------------------------------------
/** Enable or disable an overcharge */
// ------------------------------------------------------------------------------------------------
void OverchargeBehavior::enable( Bool enable )
{
Object *us = getObject();
if( enable == FALSE )
{
// if we're turned on, turn off
if( m_overchargeActive == TRUE )
{
// make sure to NOT extend rods for purpose of maintaining proper model condition
PowerPlantUpdateInterface *ppui;
for( BehaviorModule **umi = getObject()->getBehaviorModules(); *umi; ++umi)
{
ppui = (*umi)->getPowerPlantUpdateInterface();
if( ppui )
ppui->extendRods(FALSE);
}
Player *player = us->getControllingPlayer();
if ( player )
player->removePowerBonus(us);
// we are no longer active
m_overchargeActive = FALSE;
// sleep forever
setWakeFrame( us, UPDATE_SLEEP_FOREVER );
} // end if
} // end if
else
{
// if we're turned off, turn on
if( m_overchargeActive == FALSE )
{
// make sure to extend rods for purpose of maintaining proper model condition
PowerPlantUpdateInterface *ppui;
for( BehaviorModule **umi = getObject()->getBehaviorModules(); *umi; ++umi)
{
ppui = (*umi)->getPowerPlantUpdateInterface();
if( ppui )
ppui->extendRods(TRUE);
}
// add the power bonus
Player *player = us->getControllingPlayer();
if ( player )
player->addPowerBonus(us);
// we are now active
m_overchargeActive = TRUE;
// need to update every frame now
setWakeFrame( us, UPDATE_SLEEP_NONE );
} // end if
} // end else
} // end enable
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void OverchargeBehavior::onDelete( void )
{
// if we haven't been upgraded there is nothing to clean up
if( m_overchargeActive == FALSE )
return;
// remove the power bonus from the player
Player *player = getObject()->getControllingPlayer();
if( player )
player->removePowerBonus( getObject() );
m_overchargeActive = FALSE;
} // end onDelete
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void OverchargeBehavior::onCapture( Player *oldOwner, Player *newOwner )
{
// do nothing if we haven't upgraded yet
if( m_overchargeActive == FALSE )
return;
if (getObject()->isDisabled())
return;
// remove power bonus from old owner
if( oldOwner )
oldOwner->removePowerBonus( getObject() );
// add power bonus to the new owner
if( newOwner )
newOwner->addPowerBonus( getObject() );
} // end onCapture
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void OverchargeBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void OverchargeBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// overcharge active
xfer->xferBool( &m_overchargeActive );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void OverchargeBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
// Our effect is a fire and forget effect, not an upgrade state that is itself saved, so need to re-fire.
if( m_overchargeActive && getObject()->getControllingPlayer() )
getObject()->getControllingPlayer()->addPowerBonus( getObject() );
} // end loadPostProcess

View File

@@ -0,0 +1,157 @@
/*
** 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: POWTruckBehavior.cpp /////////////////////////////////////////////////////////////////////
// Author: Colin Day
// Desc: POW Truck
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/POWTruckAIUpdate.h"
#include "GameLogic/Module/POWTruckBehavior.h"
#ifdef ALLOW_SURRENDER
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
POWTruckBehaviorModuleData::POWTruckBehaviorModuleData( void )
{
} // end POWTruckBehaviorModuleData
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void POWTruckBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
OpenContainModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
POWTruckBehavior::POWTruckBehavior( Thing *thing, const ModuleData *moduleData )
: OpenContain( thing, moduleData )
{
} // end POWTruckBehavior
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
POWTruckBehavior::~POWTruckBehavior( void )
{
} // end ~POWTruckBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void POWTruckBehavior::onCollide( Object *other, const Coord3D *loc, const Coord3D *normal )
{
Object *us = getObject();
// sanity
if( other == NULL )
return;
// if other isn't slated to be picked up by us, ignore
AIUpdateInterface *otherAi = other->getAIUpdateInterface();
if( otherAi == NULL || otherAi->isSurrendered() == FALSE )
return;
// get our AI info
AIUpdateInterface *ourAI = us->getAIUpdateInterface();
DEBUG_ASSERTCRASH( ourAI, ("POWTruckBehavior::onCollide - '%s' has no AI\n",
us->getTemplate()->getName().str()) );
POWTruckAIUpdateInterface *powTruckAI = ourAI->getPOWTruckAIUpdateInterface();
DEBUG_ASSERTCRASH( powTruckAI, ("POWTruckBehavior::onCollide - '%s' has no POWTruckAI\n",
us->getTemplate()->getName().str()) );
// pick up the prisoner
powTruckAI->loadPrisoner( other );
} // end onCollide
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void POWTruckBehavior::crc( Xfer *xfer )
{
// extend base class
OpenContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void POWTruckBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
OpenContain::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void POWTruckBehavior::loadPostProcess( void )
{
// extend base class
OpenContain::loadPostProcess();
} // end loadPostProcess
#endif

View File

@@ -0,0 +1,249 @@
/*
** 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: PoisonedBehavior.cpp /////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, July 2002
// Desc: Behavior that reacts to poison Damage by continuously damaging us further in an Update
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/Module/PoisonedBehavior.h"
#include "GameLogic/Damage.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// tinting is all handled in drawable, now, Graham look near the bottom of Drawable::UpdateDrawable()
//static const RGBColor poisonedTint = {0.0f, 1.0f, 0.0f};
//-------------------------------------------------------------------------------------------------
PoisonedBehaviorModuleData::PoisonedBehaviorModuleData()
{
m_poisonDamageIntervalData = 0; // How often I retake poison damage dealt me
m_poisonDurationData = 0; // And how long after the last poison dose I am poisoned
}
//-------------------------------------------------------------------------------------------------
/*static*/ void PoisonedBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p)
{
static const FieldParse dataFieldParse[] =
{
{ "PoisonDamageInterval", INI::parseDurationUnsignedInt, NULL, offsetof(PoisonedBehaviorModuleData, m_poisonDamageIntervalData) },
{ "PoisonDuration", INI::parseDurationUnsignedInt, NULL, offsetof(PoisonedBehaviorModuleData, m_poisonDurationData) },
{ 0, 0, 0, 0 }
};
UpdateModuleData::buildFieldParse(p);
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
PoisonedBehavior::PoisonedBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
{
m_poisonDamageFrame = 0;
m_poisonOverallStopFrame = 0;
m_poisonDamageAmount = 0.0f;
m_deathType = DEATH_POISONED;
setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
PoisonedBehavior::~PoisonedBehavior( void )
{
}
//-------------------------------------------------------------------------------------------------
/** Damage has been dealt, this is an opportunity to react to that damage */
//-------------------------------------------------------------------------------------------------
void PoisonedBehavior::onDamage( DamageInfo *damageInfo )
{
if( damageInfo->in.m_damageType == DAMAGE_POISON )
startPoisonedEffects( damageInfo );
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void PoisonedBehavior::onHealing( DamageInfo *damageInfo )
{
stopPoisonedEffects();
setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER);
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime PoisonedBehavior::update()
{
const PoisonedBehaviorModuleData* d = getPoisonedBehaviorModuleData();
UnsignedInt now = TheGameLogic->getFrame();
if( m_poisonOverallStopFrame == 0 )
{
DEBUG_CRASH(("hmm, this should not happen"));
return UPDATE_SLEEP_FOREVER;
//we aren't poisoned, so nevermind
}
if (m_poisonDamageFrame != 0 && now >= m_poisonDamageFrame)
{
// If it is time to do damage, then do it and reset the damage timer
DamageInfo damage;
damage.in.m_amount = m_poisonDamageAmount;
damage.in.m_sourceID = INVALID_ID;
damage.in.m_damageType = DAMAGE_UNRESISTABLE; // Not poison, as that will infect us again
damage.in.m_damageFXOverride = DAMAGE_POISON; // but this will ensure that the right effect is played
damage.in.m_deathType = m_deathType;
getObject()->attemptDamage( &damage );
m_poisonDamageFrame = now + d->m_poisonDamageIntervalData;
}
// If we are now at zero we need to turn off our special effects...
// unless the poison killed us, then we continue to be a pulsating toxic pus ball
if( m_poisonOverallStopFrame != 0 &&
now >= m_poisonOverallStopFrame &&
!getObject()->isEffectivelyDead())
{
stopPoisonedEffects();
}
return calcSleepTime();
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime PoisonedBehavior::calcSleepTime()
{
// UPDATE_SLEEP requires a count-of-frames, not an absolute-frame, so subtract 'now'
UnsignedInt now = TheGameLogic->getFrame();
if (m_poisonOverallStopFrame == 0 || m_poisonOverallStopFrame == now)
return UPDATE_SLEEP_FOREVER;
return frameToSleepTime(m_poisonDamageFrame, m_poisonOverallStopFrame);
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void PoisonedBehavior::startPoisonedEffects( const DamageInfo *damageInfo )
{
const PoisonedBehaviorModuleData* d = getPoisonedBehaviorModuleData();
UnsignedInt now = TheGameLogic->getFrame();
// We are going to take the damage dealt by the original poisoner every so often for a while.
m_poisonDamageAmount = damageInfo->out.m_actualDamageDealt;
m_poisonOverallStopFrame = now + d->m_poisonDurationData;
// If we are getting re-poisoned, don't reset the damage counter if running, but do set it if unset
if( m_poisonDamageFrame != 0 )
m_poisonDamageFrame = min( m_poisonDamageFrame, now + d->m_poisonDamageIntervalData );
else
m_poisonDamageFrame = now + d->m_poisonDamageIntervalData;
m_deathType = damageInfo->in.m_deathType;
Drawable *myDrawable = getObject()->getDrawable();
if( myDrawable )
myDrawable->setTintStatus( TINT_STATUS_POISONED );// Graham, It has changed, see UpdateDrawable()
setWakeFrame(getObject(), calcSleepTime());
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void PoisonedBehavior::stopPoisonedEffects()
{
m_poisonDamageFrame = 0;
m_poisonOverallStopFrame = 0;
m_poisonDamageAmount = 0.0f;
Drawable *myDrawable = getObject()->getDrawable();
if( myDrawable )
myDrawable->clearTintStatus( TINT_STATUS_POISONED );// Graham, It has changed, see UpdateDrawable()
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void PoisonedBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void PoisonedBehavior::xfer( Xfer *xfer )
{
// version
const XferVersion currentVersion = 2;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// poisoned damage frame
xfer->xferUnsignedInt( &m_poisonDamageFrame );
// poison overall stop frame
xfer->xferUnsignedInt( &m_poisonOverallStopFrame );
// poison damage amount
xfer->xferReal( &m_poisonDamageAmount );
if (version >= 2)
{
xfer->xferUser(&m_deathType, sizeof(m_deathType));
}
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void PoisonedBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,478 @@
/*
** 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: PrisonBehavior.cpp ///////////////////////////////////////////////////////////////////////
// Author: Colin Day, August 2002
// Desc: Prison Behavior
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameState.h"
#include "Common/Player.h"
#include "Common/RandomValue.h"
#include "Common/ThingFactory.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/InGameUI.h"
#include "GameClient/GameClient.h"
#include "GameClient/Line2D.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/PrisonBehavior.h"
#ifdef ALLOW_SURRENDER
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
class PrisonVisual : public MemoryPoolObject
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( PrisonVisual, "PrisonVisual" )
public:
PrisonVisual( void );
// virtual destructor prototype provied by memory pool object
ObjectID m_objectID; ///< object that is contained
DrawableID m_drawableID; ///< associated visual prisoner drawable
PrisonVisual *m_next; ///< next
};
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PrisonVisual::PrisonVisual( void )
{
m_objectID = INVALID_ID;
m_drawableID = INVALID_DRAWABLE_ID;
m_next = NULL;
} // end PrisonVisual
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PrisonVisual::~PrisonVisual( void )
{
} // end ~PrisonVisual
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PrisonBehaviorModuleData::PrisonBehaviorModuleData( void )
{
m_showPrisoners = FALSE;
} // end PrisonBehaviorModuleData
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void PrisonBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
OpenContainModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "ShowPrisoners", INI::parseBool, NULL, offsetof( PrisonBehaviorModuleData, m_showPrisoners ) },
{ "YardBonePrefix", INI::parseAsciiString, NULL, offsetof( PrisonBehaviorModuleData, m_prisonYardBonePrefix ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PrisonBehavior::PrisonBehavior( Thing *thing, const ModuleData *moduleData )
: OpenContain( thing, moduleData )
{
m_visualList = NULL;
} // end PrisonBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PrisonBehavior::~PrisonBehavior( void )
{
} // end ~PrisonBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void PrisonBehavior::onDelete( void )
{
// extend functionality
OpenContain::onDelete();
// delete our list
Drawable *draw;
PrisonVisual *visual;
while( m_visualList )
{
// delete drawable if found
draw = TheGameClient->findDrawableByID( m_visualList->m_drawableID );
if( draw )
TheGameClient->destroyDrawable( draw );
// delete element and set next to head
visual = m_visualList->m_next;
m_visualList->deleteInstance();
m_visualList = visual;
} // end while
} // end onDelete
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void PrisonBehavior::onContaining( Object *obj, Bool wasSelected )
{
// extend functionality
OpenContain::onContaining( obj, wasSelected );
// objects inside a prison are held
obj->setDisabled( DISABLED_HELD );
// if we show visuals, make one
const PrisonBehaviorModuleData *modData = getPrisonBehaviorModuleData();
if( modData->m_showPrisoners )
addVisual( obj );
} // end onContaining
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void PrisonBehavior::onRemoving( Object *obj )
{
// if we show visuals, remove one
const PrisonBehaviorModuleData *modData = getPrisonBehaviorModuleData();
if( modData->m_showPrisoners )
removeVisual( obj );
// object is no longer held inside a garrisoned building
obj->clearDisabled( DISABLED_HELD );
// extend functionality
OpenContain::onRemoving( obj );
} // end onRemoving
///////////////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE ////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
/** Pick a random location inside the prison yard */
// ------------------------------------------------------------------------------------------------
void PrisonBehavior::pickVisualLocation( Coord3D *pos )
{
Object *us = getObject();
const PrisonBehaviorModuleData *modData = getPrisonBehaviorModuleData();
Int i;
// sanity
if( pos == NULL )
return;
// initialize the picked location to that of the prison center
Coord3D pickedLocation = *us->getPosition();
// get the positions of the bones that make up the prison yard area
const Int MAX_YARD_BONES = 16;
Coord3D yardPositions[ MAX_YARD_BONES ];
Int yardBones = us->getMultiLogicalBonePosition( modData->m_prisonYardBonePrefix.str(),
MAX_YARD_BONES,
yardPositions,
NULL );
//
// we must have at least 3 bone locations to make a yard polygon, otherwise we'll
// default to the object position
//
if( yardBones >= 3 )
{
// find the bounding region of the yard area
Region2D yardRegion;
yardRegion.lo.x = yardPositions[ 0 ].x;
yardRegion.lo.y = yardPositions[ 0 ].y;
yardRegion.hi.x = yardPositions[ 0 ].x;
yardRegion.hi.y = yardPositions[ 0 ].y;
for( i = 1; i < yardBones; i++ )
{
if( yardPositions[ i ].x < yardRegion.lo.x )
yardRegion.lo.x = yardPositions[ i ].x;
if( yardPositions[ i ].y < yardRegion.lo.y )
yardRegion.lo.y = yardPositions[ i ].y;
if( yardPositions[ i ].x > yardRegion.hi.x )
yardRegion.hi.x = yardPositions[ i ].x;
if( yardPositions[ i ].y > yardRegion.hi.y )
yardRegion.hi.y = yardPositions[ i ].y;
} // end for i
//
// now that we have a yard region, the default visual position will be in the middle
// of the yard region instead of the position of our object
//
pickedLocation.x = yardRegion.lo.x + yardRegion.width() / 2.0f;
pickedLocation.y = yardRegion.lo.y + yardRegion.height() / 2.0f;
// NOTE: pickedLocation.z is left alone at the object center Z
// loop till we find a valid location that is inside the yard area
Int maxTries = 32;
Coord3D loc;
for( i = 0; i < maxTries; ++i )
{
// pick a location
loc.x = GameLogicRandomValueReal( yardRegion.lo.x, yardRegion.hi.x );
loc.y = GameLogicRandomValueReal( yardRegion.lo.y, yardRegion.hi.y );
loc.z = pickedLocation.z;
// must be inside the yard polygon
if( PointInsideArea2D( &loc, yardPositions, yardBones ) == TRUE )
{
// use this location, leave Z alone as the center of the prison
pickedLocation = loc;
break; // exit for i
} // end if
} // end for i
} // end if
// return the location picked
*pos = pickedLocation;
} // end pickVisualLocation
// ------------------------------------------------------------------------------------------------
/** Add prisoner visual to the prison yard */
// ------------------------------------------------------------------------------------------------
void PrisonBehavior::addVisual( Object *obj )
{
// sanity
if( obj == NULL )
return;
// create a drawable
Drawable *draw = TheThingFactory->newDrawable( obj->getTemplate() );
// set the color of the drawable to that of the object
if (TheGlobalData->m_timeOfDay == TIME_OF_DAY_NIGHT)
draw->setIndicatorColor( obj->getNightIndicatorColor() );
else
draw->setIndicatorColor( obj->getIndicatorColor() );
// pick a location insid the prison yard
Coord3D pos;
pickVisualLocation( &pos );
// place drawable withing the prison yard area
draw->setPosition( &pos );
draw->setOrientation( GameLogicRandomValueReal( 0, TWO_PI ) );
DrawableInfo *drawInfo=draw->getDrawableInfo();
drawInfo->m_shroudStatusObjectID=getObject()->getID();
// record this object/drawable pair
PrisonVisual *visual = newInstance(PrisonVisual);
visual->m_objectID = obj->getID();
visual->m_drawableID = draw->getID();
visual->m_next = m_visualList;
m_visualList = visual;
} // end addVisual
// ------------------------------------------------------------------------------------------------
/** Remove prisoner visual from the prison yard */
// ------------------------------------------------------------------------------------------------
void PrisonBehavior::removeVisual( Object *obj )
{
// sanity
if( obj == NULL )
return;
// initialize a drawable ID to invalid
DrawableID drawableID = INVALID_DRAWABLE_ID;
// find visual info in our list, once found, take this opportunity to remove it from that list
PrisonVisual *visual, *prevVisual = NULL;
for( visual = m_visualList; visual; visual = visual->m_next )
{
// is this the one we're looking for
if( visual->m_objectID == obj->getID() )
{
// record the information we need here
drawableID = visual->m_drawableID;
// remove from list
if( prevVisual )
prevVisual->m_next = visual->m_next;
else
m_visualList = visual->m_next;
// delete the element
visual->deleteInstance();
break; // exit for
} // end if
// keep a pointer to the previous element
prevVisual = visual;
} // end for
// find the drawable visual and destroy it
Drawable *draw = TheGameClient->findDrawableByID( drawableID );
if( draw )
TheGameClient->destroyDrawable( draw );
} // end removeVisual
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void PrisonBehavior::crc( Xfer *xfer )
{
// extend base class
OpenContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void PrisonBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
OpenContain::xfer( xfer );
// count and data for the prison visuals
UnsignedShort visualCount = 0;
PrisonVisual *visual;
for( visual = m_visualList; visual; visual = visual->m_next )
visualCount++;
xfer->xferUnsignedShort( &visualCount );
if( xfer->getXferMode() == XFER_SAVE )
{
// write all data
for( visual = m_visualList; visual; visual = visual->m_next )
{
// object id
xfer->xferObjectID( &visual->m_objectID );
// drawable id
xfer->xferDrawableID( &visual->m_drawableID );
} // end for, visual
} // end if, save
else
{
// the visual list should be empty
if( m_visualList != NULL )
{
DEBUG_CRASH(( "PrisonBehavior::xfer - the visual list should be empty but is not\n" ));
throw SC_INVALID_DATA;
} // end if
// read each item
for( UnsignedShort i = 0; i < visualCount; ++i )
{
// allocate a new visual and tie to list
visual = newInstance(PrisonVisual);
visual->m_next = m_visualList;
m_visualList = visual;
// read object id
xfer->xferObjectID( &visual->m_objectID );
// read drawable id
xfer->xferDrawableID( &visual->m_drawableID );
} // end for, i
} // end else, load
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void PrisonBehavior::loadPostProcess( void )
{
// extend base class
OpenContain::loadPostProcess();
} // end loadPostProcess
#endif

View File

@@ -0,0 +1,282 @@
/*
** 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: PropagandaCenterBehavior.cpp /////////////////////////////////////////////////////////////
// Author: Colin Day, August 2002
// Desc: Propaganda Center Behavior
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/PropagandaCenterBehavior.h"
#ifdef ALLOW_SURRENDER
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PropagandaCenterBehaviorModuleData::PropagandaCenterBehaviorModuleData( void )
{
m_brainwashDuration = 0;
} // end PropagandaCenterBehaviorModuleData
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void PropagandaCenterBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
PrisonBehaviorModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "BrainwashDuration", INI::parseDurationUnsignedInt, NULL, offsetof( PropagandaCenterBehaviorModuleData, m_brainwashDuration ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PropagandaCenterBehavior::PropagandaCenterBehavior( Thing *thing, const ModuleData *moduleData )
: PrisonBehavior( thing, moduleData )
{
m_brainwashingSubjectID = INVALID_ID;
m_brainwashingSubjectStartFrame = 0;
} // end PropagandaCenterBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PropagandaCenterBehavior::~PropagandaCenterBehavior( void )
{
} // end ~PropagandaCenterBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void PropagandaCenterBehavior::onDelete( void )
{
// extend functionality
PrisonBehavior::onDelete();
//
// go through our list of brainwashed objects, and if they are still under our
// control, return them to their original owners
//
for( BrainwashedIDListContIterator it = m_brainwashedList.begin();
it != m_brainwashedList.end();
++it )
{
Object *obj;
// get this object
obj = TheGameLogic->findObjectByID( *it );
if( obj )
{
// return this object under the control of the original owner
obj->restoreOriginalTeam();
} // end if
} // end for
// clear the list
m_brainwashedList.clear();
} // end onDelete
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime PropagandaCenterBehavior::update( void )
{
Object *us = getObject();
const PropagandaCenterBehaviorModuleData *modData = getPropagandaCenterBehaviorModuleData();
// extend functionality
PrisonBehavior::update();
// if we have a prisoner inside, continue the brainwashing on them (one at a time)
if( m_brainwashingSubjectID != INVALID_ID )
{
Object *brainwashingSubject = TheGameLogic->findObjectByID( m_brainwashingSubjectID );
if( brainwashingSubject )
{
// if we've been in here long enough, we come out brainwashed
if( TheGameLogic->getFrame() - m_brainwashingSubjectStartFrame >= modData->m_brainwashDuration )
{
// only can exit if the prison allows us to
ExitDoorType exitDoor = reserveDoorForExit(brainwashingSubject->getTemplate(), brainwashingSubject);
if(exitDoor != DOOR_NONE_AVAILABLE)
{
// place this object under the control of the player
Player *player = us->getControllingPlayer();
DEBUG_ASSERTCRASH( player, ("Brainwashing: No controlling player for '%s'\n", us->getTemplate()->getName().str()) );
if( player )
brainwashingSubject->setTemporaryTeam( player->getDefaultTeam() );
// remove any surrender status from this object
AIUpdateInterface *ai = brainwashingSubject->getAIUpdateInterface();
if( ai )
ai->setSurrendered( NULL, FALSE );
// add this object to our brainwashed list if we're not already in it
for( BrainwashedIDListIterator it = m_brainwashedList.begin();
it != m_brainwashedList.end(); ++it )
if( *it == brainwashingSubject->getID() )
break; // exit for
if( it == m_brainwashedList.end() )
m_brainwashedList.push_front( brainwashingSubject->getID() );
// exit the prison
exitObjectViaDoor( brainwashingSubject, exitDoor );
} // end if
} // end if
} // end if,
} // end if
// if we have no brainwashing subject, hook one up if we have people inside us
if( m_brainwashingSubjectID == INVALID_ID )
{
// find the first object in our containment list
if( getContainList().begin() != getContainList().end() )
{
Object *obj = getContainList().front();
if( obj )
{
// assign brainwashing subject on this frame
m_brainwashingSubjectID = obj->getID();
m_brainwashingSubjectStartFrame = TheGameLogic->getFrame();
} // end if
} // end if
} // end if
return UPDATE_SLEEP_NONE;
} // end update
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void PropagandaCenterBehavior::onRemoving( Object *obj )
{
// if we're removing the brainwashing subject, NULL the pointer
if( m_brainwashingSubjectID == obj->getID() )
{
m_brainwashingSubjectID = INVALID_ID;
m_brainwashingSubjectStartFrame = 0;
} // end if
// extend functionality
PrisonBehavior::onRemoving( obj );
} // end onRemoving
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void PropagandaCenterBehavior::crc( Xfer *xfer )
{
// extend base class
PrisonBehavior::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void PropagandaCenterBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
PrisonBehavior::xfer( xfer );
// brainwashing subject
xfer->xferObjectID( &m_brainwashingSubjectID );
// brainwashing subject start frame
xfer->xferUnsignedInt( &m_brainwashingSubjectStartFrame );
// brainwashed list size and data
xfer->xferSTLObjectIDList( &m_brainwashedList );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void PropagandaCenterBehavior::loadPostProcess( void )
{
// extend base class
PrisonBehavior::loadPostProcess();
} // end loadPostProcess
#endif

View File

@@ -0,0 +1,649 @@
/*
** 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: PropagandaTowerBehavior.cpp //////////////////////////////////////////////////////////////
// Author: Colin Day, August 2002
// Desc: Behavior module for PropagandaTower
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/GameState.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/Upgrade.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/Weapon.h"
#include "GameLogic/Module/PropagandaTowerBehavior.h"
#include "GameLogic/Module/BodyModule.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// FORWARD REFERENCES /////////////////////////////////////////////////////////////////////////////
enum ObjectID;
// ------------------------------------------------------------------------------------------------
/** This class is used to track objects as they exit our area of influence */
// ------------------------------------------------------------------------------------------------
class ObjectTracker : public MemoryPoolObject
{
MEMORY_POOL_GLUE_WITH_USERLOOKUP_CREATE( ObjectTracker, "ObjectTracker" );
public:
ObjectTracker( void ) { objectID = INVALID_ID; next = NULL; }
ObjectID objectID;
ObjectTracker *next;
};
ObjectTracker::~ObjectTracker( void ) { }
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PropagandaTowerBehaviorModuleData::PropagandaTowerBehaviorModuleData( void )
{
m_scanRadius = 1.0f;
m_scanDelayInFrames = 100;
m_autoHealPercentPerSecond = 0.01f;
m_upgradedAutoHealPercentPerSecond = 0.02f;
m_pulseFX = NULL;
m_upgradeRequired = NULL;
m_upgradedPulseFX = NULL;
m_affectsSelf = FALSE;
} // end PropagandaTowerBehaviorModuleData
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void PropagandaTowerBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
UpdateModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "Radius", INI::parseReal, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_scanRadius ) },
{ "DelayBetweenUpdates", INI::parseDurationUnsignedInt, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_scanDelayInFrames ) },
{ "HealPercentEachSecond", INI::parsePercentToReal, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_autoHealPercentPerSecond ) },
{ "UpgradedHealPercentEachSecond", INI::parsePercentToReal,NULL, offsetof( PropagandaTowerBehaviorModuleData, m_upgradedAutoHealPercentPerSecond ) },
{ "PulseFX", INI::parseFXList, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_pulseFX ) },
{ "UpgradeRequired", INI::parseAsciiString, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_upgradeRequired ) },
{ "UpgradedPulseFX", INI::parseFXList, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_upgradedPulseFX ) },
{ "AffectsSelf", INI::parseBool, NULL, offsetof( PropagandaTowerBehaviorModuleData, m_affectsSelf ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PropagandaTowerBehavior::PropagandaTowerBehavior( Thing *thing, const ModuleData *modData )
: UpdateModule( thing, modData )
{
//Added By Sadullah Nader
//Initializations inserted
m_lastScanFrame = 0;
//
m_insideList = NULL;
setWakeFrame( getObject(), UPDATE_SLEEP_NONE );
} // end PropagandaTowerBehavior
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
PropagandaTowerBehavior::~PropagandaTowerBehavior( void )
{
} // end ~PropagandaTowerBehavior
// ------------------------------------------------------------------------------------------------
/** Module is being deleted */
// ------------------------------------------------------------------------------------------------
void PropagandaTowerBehavior::onDelete( void )
{
// remove any benefits from anybody in our area of influence
removeAllInfluence();
} // end onDelete
// ------------------------------------------------------------------------------------------------
/** Resolve */
// ------------------------------------------------------------------------------------------------
void PropagandaTowerBehavior::onObjectCreated( void )
{
const PropagandaTowerBehaviorModuleData *modData = getPropagandaTowerBehaviorModuleData();
// convert module upgrade name to a pointer
m_upgradeRequired = TheUpgradeCenter->findUpgrade( modData->m_upgradeRequired );
} // end onObjectCreated
// ------------------------------------------------------------------------------------------------
void PropagandaTowerBehavior::onCapture( Player *oldOwner, Player *newOwner )
{
// We don't function for the neutral player.
if( newOwner == ThePlayerList->getNeutralPlayer() )
{
removeAllInfluence();
setWakeFrame( getObject(), UPDATE_SLEEP_FOREVER );
}
else
setWakeFrame( getObject(), UPDATE_SLEEP_NONE );
}
// ------------------------------------------------------------------------------------------------
/** The update callback */
// ------------------------------------------------------------------------------------------------
UpdateSleepTime PropagandaTowerBehavior::update( void )
{
/// @todo srj use SLEEPY_UPDATE here
const PropagandaTowerBehaviorModuleData *modData = getPropagandaTowerBehaviorModuleData();
//Sep 27, 2002 (Kris): Added this code to prevent the tower from working while under construction.
Object *self = getObject();
if( self->getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) )
return UPDATE_SLEEP_NONE;
if( self->testStatus(OBJECT_STATUS_SOLD) )
{
removeAllInfluence();
return UPDATE_SLEEP_FOREVER;
}
if( self->isEffectivelyDead() )
return UPDATE_SLEEP_FOREVER;
if( self->isDisabled() )
{
// We need to let go of everyone if we are EMPd or underpowered or yadda, but not if we are only held
DisabledMaskType allButHeld = MAKE_DISABLED_MASK( DISABLED_HELD );
FLIP_DISABLEDMASK(allButHeld);
if( TEST_DISABLEDMASK_ANY(self->getDisabledFlags(), allButHeld) )
{
removeAllInfluence();
return UPDATE_SLEEP_NONE;
}
}
if( self->getContainedBy() && self->getContainedBy()->getContainedBy() )
{
// If our container is contained, we turn the heck off. Seems like a weird specific check, but all of
// attacking is guarded by the same check in isPassengersAllowedToFire. We similarly work in a container,
// but not in a double container.
removeAllInfluence();
return UPDATE_SLEEP_NONE;
}
// if it's not time to scan, nothing to do
UnsignedInt currentFrame = TheGameLogic->getFrame();
if( currentFrame - m_lastScanFrame >= modData->m_scanDelayInFrames )
{
// do a scan
doScan();
m_lastScanFrame = currentFrame;
} // end if
// go through any objects in our area of influence and do the effect logic on them
Object *obj;
ObjectTracker *curr = NULL, *prev = NULL, *next = NULL;
for( curr = m_insideList; curr; curr = next )
{
// get the next link
next = curr->next;
// find this object
obj = TheGameLogic->findObjectByID( curr->objectID );
if ((obj) &&
(obj->isKindOf(KINDOF_SCORE) || obj->isKindOf(KINDOF_SCORE_CREATE) || obj->isKindOf(KINDOF_SCORE_DESTROY) || obj->isKindOf(KINDOF_MP_COUNT_FOR_VICTORY)))
{
// give any bonus to this object
effectLogic( obj, TRUE, getPropagandaTowerBehaviorModuleData() );
// record this element as the previous one found in the list
prev = curr;
} // end if
else
{
//
// actual object wasn't found, remove this entry from our inside list so we don't
// have to search through it again
//
if( prev )
prev->next = curr->next;
else
m_insideList = curr->next;
curr->deleteInstance();
} // end else
} // end for, curr
return UPDATE_SLEEP_NONE;
} // end update
// ------------------------------------------------------------------------------------------------
/** The death callback */
// ------------------------------------------------------------------------------------------------
void PropagandaTowerBehavior::onDie( const DamageInfo *damageInfo )
{
// remove any benefits from anybody in our area of influence
removeAllInfluence();
} // end onDie
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
/** Grant or remove effect to this object */
// ------------------------------------------------------------------------------------------------
void PropagandaTowerBehavior::effectLogic( Object *obj, Bool giving,
const PropagandaTowerBehaviorModuleData *modData )
{
Bool effectUpgraded = getObject()->getControllingPlayer()->hasUpgradeComplete( m_upgradeRequired );
// if giving the effect
if( giving )
{
if ( obj->hasAnyDamageWeapon() == TRUE )
{
if( obj->testWeaponBonusCondition( WEAPONBONUSCONDITION_ENTHUSIASTIC ) == FALSE )
obj->setWeaponBonusCondition( WEAPONBONUSCONDITION_ENTHUSIASTIC );
if (effectUpgraded)
{
if (obj->testWeaponBonusCondition( WEAPONBONUSCONDITION_SUBLIMINAL ) == FALSE)
obj->setWeaponBonusCondition( WEAPONBONUSCONDITION_SUBLIMINAL );
}
} // hasdamageweapon
// grant health to this object as well
BodyModuleInterface *body = obj->getBodyModule();
if( body )
{
Real healthPercent;
if(effectUpgraded)
healthPercent = modData->m_upgradedAutoHealPercentPerSecond;
else
healthPercent = modData->m_autoHealPercentPerSecond;
Real amount = healthPercent / LOGICFRAMES_PER_SECOND * body->getMaxHealth();
// Dustin wants the healing effect not to stack from multiple propaganda towers...
// To accomplish this, I'll give every object a single healing-sender (ID)
// Any given healing recipient (object) can only receive healing from one particular healing sender
// and cannot change healing senders until the previous one expires (its scandelay)
// obj->attemptHealing(amount, getObject()); // the regular way to give healing...
obj->attemptHealingFromSoleBenefactor( amount, getObject(), modData->m_scanDelayInFrames );//the non-stacking way
} // end if
} // end if
else
{
// taking effect away
obj->clearWeaponBonusCondition( WEAPONBONUSCONDITION_ENTHUSIASTIC );
obj->clearWeaponBonusCondition( WEAPONBONUSCONDITION_SUBLIMINAL );
} // end else
} // end effectLogic
// ------------------------------------------------------------------------------------------------
/** Remove all influence from objects we've given bonuses to */
// ------------------------------------------------------------------------------------------------
void PropagandaTowerBehavior::removeAllInfluence( void )
{
ObjectTracker *o;
// go through all objects we've given bonuses to and remove them
Object *obj;
for( o = m_insideList; o; o = o->next )
{
obj = TheGameLogic->findObjectByID( o->objectID );
if( obj )
effectLogic( obj, FALSE, getPropagandaTowerBehaviorModuleData() );
} // end for
// delete the list of objects under our influence
while( m_insideList )
{
o = m_insideList->next;
m_insideList->deleteInstance();
m_insideList = o;
} // end while
} // end removeAllInfluence
// ------------------------------------------------------------------------------------------------
/** Do a scan */
// ------------------------------------------------------------------------------------------------
void PropagandaTowerBehavior::doScan( void )
{
const PropagandaTowerBehaviorModuleData *modData = getPropagandaTowerBehaviorModuleData();
Object *us = getObject();
ObjectTracker *newInsideList = NULL;
// The act of scanning is when we play our effect
Bool upgradePresent = FALSE;
if( m_upgradeRequired )
{
// see if we have the upgrade
switch( m_upgradeRequired->getUpgradeType() )
{
// ------------------------------------------------------------------------------------------
case UPGRADE_TYPE_PLAYER:
{
Player *player = us->getControllingPlayer();
upgradePresent = player->hasUpgradeComplete( m_upgradeRequired );
break;
} // end player upgrade
// ------------------------------------------------------------------------------------------
case UPGRADE_TYPE_OBJECT:
{
upgradePresent = us->hasUpgrade( m_upgradeRequired );
break;
} // end object upgrade
// ------------------------------------------------------------------------------------------
default:
{
DEBUG_CRASH(( "PropagandaTowerBehavior::doScan - Unknown upgrade type '%d'\n",
m_upgradeRequired->getUpgradeType() ));
break;
} // end default
} // end switch
} // end if
Bool doFX = TRUE;
// if it is us, or if it is he and we do see him
Object *overlord = us->getContainedBy();
if ( overlord )
{
if ( us->getControllingPlayer() != ThePlayerList->getLocalPlayer() )// daling with someone else's tower
{
if ( overlord->testStatus( OBJECT_STATUS_STEALTHED ) && !overlord->testStatus( OBJECT_STATUS_DETECTED ) )
doFX = FALSE;// so they don't give up their position
}
// Sorry, this is a lot of hard coded mishmash, but it must be done... ship it!
// Propaganda towers (proper) never get contained, so they don't care about this code
// The PropTowers that ride on the backs of overlord tanks want to suppress their fx if the overlord is stealthed to the local player
// The PropTowers that ride on the bellies of Helixes also suppress their fx when hiding stealthed (never happens?)
// The PropTowers must do nothing, scanning-or-FX-wise when they are inside something... there are two cases
// 1) When the prop tower is on an Overlord that is in a helicopter (likely a Helix II)
// 2) When the prop tower is the EmperorTank, which is in a helicopter (likely a Helix II).
if ( us->isKindOf( KINDOF_VEHICLE ) && !us->isKindOf( KINDOF_PORTABLE_STRUCTURE ) )//craptacular!
// oh my dear Lord... I am not a propaganda tower, I am an emperorTank! that means the overlord is a helix or something!
return; // proptowers that are riding IN things cannot do their scan!
//okay this is wacky, but this proptower may be on an overlord thank that is riding in a helix... oy!
Object *helix = overlord->getContainedBy();
if ( helix )
return; // proptowers that are riding ON things that are in-turn riding IN things cannot do their scan!
}
if ( us->getControllingPlayer() != ThePlayerList->getLocalPlayer() )// daling with someone else's tower
{
if ( us->testStatus( OBJECT_STATUS_STEALTHED ) && !us->testStatus( OBJECT_STATUS_DETECTED ) )
{
doFX = FALSE;// Certainly don't play if we ourselves are stelthed.
}
}
if ( doFX )
{
// play the right pulse
if( upgradePresent == TRUE )
FXList::doFXObj( modData->m_upgradedPulseFX, us );
else
FXList::doFXObj( modData->m_pulseFX, us );
}
// setup scan filters
PartitionFilterRelationship relationship( us, PartitionFilterRelationship::ALLOW_ALLIES );
PartitionFilterAlive filterAlive;
PartitionFilterSameMapStatus filterMapStatus(us);
PartitionFilterAcceptByKindOf filterOutBuildings(KINDOFMASK_NONE, MAKE_KINDOF_MASK(KINDOF_STRUCTURE));
PartitionFilter *filters[] = { &relationship,
&filterAlive,
&filterMapStatus,
&filterOutBuildings,
NULL
};
// scan objects in our region
ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( us->getPosition(),
modData->m_scanRadius,
FROM_CENTER_2D,
filters );
MemoryPoolObjectHolder hold( iter );
Object *obj;
ObjectTracker *newEntry;
for( obj = iter->first(); obj; obj = iter->next() )
{
// ignore ourselves, unless Design wants us to affect ourselves
if( obj == us && !modData->m_affectsSelf)
continue;
// record this object as being in the new "in list"
newEntry = newInstance(ObjectTracker);
newEntry->objectID = obj->getID();
newEntry->next = newInsideList;
newInsideList = newEntry;
} // end for obj
//
// now that we have a list of objects that are in our area of influence, look through
// the objects that were last recorded as in our area of influence and remove any
// bonus we've given them (if they are within the area of effect of another tower it's
// OK, they'll get the bonus back again when that tower does a scan which won't be too long
//
for( ObjectTracker *curr = m_insideList; curr; curr = curr->next )
{
// find this entry in the new list
ObjectTracker *o = NULL;
for( o = newInsideList; o; o = o->next )
if( o->objectID == curr->objectID )
break;
// if entry wasn't there, remove the bonus from this object
if( o == NULL )
{
obj = TheGameLogic->findObjectByID( curr->objectID );
if( obj )
effectLogic( obj, FALSE, modData );
} // end if
} // end for
// delete the inside list we have recoreded
ObjectTracker *next;
while( m_insideList )
{
next = m_insideList->next;
m_insideList->deleteInstance();
m_insideList = next;
} // end while
// set the new inside list to the one we're recording
m_insideList = newInsideList;
} // end doScan
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void PropagandaTowerBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void PropagandaTowerBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// last scan frame
xfer->xferUnsignedInt( &m_lastScanFrame );
// inside list tracking
ObjectTracker *trackerEntry;
UnsignedShort insideCount = 0;
for( trackerEntry = m_insideList; trackerEntry; trackerEntry = trackerEntry->next )
insideCount++;
xfer->xferUnsignedShort( &insideCount );
if( xfer->getXferMode() == XFER_SAVE )
{
// write all entries
for( trackerEntry = m_insideList; trackerEntry; trackerEntry = trackerEntry->next )
{
// object id
xfer->xferObjectID( &trackerEntry->objectID );
} // end for
} // end if, save
else
{
// sanity
if( m_insideList != NULL )
{
DEBUG_CRASH(( "PropagandaTowerBehavior::xfer - m_insideList should be empty but is not\n" ));
throw SC_INVALID_DATA;
} // end if
// read all entries
for( UnsignedShort i = 0; i < insideCount; ++i )
{
// allocate new tracker entry and tie to list
trackerEntry = newInstance(ObjectTracker);
trackerEntry->next = m_insideList;
m_insideList = trackerEntry;
// read object id
xfer->xferObjectID( &trackerEntry->objectID );
} // end for i
} // end else, load
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void PropagandaTowerBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,491 @@
/*
** 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: RebuildHoleBehavior.cpp //////////////////////////////////////////////////////////////////
// Author: Colin Day, June 2002
// Desc: GLA Hole behavior that reconstructs a building after death
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameState.h"
#include "Common/ThingFactory.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/RebuildHoleBehavior.h"
#include "GameLogic/Module/StickyBombUpdate.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
RebuildHoleBehaviorModuleData::RebuildHoleBehaviorModuleData( void )
{
m_workerRespawnDelay = 0.0f;
m_holeHealthRegenPercentPerSecond = 0.1f;
} // end RebuildHoleBehaviorModuleData
//-------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void RebuildHoleBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
UpdateModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "WorkerObjectName", INI::parseAsciiString, NULL, offsetof( RebuildHoleBehaviorModuleData, m_workerTemplateName ) },
{ "WorkerRespawnDelay", INI::parseDurationReal, NULL, offsetof( RebuildHoleBehaviorModuleData, m_workerRespawnDelay ) },
{ "HoleHealthRegen%PerSecond", INI::parsePercentToReal, NULL, offsetof( RebuildHoleBehaviorModuleData, m_holeHealthRegenPercentPerSecond ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
RebuildHoleBehavior::RebuildHoleBehavior( Thing *thing, const ModuleData* moduleData )
: UpdateModule( thing, moduleData )
{
m_workerID = INVALID_ID;
m_reconstructingID = INVALID_ID;
m_spawnerObjectID = INVALID_ID;
m_workerWaitCounter = 0;
m_workerTemplate = NULL;
m_rebuildTemplate = NULL;
} // end RebuildHoleBehavior
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
RebuildHoleBehavior::~RebuildHoleBehavior( void )
{
// ensure that our generated worker is destroyed,
// just in case someone decides to destroy (not kill) us...
if( m_workerID != INVALID_ID )
{
Object *worker = TheGameLogic->findObjectByID(m_workerID);
if( worker )
{
TheGameLogic->destroyObject(worker);
m_workerID = INVALID_ID;
}
}
} // end ~RebuildHoleBehavior
// ------------------------------------------------------------------------------------------------
/** we need to start all the timers and ID ties to make a new worker at the correct time */
// ------------------------------------------------------------------------------------------------
void RebuildHoleBehavior::newWorkerRespawnProcess( Object *existingWorker )
{
const RebuildHoleBehaviorModuleData *modData = getRebuildHoleBehaviorModuleData();
// if we have an existing worker, get rid of it
if( existingWorker )
{
DEBUG_ASSERTCRASH(existingWorker->getID() == m_workerID, ("m_workerID mismatch in RebuildHole"));
TheGameLogic->destroyObject( existingWorker );
}
m_workerID = INVALID_ID;
// set the timer for the next worker respawn
m_workerWaitCounter = modData->m_workerRespawnDelay;
//
// this method is called when a worker needs to be respawned from the hole. One of those
// situations is where the building was killed. Since during building reconstruction
// we made the hole "effectively not here" we will always want to make the hole
// "here again" (able to be selected, targeted by the AI etc) because it's the
// "focus" of this small area again
//
getObject()->maskObject( FALSE );
} // end newWorkerRespawnProcess
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void RebuildHoleBehavior::startRebuildProcess( const ThingTemplate *rebuild, ObjectID spawnerID )
{
// save what we're gonna do
m_rebuildTemplate = rebuild;
// store the object that spawned this hole (even though it's likely being destroyed)
m_spawnerObjectID = spawnerID;
// start the spawning process for a worker
Object *worker = TheGameLogic->findObjectByID(m_workerID);
newWorkerRespawnProcess( worker ); //Kill the worker if we have one.
} /// end startRebuildProcess
//----------------------------------------------------------------------------------------------
void RebuildHoleBehavior::transferBombs( Object *reconstruction )
{
Object *self = getObject();
Object *obj = TheGameLogic->getFirstObject();
while( obj )
{
if( obj->isKindOf( KINDOF_MINE ) )
{
static NameKeyType key_StickyBombUpdate = NAMEKEY( "StickyBombUpdate" );
StickyBombUpdate *update = (StickyBombUpdate*)obj->findUpdateModule( key_StickyBombUpdate );
if( update && update->getTargetObject() == self )
{
update->setTargetObject( reconstruction );
}
}
obj = obj->getNextObject();
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UpdateSleepTime RebuildHoleBehavior::update( void )
{
const RebuildHoleBehaviorModuleData *modData = getRebuildHoleBehaviorModuleData();
Object *hole = getObject();
Object *reconstructing = NULL;
Object *worker = NULL;
// get the worker object if we have one
if( m_workerID != 0 )
{
// get the worker
worker = TheGameLogic->findObjectByID( m_workerID );
// if the worker is no longer there, start the respawning process for a worker again
if( worker == NULL )
newWorkerRespawnProcess( NULL );
} // end if
// if we have a reconstructing object built, get the actual object pointer
if( m_reconstructingID != 0 )
{
// get object pointer
reconstructing = TheGameLogic->findObjectByID( m_reconstructingID );
//
// if that object does not exist anymore, we need to kill a worker if we have one
// and start the spawning process over again
//
if( reconstructing == NULL )
{
newWorkerRespawnProcess( worker );
m_reconstructingID = INVALID_ID;
} // end if
} // end if
// see if it's time for us to spawn a worker
if( worker == NULL && m_workerWaitCounter > 0 )
{
// decrement counter and respawn if it's time
if( --m_workerWaitCounter == 0 )
{
// resolve the worker template pointer if necessary
if( m_workerTemplate == NULL )
m_workerTemplate = TheThingFactory->findTemplate( modData->m_workerTemplateName );
// create a worker
worker = TheThingFactory->newObject( m_workerTemplate, hole->getTeam() );
if( worker )
{
// set the position of the worker to that of the hole
worker->setPosition( hole->getPosition() );
// save the ID of the worker spawned
m_workerID = worker->getID();
//
// tell the worker to begin construction of a building if one does not
// exist yet. If one does, have construction resume
//
AIUpdateInterface *ai = worker->getAIUpdateInterface();
if( ai )
{
if( reconstructing == NULL )
reconstructing = ai->construct( m_rebuildTemplate,
hole->getPosition(),
hole->getOrientation(),
hole->getControllingPlayer(),
TRUE );
else
ai->aiResumeConstruction( reconstructing, CMD_FROM_AI );
for ( Object *obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() )
{
// Just like the building transfers attackers to the hole when it creates us, we need to transfer
// attackers to our replacement building before we mask ourselves.
AIUpdateInterface* ai = obj->getAI();
if (!ai)
continue;
ai->transferAttack(hole->getID(), reconstructing->getID());
}
// save the id of what we are reconstructing
m_reconstructingID = reconstructing->getID();
//Kris: Hacking the building to set the hole as the producer... so if the site dies, we
//can transfer the attack back to the hole. The object has OBJECT_STATUS_RECONSTRUCTING
//which we check when the object dies.
reconstructing->setProducer( hole );
// we want to prevent the player from selecting and doing things with this worker
worker->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_UNSELECTABLE ) );
//
// we want to prevent the player and the AI from selecting or targeting the hole
// cause the focus in this area (while reconstruction is happening) is the
// actual reconstructing building
//
hole->maskObject( TRUE );
transferBombs( reconstructing );
} // end if
} // end if, worker
} // end if, time to spawn a worker
} // end if, check for working respawn
// holes get auto-healed when they're sittin around
BodyModuleInterface *body = hole->getBodyModule();
if( body->getHealth() < body->getMaxHealth() )
{
DamageInfo healingInfo;
// do some healing
healingInfo.in.m_amount = (modData->m_holeHealthRegenPercentPerSecond / LOGICFRAMES_PER_SECOND) *
body->getMaxHealth();
healingInfo.in.m_sourceID = hole->getID();
healingInfo.in.m_damageType = DAMAGE_HEALING;
healingInfo.in.m_deathType = DEATH_NONE;
body->attemptHealing( &healingInfo );
} // end if
// when re-construction is complete, we remove this hole and worker
if( reconstructing && !reconstructing->getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) )
{
// Transfer hole name to new building
TheScriptEngine->transferObjectName( hole->getName(), reconstructing );
// make the worker go away
if( worker )
TheGameLogic->destroyObject( worker );
// make the hole go away
TheGameLogic->destroyObject( hole );
} // end if
return UPDATE_SLEEP_NONE;
} // end update
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void RebuildHoleBehavior::onDie( const DamageInfo *damageInfo )
{
if( m_workerID != INVALID_ID )
{
// Our rebuilding building and us the hole can be killed in the same frame, which means we may not have
// deleted our generated worker since we do that in our update.
Object *worker = TheGameLogic->findObjectByID(m_workerID);
if( worker )
{
TheGameLogic->destroyObject(worker);
m_workerID = INVALID_ID;
}
}
Object *obj = getObject();
// destroy us
TheGameLogic->destroyObject( obj );
} // end onDie
// ------------------------------------------------------------------------------------------------
/** Helper method to get interface given an object */
// ------------------------------------------------------------------------------------------------
/*static*/ RebuildHoleBehaviorInterface* RebuildHoleBehavior::getRebuildHoleBehaviorInterfaceFromObject( Object *obj )
{
RebuildHoleBehaviorInterface *rhbi = NULL;
if( obj )
{
for( BehaviorModule **i = obj->getBehaviorModules(); *i; ++i )
{
rhbi = (*i)->getRebuildHoleBehaviorInterface();
if( rhbi )
break; // exit for
} // end for i
} // end if, obj
return rhbi;
} // end getRebuildHoleBehaviorInterfaceFromObject
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void RebuildHoleBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version,
* 2: Added spawner id */
// ------------------------------------------------------------------------------------------------
void RebuildHoleBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 2;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// worker ID
xfer->xferObjectID( &m_workerID );
// reconstructing id
xfer->xferObjectID( &m_reconstructingID );
// spawner ID
if( version >= 2 )
xfer->xferObjectID( &m_spawnerObjectID );
// worker wait counter
xfer->xferUnsignedInt( &m_workerWaitCounter );
// worker template
AsciiString workerName = m_workerTemplate ? m_workerTemplate->getName() : AsciiString::TheEmptyString;
xfer->xferAsciiString( &workerName );
if( xfer->getXferMode() == XFER_LOAD )
{
if( workerName != AsciiString::TheEmptyString )
{
m_workerTemplate = TheThingFactory->findTemplate( workerName );
if( m_workerTemplate == NULL )
{
DEBUG_CRASH(( "RebuildHoleBehavior::xfer - Unable to find template '%s'\n",
workerName.str() ));
throw SC_INVALID_DATA;
} // end if
} // end if
else
m_workerTemplate = NULL;
} // end if
// rebuild template
AsciiString rebuildName = m_rebuildTemplate ? m_rebuildTemplate->getName() : AsciiString::TheEmptyString;
xfer->xferAsciiString( &rebuildName );
if( xfer->getXferMode() == XFER_LOAD )
{
if( rebuildName != AsciiString::TheEmptyString )
{
m_rebuildTemplate = TheThingFactory->findTemplate( rebuildName );
if( m_rebuildTemplate == NULL )
{
DEBUG_CRASH(( "RebuildHoleBehavior::xfer - Unable to find template '%s'\n",
rebuildName.str() ));
throw SC_INVALID_DATA;
} // end if
} // end if
else
m_rebuildTemplate = NULL;
} // end if
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void RebuildHoleBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,584 @@
/*
** 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: SlowDeathBehavior.cpp ///////////////////////////////////////////////////////////////////////
// Author:
// Desc:
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_SLOWDEATHPHASE_NAMES
#include "Common/GameLOD.h"
#include "Common/INI.h"
#include "Common/RandomValue.h"
#include "Common/Thing.h"
#include "Common/ThingTemplate.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/PhysicsUpdate.h"
#include "GameLogic/Module/SlowDeathBehavior.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/SlavedUpdate.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectCreationList.h"
#include "GameLogic/Weapon.h"
#include "GameClient/Drawable.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
const Real BEGIN_MIDPOINT_RATIO = 0.35f;
const Real END_MIDPOINT_RATIO = 0.65f;
//-------------------------------------------------------------------------------------------------
SlowDeathBehaviorModuleData::SlowDeathBehaviorModuleData()
{
m_sinkRate = 0;
m_probabilityModifier = 10;
m_modifierBonusPerOverkillPercent = 0;
m_sinkDelay = 0;
m_sinkDelayVariance = 0;
m_destructionDelay = 0;
m_destructionDelayVariance = 0;
m_destructionAltitude = -10;
m_maskOfLoadedEffects = 0; //assume no ocl, fx, or weapons.
m_flingForce = 0;
m_flingForceVariance = 0;
m_flingPitch = 0;
m_flingPitchVariance = 0;
// redundant.
//m_fx.clear();
//m_ocls.clear();
//m_weapons.clear();
}
//-------------------------------------------------------------------------------------------------
static void parseFX( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ )
{
SlowDeathBehaviorModuleData* self = (SlowDeathBehaviorModuleData*)instance;
SlowDeathPhaseType sdphase = (SlowDeathPhaseType)INI::scanIndexList(ini->getNextToken(), TheSlowDeathPhaseNames);
for (const char* token = ini->getNextToken(); token != NULL; token = ini->getNextTokenOrNull())
{
const FXList *fxl = TheFXListStore->findFXList((token)); // could be null! this is OK!
self->m_fx[sdphase].push_back(fxl);
if (fxl)
self->m_maskOfLoadedEffects |= SlowDeathBehaviorModuleData::HAS_FX;
}
}
//-------------------------------------------------------------------------------------------------
static void parseOCL( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ )
{
SlowDeathBehaviorModuleData* self = (SlowDeathBehaviorModuleData*)instance;
SlowDeathPhaseType sdphase = (SlowDeathPhaseType)INI::scanIndexList(ini->getNextToken(), TheSlowDeathPhaseNames);
for (const char* token = ini->getNextToken(); token != NULL; token = ini->getNextTokenOrNull())
{
const ObjectCreationList *ocl = TheObjectCreationListStore->findObjectCreationList(token); // could be null! this is OK!
self->m_ocls[sdphase].push_back(ocl);
if (ocl)
self->m_maskOfLoadedEffects |= SlowDeathBehaviorModuleData::HAS_OCL;
}
}
//-------------------------------------------------------------------------------------------------
static void parseWeapon( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ )
{
SlowDeathBehaviorModuleData* self = (SlowDeathBehaviorModuleData*)instance;
SlowDeathPhaseType sdphase = (SlowDeathPhaseType)INI::scanIndexList(ini->getNextToken(), TheSlowDeathPhaseNames);
for (const char* token = ini->getNextToken(); token != NULL; token = ini->getNextTokenOrNull())
{
const WeaponTemplate *wt = TheWeaponStore->findWeaponTemplate(token); // could be null! this is OK!
self->m_weapons[sdphase].push_back(wt);
if (wt)
self->m_maskOfLoadedEffects |= SlowDeathBehaviorModuleData::HAS_WEAPON;
}
}
//-------------------------------------------------------------------------------------------------
/*static*/ void SlowDeathBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p)
{
UpdateModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "SinkRate", INI::parseVelocityReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_sinkRate ) },
{ "ProbabilityModifier", INI::parseInt, NULL, offsetof( SlowDeathBehaviorModuleData, m_probabilityModifier ) },
{ "ModifierBonusPerOverkillPercent", INI::parsePercentToReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_modifierBonusPerOverkillPercent ) },
{ "SinkDelay", INI::parseDurationUnsignedInt, NULL, offsetof( SlowDeathBehaviorModuleData, m_sinkDelay ) },
{ "SinkDelayVariance", INI::parseDurationUnsignedInt, NULL, offsetof( SlowDeathBehaviorModuleData, m_sinkDelayVariance ) },
{ "DestructionDelay", INI::parseDurationUnsignedInt, NULL, offsetof( SlowDeathBehaviorModuleData, m_destructionDelay ) },
{ "DestructionDelayVariance", INI::parseDurationUnsignedInt, NULL, offsetof( SlowDeathBehaviorModuleData, m_destructionDelayVariance ) },
{ "DestructionAltitude", INI::parseReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_destructionAltitude ) },
{ "FX", parseFX, NULL, 0 },
{ "OCL", parseOCL, NULL, 0 },
{ "Weapon", parseWeapon, NULL, 0 },
{ "FlingForce", INI::parseReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_flingForce) },
{ "FlingForceVariance", INI::parseReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_flingForceVariance) },
{ "FlingPitch", INI::parseAngleReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_flingPitch) },
{ "FlingPitchVariance", INI::parseAngleReal, NULL, offsetof( SlowDeathBehaviorModuleData, m_flingPitchVariance) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
p.add(DieMuxData::getFieldParse(), offsetof( SlowDeathBehaviorModuleData, m_dieMuxData ));
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SlowDeathBehavior::SlowDeathBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
{
m_flags = 0;
m_sinkFrame = 0;
m_midpointFrame = 0;
m_destructionFrame = 0;
m_acceleratedTimeScale = 1.0f;
if (getSlowDeathBehaviorModuleData()->m_probabilityModifier < 1)
{
DEBUG_CRASH(("ProbabilityModifer must be >= 1.\n"));
throw INI_INVALID_DATA;
}
setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SlowDeathBehavior::~SlowDeathBehavior( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Int SlowDeathBehavior::getProbabilityModifier( const DamageInfo *damageInfo ) const
{
// Calculating how far past dead we were allows us to pick more spectacular deaths when
// severly killed, and more sedate ones when only slightly killed.
// eg ( 200 hp max, had 10 left, took 50 damage, 40 overkill, (40/200) * 100 = 20 overkill %)
Int overkillDamage = damageInfo->out.m_actualDamageDealt - damageInfo->out.m_actualDamageClipped;
Real overkillPercent = (float)overkillDamage / (float)getObject()->getBodyModule()->getMaxHealth();
Int overkillModifier = overkillPercent * getSlowDeathBehaviorModuleData()->m_modifierBonusPerOverkillPercent;
return max( getSlowDeathBehaviorModuleData()->m_probabilityModifier + overkillModifier, 1 );
}
//-------------------------------------------------------------------------------------------------
static void calcRandomForce(Real minMag, Real maxMag, Real minPitch, Real maxPitch, Coord3D& force)
{
Real angle = GameLogicRandomValueReal(-PI, PI);
Real pitch = GameLogicRandomValueReal(minPitch, maxPitch);
Real mag = GameLogicRandomValueReal(minMag, maxMag);
Matrix3D mtx(1);
mtx.Scale(mag);
mtx.Rotate_Z(angle);
mtx.Rotate_Y(-pitch);
Vector3 v = mtx.Get_X_Vector();
force.x = v.X;
force.y = v.Y;
force.z = v.Z;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void SlowDeathBehavior::beginSlowDeath(const DamageInfo *damageInfo)
{
if (!isSlowDeathActivated())
{
const SlowDeathBehaviorModuleData* d = getSlowDeathBehaviorModuleData();
Object* obj = getObject();
if (d->m_sinkRate && obj->isKindOf(KINDOF_INFANTRY))
{
Drawable *draw = getObject()->getDrawable();
if ( draw )
{
// this object sinks slowly after it dies so don't draw a
// floating shadow decal on the ground above it.
obj->getDrawable()->setShadowsEnabled(false);
draw->setTerrainDecalFadeTarget( 0.0f, -0.2f );
}
}
// Ask game detail manager if we need to speedup all deaths to improve performance
Real timeScale = TheGameLODManager->getSlowDeathScale();
m_acceleratedTimeScale = 1.0f; // assume normal death speed.
if (timeScale == 0.0f && !d->hasNonLodEffects())
{
// Deaths happen instantly so just delete the object and return
TheGameLogic->destroyObject(obj);
return;
}
else
{
// timescale is some non-zero value so we may need to speed up death
if( getObject()->isKindOf( KINDOF_HULK ) && TheGameLogic->getHulkMaxLifetimeOverride() != -1 )
{
//Scripts don't want hulks around, so start sinking immediately!
m_sinkFrame = 1;
m_midpointFrame = (LOGICFRAMES_PER_SECOND/2) + 1;
m_destructionFrame = LOGICFRAMES_PER_SECOND + 1;
m_acceleratedTimeScale = 1.0f;
}
else
{
m_sinkFrame = timeScale * (d->m_sinkDelay + GameLogicRandomValue(0, d->m_sinkDelayVariance));
m_destructionFrame = timeScale * (d->m_destructionDelay + GameLogicRandomValue(0, d->m_destructionDelayVariance));
m_midpointFrame = GameLogicRandomValue( BEGIN_MIDPOINT_RATIO * m_destructionFrame, END_MIDPOINT_RATIO * m_destructionFrame );
m_acceleratedTimeScale = timeScale;
}
}
UnsignedInt now = TheGameLogic->getFrame();
if (d->m_flingForce > 0)
{
//Just in case this is a stingersoldier or other HELD object, lets set them free so they will fly
// with their own physics during slow death
if( obj->isDisabledByType( DISABLED_HELD ) )
{
static NameKeyType key_SlavedUpdate = NAMEKEY( "SlavedUpdate" );
SlavedUpdate* slave = (SlavedUpdate*)obj->findUpdateModule( key_SlavedUpdate );
if( slave )
{
slave->onSlaverDie( NULL );
}
}
PhysicsBehavior* physics = obj->getPhysics();
if (physics)
{
// make sure we are at least a bit above the ground
const Real MIN_ALTITUDE = 1.0f;
Real altitude = obj->getHeightAboveTerrain();
if (altitude < MIN_ALTITUDE)
{
Coord3D pos = *obj->getPosition();
pos.z += MIN_ALTITUDE;
obj->setPosition(&pos);
}
Coord3D force;
calcRandomForce(d->m_flingForce, d->m_flingForce + d->m_flingForceVariance,
d->m_flingPitch, d->m_flingPitch + d->m_flingPitchVariance, force);
physics->setAllowToFall(true);
physics->applyForce(&force);
physics->setExtraBounciness(-1.0); // we don't want this guy to bounce at all
physics->setExtraFriction(-3 * SECONDS_PER_LOGICFRAME_REAL); // reduce his ground friction a bit
physics->setAllowBouncing(true);
Real orientation = atan2(force.y, force.x);
physics->setAngles(orientation, 0, 0);
obj->getDrawable()->setModelConditionState(MODELCONDITION_EXPLODED_FLAILING);
m_flags |= (1<<FLUNG_INTO_AIR);
}
setWakeFrame(obj, UPDATE_SLEEP_NONE);
}
else
{
// we don't need to wake up immediately, but only when the first of these
// counters wants to trigger....
Int whenToWakeTime = m_sinkFrame;
if (whenToWakeTime > m_destructionFrame)
whenToWakeTime = m_destructionFrame;
if (whenToWakeTime > m_midpointFrame)
whenToWakeTime = m_midpointFrame;
setWakeFrame(obj, UPDATE_SLEEP(whenToWakeTime));
}
m_sinkFrame += now;
m_destructionFrame += now;
m_midpointFrame += now;
m_flags |= (1<<SLOW_DEATH_ACTIVATED);
doPhaseStuff(SDPHASE_INITIAL);
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void SlowDeathBehavior::doPhaseStuff(SlowDeathPhaseType sdphase)
{
const SlowDeathBehaviorModuleData* d = getSlowDeathBehaviorModuleData();
Int idx, listSize;
if (!d->m_maskOfLoadedEffects)
return; //has no ocl, fx, or weapons.
listSize = d->m_fx[sdphase].size();
if (listSize > 0)
{
idx = GameLogicRandomValue(0, listSize-1);
const FXListVec& v = d->m_fx[sdphase];
DEBUG_ASSERTCRASH(idx>=0&&idx<v.size(),("bad idx"));
const FXList* fxl = v[idx];
FXList::doFXObj(fxl, getObject(), NULL);
}
listSize = d->m_ocls[sdphase].size();
if (listSize > 0)
{
idx = GameLogicRandomValue(0, listSize-1);
const OCLVec& v = d->m_ocls[sdphase];
DEBUG_ASSERTCRASH(idx>=0&&idx<v.size(),("bad idx"));
const ObjectCreationList* ocl = v[idx];
ObjectCreationList::create(ocl, getObject(), NULL);
}
listSize = d->m_weapons[sdphase].size();
if (listSize > 0)
{
idx = GameLogicRandomValue(0, listSize-1);
const WeaponTemplateVec& v = d->m_weapons[sdphase];
DEBUG_ASSERTCRASH(idx>=0&&idx<v.size(),("bad idx"));
const WeaponTemplate* wt = v[idx];
if (wt)
{
TheWeaponStore->createAndFireTempWeapon(wt, getObject(), getObject()->getPosition());
}
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UpdateSleepTime SlowDeathBehavior::update()
{
//DEBUG_LOG(("updating SlowDeathBehavior %08lx\n",this));
DEBUG_ASSERTCRASH(isSlowDeathActivated(), ("hmm, this should not be possible"));
const SlowDeathBehaviorModuleData* d = getSlowDeathBehaviorModuleData();
Object* obj = getObject();
Real timeScale = TheGameLODManager->getSlowDeathScale();
// Check if we have normal time scale but LODManager is requeseting acceleration
if (timeScale != 1.0f && m_acceleratedTimeScale == 1.0f && !d->hasNonLodEffects())
{
// speed of deaths has been increased since beginning of death
// so adjust it to current levels.
if (timeScale == 0)
{
// instant death
TheGameLogic->destroyObject(obj);
return UPDATE_SLEEP_NONE;
}
m_sinkFrame = (Real)m_sinkFrame * timeScale;
m_midpointFrame = (Real)m_midpointFrame * timeScale;
m_destructionFrame = (Real)m_destructionFrame * timeScale;
m_acceleratedTimeScale = timeScale;
};
UnsignedInt now = TheGameLogic->getFrame();
if ((m_flags & (1<<FLUNG_INTO_AIR)) != 0)
{
if ((m_flags & (1<<BOUNCED)) == 0)
{
++m_sinkFrame;
++m_midpointFrame;
++m_destructionFrame;
if (!obj->isAboveTerrain())
{
obj->clearAndSetModelConditionFlags(MAKE_MODELCONDITION_MASK(MODELCONDITION_EXPLODED_FLAILING),
MAKE_MODELCONDITION_MASK(MODELCONDITION_EXPLODED_BOUNCING));
m_flags |= (1<<BOUNCED);
}
// Here we want to make sure we die if we collide with a tree on the way down
PhysicsBehavior *phys = obj->getPhysics();
if ( phys )
{
ObjectID treeID = phys->getLastCollidee();
Object *tree = TheGameLogic->findObjectByID( treeID );
if ( tree )
{
if (tree->isKindOf( KINDOF_SHRUBBERY ) )
{
obj->setDisabled( DISABLED_HELD );
obj->clearModelConditionFlags( MAKE_MODELCONDITION_MASK(MODELCONDITION_EXPLODED_FLAILING) );
obj->clearModelConditionFlags( MAKE_MODELCONDITION_MASK(MODELCONDITION_EXPLODED_BOUNCING) );
obj->setModelConditionFlags( MAKE_MODELCONDITION_MASK(MODELCONDITION_PARACHUTING) ); //looks like he is snagged in a tree
obj->setPositionZ( obj->getPosition()->z - (d->m_sinkRate * 50.0f) );// make him sink faster
if ( !obj->isAboveTerrain() )
TheGameLogic->destroyObject(obj);
}
}
}
}
}
if ( (now >= m_sinkFrame && d->m_sinkRate > 0.0f) )
{
// disable Physics (if any) so that we can control the sink...
obj->setDisabled( DISABLED_HELD );
Coord3D pos = *obj->getPosition();
pos.z -= d->m_sinkRate / m_acceleratedTimeScale;
obj->setPosition( &pos );
}
if( now >= m_midpointFrame && (m_flags & (1<<MIDPOINT_EXECUTED)) == 0 )
{
doPhaseStuff(SDPHASE_MIDPOINT);
m_flags |= (1<<MIDPOINT_EXECUTED);
}
if (now >= m_destructionFrame)
{
doPhaseStuff(SDPHASE_FINAL);
TheGameLogic->destroyObject(obj);
}
return UPDATE_SLEEP_NONE;
}
//-------------------------------------------------------------------------------------------------
void SlowDeathBehavior::onDie( const DamageInfo *damageInfo )
{
Object *obj = getObject();
if (!isDieApplicable(damageInfo))
return;
AIUpdateInterface *ai = obj->getAIUpdateInterface();
if (ai)
{
// has another AI already handled us. (hopefully another SlowDeathBehavior)
if (ai->isAiInDeadState())
return;
ai->markAsDead();
}
// deselect this unit for all players.
TheGameLogic->deselectObject(obj, PLAYERMASK_ALL, TRUE);
Int total = 0;
for (BehaviorModule** update = obj->getBehaviorModules(); *update; ++update)
{
SlowDeathBehaviorInterface* sdu = (*update)->getSlowDeathBehaviorInterface();
if (sdu != NULL && sdu->isDieApplicable(damageInfo))
{
total += sdu->getProbabilityModifier( damageInfo );
}
}
DEBUG_ASSERTCRASH(total > 0, ("Hmm, this is wrong"));
// this returns a value from 1...total, inclusive
Int roll = GameLogicRandomValue(1, total);
for (/* UpdateModuleInterface** */ update = obj->getBehaviorModules(); *update; ++update)
{
SlowDeathBehaviorInterface* sdu = (*update)->getSlowDeathBehaviorInterface();
if (sdu != NULL && sdu->isDieApplicable(damageInfo))
{
roll -= sdu->getProbabilityModifier( damageInfo );
if (roll <= 0)
{
sdu->beginSlowDeath(damageInfo);
return;
}
}
}
DEBUG_CRASH(("We should never get here"));
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SlowDeathBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SlowDeathBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// sink frame
xfer->xferUnsignedInt( &m_sinkFrame );
// midpoint frame
xfer->xferUnsignedInt( &m_midpointFrame );
// destruction frame
xfer->xferUnsignedInt( &m_destructionFrame );
// accelerated time scale
xfer->xferReal( &m_acceleratedTimeScale );
// flags
xfer->xferUnsignedInt( &m_flags );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SlowDeathBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,184 @@
/*
** 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: SupplyWarehouseCripplingBehavior.cpp /////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, Septemmber 2002
// Desc: Behavior that Disables the building on ReallyDamaged edge state, and manages an Update timer to heal
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameLogic/Module/SupplyWarehouseCripplingBehavior.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/BodyModule.h"
//-------------------------------------------------------------------------------------------------
SupplyWarehouseCripplingBehaviorModuleData::SupplyWarehouseCripplingBehaviorModuleData()
{
m_selfHealSupression = 0; ///< Time since last damage until I can start to heal
m_selfHealDelay = 0; ///< Once I am okay to heal, how often to do so
m_selfHealAmount = 0; ///< And how much
}
//-------------------------------------------------------------------------------------------------
/*static*/ void SupplyWarehouseCripplingBehaviorModuleData::buildFieldParse(MultiIniFieldParse& p)
{
static const FieldParse dataFieldParse[] =
{
{ "SelfHealSupression", INI::parseDurationUnsignedInt, NULL, offsetof(SupplyWarehouseCripplingBehaviorModuleData, m_selfHealSupression) },
{ "SelfHealDelay", INI::parseDurationUnsignedInt, NULL, offsetof(SupplyWarehouseCripplingBehaviorModuleData, m_selfHealDelay) },
{ "SelfHealAmount", INI::parseReal, NULL, offsetof(SupplyWarehouseCripplingBehaviorModuleData, m_selfHealAmount) },
{ 0, 0, 0, 0 }
};
UpdateModuleData::buildFieldParse(p);
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SupplyWarehouseCripplingBehavior::SupplyWarehouseCripplingBehavior( Thing *thing, const ModuleData* moduleData ) : UpdateModule( thing, moduleData )
{
m_healingSupressedUntilFrame = 0;
m_nextHealingFrame = 0;
setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SupplyWarehouseCripplingBehavior::~SupplyWarehouseCripplingBehavior( void )
{
}
//-------------------------------------------------------------------------------------------------
/** Damage has been dealt, this is an opportunity to react to that damage */
//-------------------------------------------------------------------------------------------------
void SupplyWarehouseCripplingBehavior::onDamage( DamageInfo *damageInfo )
{
UnsignedInt now = TheGameLogic->getFrame();
resetSelfHealSupression();
setWakeFrame(getObject(), UPDATE_SLEEP(m_healingSupressedUntilFrame - now));// we got hit, time to get up for work after a quick snooze
}
//-------------------------------------------------------------------------------------------------
void SupplyWarehouseCripplingBehavior::onBodyDamageStateChange(const DamageInfo* damageInfo, BodyDamageType oldState, BodyDamageType newState)
{
if( newState == BODY_REALLYDAMAGED )
startCrippledEffects();
else if( oldState == BODY_REALLYDAMAGED )
stopCrippledEffects();
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime SupplyWarehouseCripplingBehavior::update()
{
// Supression is handled by sleeping the module, so if I am here, I know it is time to heal.
const SupplyWarehouseCripplingBehaviorModuleData* md = getSupplyWarehouseCripplingBehaviorModuleData();
UnsignedInt now = TheGameLogic->getFrame();
m_nextHealingFrame = now + md->m_selfHealDelay;
getObject()->attemptHealing(md->m_selfHealAmount, NULL);
if( getObject()->getBodyModule()->getHealth() == getObject()->getBodyModule()->getMaxHealth() )
return UPDATE_SLEEP_FOREVER;// this can't be in onHealing, as the healing comes from here
// in the update, and sleep settings in onHealing would be overridden by my return value.
// Delay between heals is also handled by sleeping the module. How cool is that?
return UPDATE_SLEEP(m_nextHealingFrame - now);
}
// ------------------------------------------------------------------------------------------------
void SupplyWarehouseCripplingBehavior::resetSelfHealSupression()
{
const SupplyWarehouseCripplingBehaviorModuleData* md = getSupplyWarehouseCripplingBehaviorModuleData();
UnsignedInt now = TheGameLogic->getFrame();
m_healingSupressedUntilFrame = now + md->m_selfHealSupression;
m_nextHealingFrame = m_healingSupressedUntilFrame;
}
// ------------------------------------------------------------------------------------------------
void SupplyWarehouseCripplingBehavior::startCrippledEffects()
{
DockUpdateInterface *myDock = getObject()->getDockUpdateInterface();
myDock->setDockCrippled( TRUE );
}
// ------------------------------------------------------------------------------------------------
void SupplyWarehouseCripplingBehavior::stopCrippledEffects()
{
DockUpdateInterface *myDock = getObject()->getDockUpdateInterface();
myDock->setDockCrippled( FALSE );
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SupplyWarehouseCripplingBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SupplyWarehouseCripplingBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
// healing supressed until frame
xfer->xferUnsignedInt( &m_healingSupressedUntilFrame );
// next healing frame
xfer->xferUnsignedInt( &m_nextHealingFrame );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SupplyWarehouseCripplingBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,181 @@
/*
** 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: TechBuildingBehavior.cpp /////////////////////////////////////////////////////////////////
// Author: Colin Day, October 2002
// Desc: Tech building basic behavior
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.H"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/FXList.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/Module/TechBuildingBehavior.h"
#include "GameLogic/Object.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
TechBuildingBehaviorModuleData::TechBuildingBehaviorModuleData( void )
{
m_pulseFX = NULL;
m_pulseFXRate = 0;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void TechBuildingBehaviorModuleData::buildFieldParse( MultiIniFieldParse &p )
{
UpdateModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "PulseFX", INI::parseFXList, NULL, offsetof( TechBuildingBehaviorModuleData, m_pulseFX ) },
{ "PulseFXRate", INI::parseDurationUnsignedInt, NULL, offsetof( TechBuildingBehaviorModuleData, m_pulseFXRate ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
TechBuildingBehavior::TechBuildingBehavior( Thing *thing, const ModuleData *modData )
: UpdateModule( thing, modData )
{
//
// setup ourselves so we do at least one update evaluation after the module
// is in the world
//
setWakeFrame(getObject(), UPDATE_SLEEP_NONE);
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
TechBuildingBehavior::~TechBuildingBehavior( void )
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime TechBuildingBehavior::update( void )
{
Object *us = getObject();
const TechBuildingBehaviorModuleData* d = getTechBuildingBehaviorModuleData();
Bool captured = false;
// update our model condition for the captured status
Player *player = us->getControllingPlayer();
if( player && player->isPlayableSide() )
{
us->setModelConditionState( MODELCONDITION_CAPTURED );
captured = true;
}
else
{
us->clearModelConditionState( MODELCONDITION_CAPTURED );
captured = false;
}
// if we have a pulse fx, and are owned, sleep only a little while, otherwise sleep forever
if (d->m_pulseFX != NULL && d->m_pulseFXRate > 0 && captured)
{
FXList::doFXObj( d->m_pulseFX, us );
return UPDATE_SLEEP(d->m_pulseFXRate);
}
else
{
// now sleep forever my dear
return UPDATE_SLEEP_FOREVER;
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TechBuildingBehavior::onDie( const DamageInfo *damageInfo )
{
//
// put us on the team of the neutral player so no player has any bonus from us
//
Object *us = getObject();
us->clearModelConditionState( MODELCONDITION_CAPTURED );
us->setTeam( ThePlayerList->getNeutralPlayer()->getDefaultTeam() );
} // end onDie
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TechBuildingBehavior::onCapture( Player *oldOwner, Player *newOwner )
{
// wake up next frame so we can re-evaluate our captured status
setWakeFrame( getObject(), UPDATE_SLEEP_NONE );
} // end onCapture
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TechBuildingBehavior::crc( Xfer *xfer )
{
// extend base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TechBuildingBehavior::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
UpdateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TechBuildingBehavior::loadPostProcess( void )
{
// extend base class
UpdateModule::loadPostProcess();
} // end loadPostProcess

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
/*
** 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: BodyModule.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Colin Day, September 2002
// Desc: BodyModule base class
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/BodyModule.h"
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void BodyModule::crc( Xfer *xfer )
{
// call base class
BehaviorModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void BodyModule::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// call base class
BehaviorModule::xfer( xfer );
// damage scalar
xfer->xferReal( &m_damageScalar );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void BodyModule::loadPostProcess( void )
{
// call base class
BehaviorModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,101 @@
/*
** 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: HighlanderBody.cpp ////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, November 2002
// Desc: Takes damage according to armor, but can't die from normal damage. Can die from Unresistable though
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameLogic/Module/HighlanderBody.h"
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
HighlanderBody::HighlanderBody( Thing *thing, const ModuleData* moduleData )
: ActiveBody( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
HighlanderBody::~HighlanderBody( void )
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void HighlanderBody::attemptDamage( DamageInfo *damageInfo )
{
// Bind to one hitpoint remaining afterwards, unless it is Unresistable damage
if( damageInfo->in.m_damageType != DAMAGE_UNRESISTABLE )
damageInfo->in.m_amount = min( damageInfo->in.m_amount, getHealth() - 1 );
ActiveBody::attemptDamage(damageInfo);
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void HighlanderBody::crc( Xfer *xfer )
{
// extend base class
ActiveBody::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void HighlanderBody::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
ActiveBody::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void HighlanderBody::loadPostProcess( void )
{
// extend base class
ActiveBody::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,171 @@
/*
** 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: HiveStructureBody.cpp ////////////////////////////////////////////////////////////////////////
// Desc: Hive structure bodies are structure bodies with the ability to propagate specified
// damage types to slaves when available. If there are no slaves, the the structure
// will take the damage.
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "Common/ThingTemplate.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Damage.h"
#include "GameLogic/Module/HiveStructureBody.h"
#include "GameLogic/Module/SpawnBehavior.h"
#include "GameLogic/Module/ContainModule.h"
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
HiveStructureBodyModuleData::HiveStructureBodyModuleData()
{
m_damageTypesToPropagateToSlaves = DAMAGE_TYPE_FLAGS_NONE;
m_damageTypesToSwallow = DAMAGE_TYPE_FLAGS_NONE;
}
//-------------------------------------------------------------------------------------------------
HiveStructureBody::HiveStructureBody( Thing *thing, const ModuleData* moduleData )
: StructureBody( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
HiveStructureBody::~HiveStructureBody( void )
{
}
//------------------------------------------------------------------------------------------------
void HiveStructureBody::attemptDamage( DamageInfo *damageInfo )
{
const HiveStructureBodyModuleData *data = getHiveStructureBodyModuleData();
Object *hive = getObject();
if( getDamageTypeFlag( data->m_damageTypesToPropagateToSlaves, damageInfo->in.m_damageType ) )
{
//We have the right type of damage types incoming to propagate to slaves. Do we have slaves?
SpawnBehaviorInterface *spawnInterface = hive->getSpawnBehaviorInterface();
if( spawnInterface )
{
//We found the spawn interface, now get some slaves!
Object *shooter = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID );
if( shooter )
{
Object *slave = spawnInterface->getClosestSlave( shooter->getPosition() );
if( slave )
{
//Propagate damage and return!
slave->attemptDamage( damageInfo );
return;
}
else if( getDamageTypeFlag( data->m_damageTypesToSwallow, damageInfo->in.m_damageType ) )
{
//no slave to give to, so eat it
damageInfo->out.m_actualDamageDealt = 0.0f;
damageInfo->out.m_actualDamageClipped = 0.0f;
damageInfo->out.m_noEffect = true;
return;
}
}
}
else if ( hive->getContain() )
{
ContainModuleInterface *contain = hive->getContain();
//We found the spawn interface, now get some slaves!
Object *shooter = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID );
if( shooter )
{
Object *rider = contain->getClosestRider( shooter->getPosition() );
if( rider )
{
//Propagate damage and return!
rider->attemptDamage( damageInfo );
return;
}
else if( getDamageTypeFlag( data->m_damageTypesToSwallow, damageInfo->in.m_damageType ) )
{
//no slave to give to, so eat it
damageInfo->out.m_actualDamageDealt = 0.0f;
damageInfo->out.m_actualDamageClipped = 0.0f;
damageInfo->out.m_noEffect = true;
return;
}
}
}
else
{
DEBUG_CRASH( ("%s has a HiveStructureBody module, which requires a SpawnBehavior module. Thus it is unable to propagate damage to slaves.", hive->getTemplate()->getName().str() ) );
}
}
//Nothing to propagate (either different damage type or no slaves),
//so damage me instead!
StructureBody::attemptDamage( damageInfo );
}
//------------------------------------------------------------------------------------------------
void HiveStructureBody::crc( Xfer *xfer )
{
// extend parent class
StructureBody::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void HiveStructureBody::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// parent class
StructureBody::xfer( xfer );
}
//------------------------------------------------------------------------------------------------
void HiveStructureBody::loadPostProcess( void )
{
// extend parent class
StructureBody::loadPostProcess();
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: ImmortalBody.cpp ////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, April 2002
// Desc: Just like Active Body, but won't let health drop below 1
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameLogic/Object.h"
#include "GameLogic/Damage.h"
#include "GameLogic/Module/ImmortalBody.h"
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ImmortalBody::ImmortalBody( Thing *thing, const ModuleData* moduleData )
: ActiveBody( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ImmortalBody::~ImmortalBody( void )
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void ImmortalBody::internalChangeHealth( Real delta )
{
// 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 );
// nothing -- never mark it as dead.
DEBUG_ASSERTCRASH( (getHealth() > 0 && !getObject()->isEffectivelyDead() ), ("Immortal objects should never get marked as dead!"));
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void ImmortalBody::crc( Xfer *xfer )
{
// extend base class
ActiveBody::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void ImmortalBody::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
ActiveBody::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void ImmortalBody::loadPostProcess( void )
{
// extend base class
ActiveBody::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,205 @@
/*
** 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: InactiveBody.cpp /////////////////////////////////////////////////////////////////////////
// Author: Colin Day, November 2001
// Desc: An Inactive body module doesn't have any of the data storage for
// health and damage etc ... it's an "Inactive" object that isn't
// affected by matters of the body ... it's all in the mind!!!!
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/InactiveBody.h"
#include "GameLogic/Module/DieModule.h"
#include "GameLogic/Damage.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
InactiveBody::InactiveBody( Thing *thing, const ModuleData* moduleData )
: BodyModule( thing, moduleData ), m_dieCalled(false)
{
getObject()->setEffectivelyDead(true);
} // end InactiveBody
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
InactiveBody::~InactiveBody( void )
{
} // end ~InactiveBody
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Real InactiveBody::estimateDamage( DamageInfoInput& damageInfo ) const
{
// Inactive bodies have no health so no damage can really be done
Real amount = 0.0f;
// exception!
if (damageInfo.m_damageType == DAMAGE_UNRESISTABLE)
{
amount = damageInfo.m_amount;
}
return amount;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void InactiveBody::attemptDamage( DamageInfo *damageInfo )
{
if( damageInfo == NULL )
return;
if( damageInfo->in.m_damageType == DAMAGE_HEALING )
{
// Healing and Damage are separate, so this shouldn't happen
attemptHealing( damageInfo );
return;
}
// Inactive bodies have no health so no damage can really be done
damageInfo->out.m_actualDamageDealt = 0.0f;
damageInfo->out.m_actualDamageClipped = 0.0f;
damageInfo->out.m_noEffect = true;
// exception: damage type KILL always wipes us out
if (damageInfo->in.m_damageType == DAMAGE_UNRESISTABLE)
{
DEBUG_ASSERTCRASH(!getObject()->getTemplate()->isPrerequisite(), ("Prerequisites should not have InactiveBody"));
damageInfo->out.m_noEffect = false;
// since we have no Health, we do not call DamageModules, nor do DamageFX.
// however, we DO process DieModules.
if (!m_dieCalled)
{
getObject()->onDie( damageInfo );
m_dieCalled = true;
}
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void InactiveBody::attemptHealing( DamageInfo *damageInfo )
{
if( damageInfo == NULL )
return;
if( damageInfo->in.m_damageType != DAMAGE_HEALING )
{
// Healing and Damage are separate, so this shouldn't happen
attemptDamage( damageInfo );
return;
}
// Inactive bodies have no health so no damage can really be done
damageInfo->out.m_actualDamageDealt = 0.0f;
damageInfo->out.m_actualDamageClipped = 0.0f;
damageInfo->out.m_noEffect = true;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void InactiveBody::internalChangeHealth( Real delta )
{
// Inactive bodies have no health to increase or decrease
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Real InactiveBody::getHealth() const
{
// Inactive bodies have no health to get
return 0.0f;
} // end getHealth
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
BodyDamageType InactiveBody::getDamageState() const
{
return BODY_PRISTINE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void InactiveBody::setDamageState( BodyDamageType ) ///< control damage state directly. Will adjust hitpoints.
{
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void InactiveBody::crc( Xfer *xfer )
{
// extend base class
BodyModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void InactiveBody::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// base class
BodyModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void InactiveBody::loadPostProcess( void )
{
// extend base class
BodyModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -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: StructureBody.cpp ////////////////////////////////////////////////////////////////////////
// Author: Colin Day, November 2001
// Desc: Structure bodies are active bodies specifically for structures that are built
// and/or interactable (is that a world) with the player.
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameLogic/Object.h"
#include "GameLogic/Damage.h"
#include "GameLogic/Module/StructureBody.h"
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
StructureBody::StructureBody( Thing *thing, const ModuleData* moduleData )
: ActiveBody( thing, moduleData )
{
m_constructorObjectID = INVALID_ID;
} // end StructureBody
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
StructureBody::~StructureBody( void )
{
} // end ~StructureBody
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void StructureBody::setConstructorObject( Object *obj )
{
if( obj )
m_constructorObjectID = obj->getID();
} // end setConstructorObject
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void StructureBody::crc( Xfer *xfer )
{
// extend base class
ActiveBody::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void StructureBody::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// base class
ActiveBody::xfer( xfer );
// constructor object id
xfer->xferObjectID( &m_constructorObjectID );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void StructureBody::loadPostProcess( void )
{
// extend base class
ActiveBody::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,187 @@
/*
** 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: UndeadBody.cpp ////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, June 2003
// Desc: First death is intercepted and sets flags and setMaxHealth. Second death is handled normally.
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameLogic/Module/UndeadBody.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/SlowDeathBehavior.h"
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
void UndeadBodyModuleData::buildFieldParse(MultiIniFieldParse& p)
{
ActiveBodyModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "SecondLifeMaxHealth", INI::parseReal, NULL, offsetof( UndeadBodyModuleData, m_secondLifeMaxHealth ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UndeadBodyModuleData::UndeadBodyModuleData()
{
m_secondLifeMaxHealth = 1;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UndeadBody::UndeadBody( Thing *thing, const ModuleData* moduleData )
: ActiveBody( thing, moduleData )
{
m_isSecondLife = FALSE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UndeadBody::~UndeadBody( void )
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void UndeadBody::attemptDamage( DamageInfo *damageInfo )
{
// If we are on our first life, see if this damage will kill us. If it will, bind it to one hitpoint
// remaining, then go ahead and take it.
Bool shouldStartSecondLife = FALSE;
if( damageInfo->in.m_damageType != DAMAGE_UNRESISTABLE
&& !m_isSecondLife
&& damageInfo->in.m_amount >= getHealth()
&& IsHealthDamagingDamage(damageInfo->in.m_damageType)
)
{
damageInfo->in.m_amount = min( damageInfo->in.m_amount, getHealth() - 1 );
shouldStartSecondLife = TRUE;
}
ActiveBody::attemptDamage(damageInfo);
// After we take it (which allows for damaging special effects), we will do our modifications to the body module
if( shouldStartSecondLife )
startSecondLife(damageInfo);
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void UndeadBody::startSecondLife(DamageInfo *damageInfo)
{
const UndeadBodyModuleData *data = getUndeadBodyModuleData();
// Flag module as no longer intercepting damage
m_isSecondLife = TRUE;
// Modify ActiveBody's max health and initial health
setMaxHealth(data->m_secondLifeMaxHealth, FULLY_HEAL);
// Set Armor set flag to use second life armor
setArmorSetFlag(ARMORSET_SECOND_LIFE);
// Fire the Slow Death module. The fact that this is not the result of an onDie will cause the special behavior
Int total = 0;
for( BehaviorModule** update = getObject()->getBehaviorModules(); *update; ++update )
{
SlowDeathBehaviorInterface* sdu = (*update)->getSlowDeathBehaviorInterface();
if (sdu != NULL && sdu->isDieApplicable(damageInfo) )
{
total += sdu->getProbabilityModifier( damageInfo );
}
}
DEBUG_ASSERTCRASH(total > 0, ("Hmm, this is wrong"));
// this returns a value from 1...total, inclusive
Int roll = GameLogicRandomValue(1, total);
for( update = getObject()->getBehaviorModules(); *update; ++update)
{
SlowDeathBehaviorInterface* sdu = (*update)->getSlowDeathBehaviorInterface();
if (sdu != NULL && sdu->isDieApplicable(damageInfo))
{
roll -= sdu->getProbabilityModifier( damageInfo );
if (roll <= 0)
{
sdu->beginSlowDeath(damageInfo);
return;
}
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void UndeadBody::crc( Xfer *xfer )
{
// extend base class
ActiveBody::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void UndeadBody::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
ActiveBody::xfer( xfer );
xfer->xferBool(&m_isSecondLife);
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void UndeadBody::loadPostProcess( void )
{
// extend base class
ActiveBody::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,71 @@
/*
** 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: CollideModule.cpp ////////////////////////////////////////////////////////////////////////
// Author: Colin Day, September 2002
// Desc: Collide module base class implementations
///////////////////////////////////////////////////////////////////////////////////////////////////
// INLCUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/CollideModule.h"
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void CollideModule::crc( Xfer *xfer )
{
// extend base class
BehaviorModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void CollideModule::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// call base class
BehaviorModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void CollideModule::loadPostProcess( void )
{
// call base class
BehaviorModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,200 @@
/*
** 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: ConvertToCarBombCrateCollide.cpp ///////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, March 2002
// Desc: A crate that gives a level of experience to all within n distance
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/Radar.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/FXList.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/ConvertToCarBombCrateCollide.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/ExperienceTracker.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ConvertToCarBombCrateCollide::ConvertToCarBombCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ConvertToCarBombCrateCollide::~ConvertToCarBombCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool ConvertToCarBombCrateCollide::isValidToExecute( const Object *other ) const
{
if( !CrateCollide::isValidToExecute(other) )
{
return FALSE;
}
if( other->isEffectivelyDead() )
{
return FALSE;
}
if( other->isKindOf( KINDOF_AIRCRAFT ) || other->isKindOf( KINDOF_BOAT ) )
{
//Can't make carbombs out of planes and boats!
return FALSE;
}
if( other->getStatusBits().test( OBJECT_STATUS_IS_CARBOMB ) )
{
return FALSE;// oops, sorry, I'll convert the next one.
}
// Check to see if this other object has a carbomb weapon set that isn't in use.
WeaponSetFlags flags;
flags.set( WEAPONSET_CARBOMB );
const WeaponTemplateSet* set = other->getTemplate()->findWeaponTemplateSet( flags );
if( !set )
{
//This unit has no weapon set!
return FALSE;
}
if( !set->testWeaponSetFlag( WEAPONSET_CARBOMB ) )
{
//This unit has a weaponset, but the best match code above chose a different
//weaponset.
return FALSE;
}
// Also make sure that the car isn't already a carbomb!
if( other->testWeaponSetFlag( WEAPONSET_CARBOMB ) )
{
return FALSE;
}
return TRUE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool ConvertToCarBombCrateCollide::executeCrateBehavior( Object *other )
{
//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.
//Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
Object *obj = getObject();
AIUpdateInterface* ai = obj->getAIUpdateInterface();
if (ai && ai->getGoalObject() != other)
return false;
if( other && other->checkAndDetonateBoobyTrap(obj) )
{
// Whoops, it was mined. Cancel if it or us is now dead.
if( other->isEffectivelyDead() || getObject()->isEffectivelyDead() )
{
return false;
}
}
other->setWeaponSetFlag( WEAPONSET_CARBOMB );
FXList::doFXObj( getConvertToCarBombCrateCollideModuleData()->m_fxList, other );
other->defect( getObject()->getControllingPlayer()->getDefaultTeam(), 0);
//In order to make things easier for the designers, we are going to transfer the terrorist name
//to the car... so the designer can control the car with their scripts.
TheScriptEngine->transferObjectName( getObject()->getName(), other );
//This is kinda special... we will endow our new ride with our vision and shroud range, since we are driving
other->setVisionRange(getObject()->getVisionRange());
other->setShroudClearingRange(getObject()->getShroudClearingRange());
other->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_IS_CARBOMB ) );
ExperienceTracker *exp = other->getExperienceTracker();
if (exp)
{
exp->setVeterancyLevel(obj->getExperienceTracker()->getVeterancyLevel());
}
TheRadar->removeObject( other );
TheRadar->addObject( other );
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void ConvertToCarBombCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void ConvertToCarBombCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void ConvertToCarBombCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,299 @@
/*
** 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: ConvertToHijackedVehicleCrateCollide.cpp
// Author: Mark Lorenzen, July 2002
// Desc: A crate (actually a terrorist - mobile crate) that makes the target vehicle switch
// sides, and kills its driver
// @todo Needs to set the science of that vehicle (dozer) so still can build same stuff as always
//
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/Radar.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/Module/AIUpdate.h"
#include "GameLogic/Module/ConvertToHijackedVehicleCrateCollide.h"
#include "GameLogic/Module/HijackerUpdate.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/Module/DozerAIUpdate.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ConvertToHijackedVehicleCrateCollide::ConvertToHijackedVehicleCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ConvertToHijackedVehicleCrateCollide::~ConvertToHijackedVehicleCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool ConvertToHijackedVehicleCrateCollide::isValidToExecute( const Object *other ) const
{
if( !CrateCollide::isValidToExecute(other) )
{
return FALSE;
}
if( other->isEffectivelyDead() )
{
return FALSE;// can't hijack a dead vehicle
}
if( other->isKindOf( KINDOF_IMMUNE_TO_CAPTURE ) )
{
return FALSE; //Kris: Patch 1.03 -- Prevent hijackers from being able to hijack battle buses.
}
if( other->isKindOf( KINDOF_AIRCRAFT ) || other->isKindOf( KINDOF_BOAT ) )
{
//Can't hijack planes and boats!
return FALSE;
}
if( other->isKindOf( KINDOF_DRONE ) )
{
//Can't hijack drones!
return FALSE;
}
if( other->getStatusBits().test( OBJECT_STATUS_HIJACKED ) )
{
return FALSE;// oops, sorry, I'll jack the next one.
}
Relationship r = getObject()->getRelationship( other );
//Only hijack enemy objects
if( r != ENEMIES )
{
return FALSE;
}
if( other->isKindOf( KINDOF_TRANSPORT ) )
{
//Kris: Allow empty transports to be hijacked.
if( other->getContain() && other->getContain()->getContainCount() > 0 )
{
return FALSE;// dustin sez: do not jack vehicles that may carry hostile passengers
}
}
//Kris: Make sure you can't hijack any aircraft (or hijack-enter).
if( other->isKindOf( KINDOF_AIRCRAFT ) )
{
return FALSE;
}
//VeterancyLevel veterancyLevel = other->getVeterancyLevel();
//if( veterancyLevel >= LEVEL_ELITE )
//{
// return FALSE;
//}
return TRUE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool ConvertToHijackedVehicleCrateCollide::executeCrateBehavior( Object *other )
{
//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.
//Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
Object *obj = getObject();
AIUpdateInterface* ai = obj->getAIUpdateInterface();
if (ai && ai->getGoalObject() != other)
{
return false;
}
TheRadar->tryInfiltrationEvent( other );
//Before the actual defection takes place, play the "vehicle stolen" EVA
//event if the local player is the victim!
if( other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_VehicleStolen );
}
other->setTeam( obj->getControllingPlayer()->getDefaultTeam() );
other->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_HIJACKED ) );// I claim this car in the name of the GLA
AIUpdateInterface* targetAI = other->getAIUpdateInterface();
targetAI->aiMoveToPosition( other->getPosition(), CMD_FROM_AI );
targetAI->aiIdle( CMD_FROM_AI );
//Just in case this target is a dozer, lets make him stop al his dozer tasks, like building and repairing,
//So the previous owner does not benefit from these tasks
DozerAIInterface * dozerAI = targetAI->getDozerAIInterface();
if ( dozerAI )
{
for (UnsignedInt task = DOZER_TASK_FIRST; task < DOZER_NUM_TASKS; ++task)
{
dozerAI->cancelTask( (DozerTask)task );
}
}
AudioEventRTS hijackEvent( "HijackDriver", obj->getID() );
TheAudio->addAudioEvent( &hijackEvent );
//In order to make things easier for the designers, we are going to transfer the hijacker's name
//to the car... so the designer can control the car with their scripts.
TheScriptEngine->transferObjectName( obj->getName(), other );
ExperienceTracker *targetExp = other->getExperienceTracker();
ExperienceTracker *jackerExp = obj->getExperienceTracker();
if ( targetExp && jackerExp )
{
VeterancyLevel highestLevel = MAX(targetExp->getVeterancyLevel(),jackerExp->getVeterancyLevel());
jackerExp->setVeterancyLevel( highestLevel, FALSE );
targetExp->setVeterancyLevel( highestLevel, FALSE );
}
Bool targetCanEject = FALSE;
BehaviorModule **dmi = NULL;
for( dmi = other->getBehaviorModules(); *dmi; ++dmi )
{
if( (*dmi)->getEjectPilotDieInterface() )
{
targetCanEject = TRUE;
break;
}
} // end for dmi
if ( ! targetCanEject )
{
TheGameLogic->destroyObject( obj );
return TRUE;
}
// I we have made it this far, we are going to ride in this vehicle for a while
// get the name of the hijackerupdate
static NameKeyType key_HijackerUpdate = NAMEKEY( "HijackerUpdate" );
HijackerUpdate *hijackerUpdate = (HijackerUpdate*)obj->findUpdateModule( key_HijackerUpdate );
if( hijackerUpdate )
{
hijackerUpdate->setTargetObject( other );
hijackerUpdate->setIsInVehicle( TRUE );
hijackerUpdate->setUpdate( TRUE );
// flag bits so hijacker won't be selectible or collideable
//while within the vehicle
obj->setStatus( MAKE_OBJECT_STATUS_MASK3( OBJECT_STATUS_NO_COLLISIONS, OBJECT_STATUS_MASKED, OBJECT_STATUS_UNSELECTABLE ) );
}
// THIS BLOCK HIDES THE HIJACKER AND REMOVES HIM FROM PARTITION MANAGER
// remove object from its group (if any)
obj->leaveGroup();
if( ai )
{
//By setting him to idle, we will prevent him from entering the target after this gets called.
ai->aiIdle( CMD_FROM_AI );
}
//This is kinda special... we will endow our new ride with our vision and shroud range, since we are driving
other->setVisionRange(getObject()->getVisionRange());
other->setShroudClearingRange(getObject()->getShroudClearingRange());
// remove rider from partition manager
ThePartitionManager->unRegisterObject( obj );
// hide the drawable associated with rider
if( obj->getDrawable() )
obj->getDrawable()->setDrawableHidden( true );
// By returning FALSE, we will not remove the object (Hijacker)
return FALSE;
// return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void ConvertToHijackedVehicleCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void ConvertToHijackedVehicleCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void ConvertToHijackedVehicleCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,284 @@
/*
** 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: CrateCollide.cpp ///////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, March 2002
// Desc: Abstract base Class Crate Collide
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/BitFlagsIO.h"
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "Common/GameAudio.h"
#include "Common/MiscAudio.h"
#include "GameClient/Anim2D.h"
#include "GameClient/FXList.h"
#include "GameClient/InGameUI.h"
#include "GameClient/Drawable.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/CrateCollide.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CrateCollideModuleData::CrateCollideModuleData()
{
m_isForbidOwnerPlayer = FALSE;
m_executeAnimationDisplayTimeInSeconds = 0.0f;
m_executeAnimationZRisePerSecond = 0.0f;
m_executeAnimationFades = TRUE;
m_isBuildingPickup = FALSE;
m_isHumanOnlyPickup = FALSE;
m_executeFX = NULL;
m_pickupScience = SCIENCE_INVALID;
// Added By Sadullah Nader
// Initializations missing and needed
m_executionAnimationTemplate = AsciiString::TheEmptyString;
// End Add
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void CrateCollideModuleData::buildFieldParse(MultiIniFieldParse& p)
{
ModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "RequiredKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( CrateCollideModuleData, m_kindof ) },
{ "ForbiddenKindOf", KindOfMaskType::parseFromINI, NULL, offsetof( CrateCollideModuleData, m_kindofnot ) },
{ "ForbidOwnerPlayer", INI::parseBool, NULL, offsetof( CrateCollideModuleData, m_isForbidOwnerPlayer ) },
{ "BuildingPickup", INI::parseBool, NULL, offsetof( CrateCollideModuleData, m_isBuildingPickup ) },
{ "HumanOnly", INI::parseBool, NULL, offsetof( CrateCollideModuleData, m_isHumanOnlyPickup ) },
{ "PickupScience", INI::parseScience, NULL, offsetof( CrateCollideModuleData, m_pickupScience ) },
{ "ExecuteFX", INI::parseFXList, NULL, offsetof( CrateCollideModuleData, m_executeFX ) },
{ "ExecuteAnimation", INI::parseAsciiString, NULL, offsetof( CrateCollideModuleData, m_executionAnimationTemplate ) },
{ "ExecuteAnimationTime", INI::parseReal, NULL, offsetof( CrateCollideModuleData, m_executeAnimationDisplayTimeInSeconds ) },
{ "ExecuteAnimationZRise", INI::parseReal, NULL, offsetof( CrateCollideModuleData, m_executeAnimationZRisePerSecond ) },
{ "ExecuteAnimationFades", INI::parseBool, NULL, offsetof( CrateCollideModuleData, m_executeAnimationFades ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CrateCollide::CrateCollide( Thing *thing, const ModuleData* moduleData ) : CollideModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CrateCollide::~CrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
/** The collide event.
* Note that when other is NULL it means "collide with ground" */
//-------------------------------------------------------------------------------------------------
void CrateCollide::onCollide( Object *other, const Coord3D *, const Coord3D * )
{
const CrateCollideModuleData *modData = getCrateCollideModuleData();
// If the crate can be picked up, perform the game logic and destroy the crate.
if( isValidToExecute( other ) )
{
if( executeCrateBehavior( other ) )
{
if( modData->m_executeFX != NULL )
{
// Note: We pass in other here, because the crate is owned by the neutral player, and
// we want to do things that only the other person can see.
FXList::doFXObj( modData->m_executeFX, other );
}
TheGameLogic->destroyObject( getObject() );
}
// play animation in the world at this spot if there is one
if( TheAnim2DCollection && modData->m_executionAnimationTemplate.isEmpty() == FALSE && TheGameLogic->getDrawIconUI() )
{
Anim2DTemplate *animTemplate = TheAnim2DCollection->findTemplate( modData->m_executionAnimationTemplate );
TheInGameUI->addWorldAnimation( animTemplate,
getObject()->getPosition(),
WORLD_ANIM_FADE_ON_EXPIRE,
modData->m_executeAnimationDisplayTimeInSeconds,
modData->m_executeAnimationZRisePerSecond );
}
}
}
//-------------------------------------------------------------------------------------------------
Bool CrateCollide::isValidToExecute( const Object *other ) const
{
//The ground never picks up a crate
if( other == NULL )
return FALSE;
//Nothing Neutral can pick up any type of crate
if( other->isNeutralControlled() )
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
if( other->getAIUpdateInterface() == NULL && !validBuildingAttempt )// Building exception flag for Drop Zone
return FALSE;
// must match our kindof flags (if any)
if (md && !other->isKindOfMulti(md->m_kindof, md->m_kindofnot))
return FALSE;
if( other->isEffectivelyDead() )
return FALSE;
// crates cannot be claimed while in the air, except by buildings
if( getObject()->isAboveTerrain() && !validBuildingAttempt )
return FALSE;
if( md->m_isForbidOwnerPlayer && (getObject()->getControllingPlayer() == other->getControllingPlayer()) )
return FALSE; // Design has decreed this to not be picked up by the dead guy's team.
if( md->m_isHumanOnlyPickup && other->getControllingPlayer() && (other->getControllingPlayer()->getPlayerType() != PLAYER_HUMAN) )
return FALSE; // Human only mission crate
if( (md->m_pickupScience != SCIENCE_INVALID) && other->getControllingPlayer() && !other->getControllingPlayer()->hasScience(md->m_pickupScience) )
return FALSE; // Science required to pick this up
if( other->isKindOf( KINDOF_PARACHUTE ) )
return FALSE;
return TRUE;
}
void CrateCollide::doSabotageFeedbackFX( const Object *other, SabotageVictimType type )
{
if ( ! getObject() )
return;
if ( ! other )
return;
AudioEventRTS soundToPlay;
switch ( type )
{
case CrateCollide::SAB_VICTIM_FAKE_BUILDING:
{
return; // THIS NEEDS NO ADD'L FEEDBACK
}
case CrateCollide::SAB_VICTIM_COMMAND_CENTER:
case CrateCollide::SAB_VICTIM_SUPERWEAPON:
{
soundToPlay = TheAudio->getMiscAudio()->m_sabotageResetTimerBuilding;
break;
}
case CrateCollide::SAB_VICTIM_DROP_ZONE:
case CrateCollide::SAB_VICTIM_SUPPLY_CENTER:
{
soundToPlay = TheAudio->getMiscAudio()->m_moneyWithdrawSound;
break;
}
case CrateCollide::SAB_VICTIM_INTERNET_CENTER:
case CrateCollide::SAB_VICTIM_MILITARY_FACTORY:
case CrateCollide::SAB_VICTIM_POWER_PLANT:
default:
{
soundToPlay = TheAudio->getMiscAudio()->m_sabotageShutDownBuilding;
break;
}
}
soundToPlay.setPosition( other->getPosition() );
TheAudio->addAudioEvent( &soundToPlay );
Drawable *draw = other->getDrawable();
if ( draw )
draw->flashAsSelected();
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void CrateCollide::crc( Xfer *xfer )
{
// extend base class
CollideModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void CrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CollideModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void CrateCollide::loadPostProcess( void )
{
// extend base class
CollideModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: MoneyCrateCollide.cpp ///////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, March 2002
// Desc: A crate that heals everything owned by the collider
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/AudioEventRTS.h"
#include "Common/MiscAudio.h"
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/HealCrateCollide.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
HealCrateCollide::HealCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
HealCrateCollide::~HealCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
Bool HealCrateCollide::executeCrateBehavior( Object *other )
{
Player* cratePlayer = other->getControllingPlayer();
cratePlayer->healAllObjects();
//Play a crate pickup sound.
AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_crateHeal;
soundToPlay.setPosition( other->getPosition() );
TheAudio->addAudioEvent(&soundToPlay);
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void HealCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void HealCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void HealCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,135 @@
/*
** 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: MoneyCrateCollide.cpp ///////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, March 2002
// Desc: A crate that gives x money to the collider
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/AudioEventRTS.h"
#include "Common/MiscAudio.h"
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/MoneyCrateCollide.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
MoneyCrateCollide::MoneyCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
MoneyCrateCollide::~MoneyCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
Bool MoneyCrateCollide::executeCrateBehavior( Object *other )
{
UnsignedInt money = getMoneyCrateCollideModuleData()->m_moneyProvided;
money += getUpgradedSupplyBoost(other);
other->getControllingPlayer()->getMoney()->deposit( money );
other->getControllingPlayer()->getScoreKeeper()->addMoneyEarned( money );
//Play a crate pickup sound.
AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_crateMoney;
soundToPlay.setObjectID( other->getID() );
TheAudio->addAudioEvent(&soundToPlay);
return TRUE;
}
//------------------------------------------------------------------------------------------------
Int MoneyCrateCollide::getUpgradedSupplyBoost( Object *other ) const
{
Player *player = other->getControllingPlayer();
if (!player) return 0;
// Loop through the upgrade pairs and see if an upgrade is present that adds supply boost
std::list<upgradePair>::const_iterator it = getMoneyCrateCollideModuleData()->m_upgradeBoost.begin();
while (it != getMoneyCrateCollideModuleData()->m_upgradeBoost.end())
{
upgradePair info = *it;
// Check if the player has the desired upgrade. If so return the boost
static const UpgradeTemplate *upgradeTemplate = TheUpgradeCenter->findUpgrade( info.type.c_str() );
if (player && upgradeTemplate && player->hasUpgradeComplete(upgradeTemplate))
{
return info.amount;
}
// check next
++it;
}
return 0;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void MoneyCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void MoneyCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void MoneyCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,193 @@
/*
** 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: SabotageCommandCenterCrateCollide.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/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/SabotageCommandCenterCrateCollide.h"
#include "GameLogic/Module/SpecialPowerModule.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageCommandCenterCrateCollide::SabotageCommandCenterCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageCommandCenterCrateCollide::~SabotageCommandCenterCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageCommandCenterCrateCollide::isValidToExecute( const Object *other ) const
{
if( !CrateCollide::isValidToExecute(other) )
{
//Extend functionality.
return FALSE;
}
if( other->isEffectivelyDead() )
{
//Can't sabotage dead structures
return FALSE;
}
if( !other->isKindOf( KINDOF_COMMANDCENTER ) )
{
//We can only sabotage superweapon structures.
return FALSE;
}
Relationship r = getObject()->getRelationship( other );
if( r != ENEMIES )
{
//Can only sabotage enemy buildings.
return FALSE;
}
return TRUE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageCommandCenterCrateCollide::executeCrateBehavior( Object *other )
{
//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.
//Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
Object *obj = getObject();
AIUpdateInterface* ai = obj->getAIUpdateInterface();
if (ai && ai->getGoalObject() != other)
{
return false;
}
TheRadar->tryInfiltrationEvent( other );
doSabotageFeedbackFX( other, CrateCollide::SAB_VICTIM_COMMAND_CENTER );
//When the sabotage occurs, play the appropriate EVA
//event if the local player is the victim!
if( other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_BuildingSabotaged );
}
//Reset ALL special powers!
for( BehaviorModule **m = other->getBehaviorModules(); *m; ++m )
{
SpecialPowerModuleInterface* sp = (*m)->getSpecialPower();
if( !sp )
{
continue;
}
sp->startPowerRecharge();
}
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SabotageCommandCenterCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SabotageCommandCenterCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SabotageCommandCenterCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,188 @@
/*
** 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: SabotageFakeBuildingCrateCollide.cpp
// Author: Kris Morness, July 2003
// Desc: A crate (actually a saboteur - mobile crate) that destroys a fake building.
//
///////////////////////////////////////////////////////////////////////////////////////////////////
// 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/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/Module/AIUpdate.h"
#include "GameLogic/Module/SabotageFakeBuildingCrateCollide.h"
#include "GameLogic/Module/HijackerUpdate.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/Module/DozerAIUpdate.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageFakeBuildingCrateCollide::SabotageFakeBuildingCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageFakeBuildingCrateCollide::~SabotageFakeBuildingCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageFakeBuildingCrateCollide::isValidToExecute( const Object *other ) const
{
if( !CrateCollide::isValidToExecute(other) )
{
//Extend functionality.
return FALSE;
}
if( other->isEffectivelyDead() )
{
//Can't sabotage dead structures
return FALSE;
}
if( !other->isKindOf( KINDOF_FS_FAKE ) )
{
//We can only sabotage fake structures.
return FALSE;
}
Relationship r = getObject()->getRelationship( other );
if( r != ENEMIES )
{
//Can only sabotage enemy buildings.
return FALSE;
}
return TRUE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageFakeBuildingCrateCollide::executeCrateBehavior( Object *other )
{
//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.
//Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
Object *obj = getObject();
AIUpdateInterface* ai = obj->getAIUpdateInterface();
if (ai && ai->getGoalObject() != other)
{
return false;
}
TheRadar->tryInfiltrationEvent( other );
doSabotageFeedbackFX( other, CrateCollide::SAB_VICTIM_FAKE_BUILDING );
//When the sabotage occurs, play the appropriate EVA
//event if the local player is the victim!
if( other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_BuildingSabotaged );
}
Player *player = other->getControllingPlayer();
if( player )
{
DamageInfo damageInfo;
damageInfo.in.m_damageType = DAMAGE_UNRESISTABLE;
damageInfo.in.m_deathType = DEATH_DETONATED;
damageInfo.in.m_sourceID = obj->getID();
damageInfo.in.m_amount = other->getBodyModule()->getMaxHealth();
other->attemptDamage( &damageInfo );
}
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SabotageFakeBuildingCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SabotageFakeBuildingCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SabotageFakeBuildingCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,231 @@
/*
** 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: SabotageInternetCenterCrateCollide.cpp
// Author: Kris Morness, July 2003
// Desc: A crate (actually a saboteur - mobile crate) that temporarily disables an internet center
//
///////////////////////////////////////////////////////////////////////////////////////////////////
// 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/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/Eva.h"
#include "GameClient/GameText.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/BehaviorModule.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Module/DozerAIUpdate.h"
#include "GameLogic/Module/HijackerUpdate.h"
#include "GameLogic/Module/OCLUpdate.h"
#include "GameLogic/Module/SabotageInternetCenterCrateCollide.h"
#include "GameLogic/Module/SpyVisionUpdate.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageInternetCenterCrateCollide::SabotageInternetCenterCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageInternetCenterCrateCollide::~SabotageInternetCenterCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageInternetCenterCrateCollide::isValidToExecute( const Object *other ) const
{
if( !CrateCollide::isValidToExecute(other) )
{
//Extend functionality.
return FALSE;
}
if( other->isEffectivelyDead() )
{
//Can't sabotage dead structures
return FALSE;
}
if( !other->isKindOf( KINDOF_FS_INTERNET_CENTER ) )
{
//We can only sabotage supply dropzones.
return FALSE;
}
Relationship r = getObject()->getRelationship( other );
if( r != ENEMIES )
{
//Can only sabotage enemy buildings.
return FALSE;
}
return TRUE;
}
static void disableHacker( Object *obj, void *userData )
{
UnsignedInt frame = (UnsignedInt)userData;
if( obj )
{
obj->setDisabledUntil( DISABLED_HACKED, frame );
}
}
static void disableInternetCenterSpyVision( Object *obj, void *userData )
{
if( obj && obj->isKindOf( KINDOF_FS_INTERNET_CENTER ) )
{
UnsignedInt frame = (UnsignedInt)userData;
//Loop through all it's SpyVisionUpdates() and wake them all up so they can be shut down. This is weird because
//it's one of the few update modules that is actually properly sleepified.
for( BehaviorModule** u = obj->getBehaviorModules(); *u; ++u )
{
SpyVisionUpdate *svUpdate = (*u)->getSpyVisionUpdate();
if( svUpdate )
{
//Turn off the vision temporarily. When it recovers from being disabled, the
//timer will need to start over from scratch so it won't come on right away unless
//it's a permanent spy vision.
svUpdate->setDisabledUntilFrame( frame );
}
}
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageInternetCenterCrateCollide::executeCrateBehavior( Object *other )
{
//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.
//Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
Object *obj = getObject();
AIUpdateInterface* ai = obj->getAIUpdateInterface();
if (ai && ai->getGoalObject() != other)
{
return false;
}
TheRadar->tryInfiltrationEvent( other );
doSabotageFeedbackFX( other, CrateCollide::SAB_VICTIM_INTERNET_CENTER );
if( other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_BuildingSabotaged );
}
//Loop through every internet center to temporarily disable the spy vision upgrades.
UnsignedInt frame = TheGameLogic->getFrame() + getSabotageInternetCenterCrateCollideModuleData()->m_sabotageFrames;
//Disable all internet center spy visions (they stack) without visually disabling the other centers.
//Kris: Note -- Design has changed that we only can have one center at a time... logically, this code
//doesn't need to change.
// This loop goes before the Disabled_Hacked one since that will use the normal disabled code with its cool timers.
// This loop is for the other centers, but it hits the main one too.
other->getControllingPlayer()->iterateObjects( disableInternetCenterSpyVision, (void*)frame );
//Disable the internet center. Note this is purely fluff... because the spy vision update will still run even
//though we are disabling it. We have to disable the spyvision updates manually because other centers need to
//be turned off too but without visually disabling them. Yikes!
other->setDisabledUntil( DISABLED_HACKED, frame );
//Disable all the hackers inside.
ContainModuleInterface *contain = other->getContain();
contain->iterateContained( disableHacker, (void*)frame, FALSE );
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SabotageInternetCenterCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SabotageInternetCenterCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SabotageInternetCenterCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,188 @@
/*
** 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: SabotageMilitaryFactoryCrateCollide.cpp
// Author: Kris Morness, June 2003
// Desc: A crate (actually a saboteur - mobile crate) that steals cash from the target supply center.
//
///////////////////////////////////////////////////////////////////////////////////////////////////
// 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/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/Eva.h"
#include "GameClient/GameText.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/SabotageMilitaryFactoryCrateCollide.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageMilitaryFactoryCrateCollide::SabotageMilitaryFactoryCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageMilitaryFactoryCrateCollide::~SabotageMilitaryFactoryCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageMilitaryFactoryCrateCollide::isValidToExecute( const Object *other ) const
{
if( !CrateCollide::isValidToExecute(other) )
{
//Extend functionality.
return FALSE;
}
if( other->isEffectivelyDead() )
{
//Can't sabotage dead structures
return FALSE;
}
if( other->isKindOf( KINDOF_AIRCRAFT_CARRIER ) )
{
//Can't sabotage that!
return FALSE;
}
if( !other->isKindOf( KINDOF_FS_BARRACKS ) && !other->isKindOf( KINDOF_FS_WARFACTORY ) && !other->isKindOf( KINDOF_FS_AIRFIELD ) )
{
//We can only sabotage military factory buildings.
return FALSE;
}
Relationship r = getObject()->getRelationship( other );
if( r != ENEMIES )
{
//Can only sabotage enemy buildings.
return FALSE;
}
return TRUE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageMilitaryFactoryCrateCollide::executeCrateBehavior( Object *other )
{
//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.
//Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
Object *obj = getObject();
AIUpdateInterface* ai = obj->getAIUpdateInterface();
if (ai && ai->getGoalObject() != other)
{
return false;
}
TheRadar->tryInfiltrationEvent( other );
doSabotageFeedbackFX( other, CrateCollide::SAB_VICTIM_MILITARY_FACTORY );
if( other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_BuildingSabotaged );
}
UnsignedInt frame = TheGameLogic->getFrame() + getSabotageMilitaryFactoryCrateCollideModuleData()->m_sabotageFrames;
other->setDisabledUntil( DISABLED_HACKED, frame );
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SabotageMilitaryFactoryCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SabotageMilitaryFactoryCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SabotageMilitaryFactoryCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,190 @@
/*
** 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: SabotagePowerPlantCrateCollide.cpp
// Author: Kris Morness, June 2003
// Desc: A crate (actually a saboteur - mobile crate) that makes the target powerplant lose power
//
///////////////////////////////////////////////////////////////////////////////////////////////////
// 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/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/Module/AIUpdate.h"
#include "GameLogic/Module/SabotagePowerPlantCrateCollide.h"
#include "GameLogic/Module/HijackerUpdate.h"
#include "GameLogic/Module/ContainModule.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/Module/DozerAIUpdate.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotagePowerPlantCrateCollide::SabotagePowerPlantCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotagePowerPlantCrateCollide::~SabotagePowerPlantCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotagePowerPlantCrateCollide::isValidToExecute( const Object *other ) const
{
if( !CrateCollide::isValidToExecute(other) )
{
//Extend functionality.
return FALSE;
}
if( other->isEffectivelyDead() )
{
//Can't sabotage dead structures
return FALSE;
}
if( !other->isKindOf( KINDOF_FS_POWER ) )
{
//We can only sabotage power plants.
return FALSE;
}
Relationship r = getObject()->getRelationship( other );
if( r != ENEMIES )
{
//Can only sabotage enemy buildings.
return FALSE;
}
return TRUE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotagePowerPlantCrateCollide::executeCrateBehavior( Object *other )
{
//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.
//Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
Object *obj = getObject();
AIUpdateInterface* ai = obj->getAIUpdateInterface();
if (ai && ai->getGoalObject() != other)
{
return false;
}
TheRadar->tryInfiltrationEvent( other );
doSabotageFeedbackFX( other, CrateCollide::SAB_VICTIM_POWER_PLANT );
//When the sabotage occurs, play the appropriate EVA
//event if the local player is the victim!
if( other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_BuildingSabotaged );
}
Player *player = other->getControllingPlayer();
if( player )
{
//Set the duration inside the player's energy class to record the length of the power outage.
UnsignedInt frame = TheGameLogic->getFrame() + getSabotagePowerPlantCrateCollideModuleData()->m_powerSabotageFrames;
player->getEnergy()->setPowerSabotagedTillFrame( frame );
//Trigger the callback function that will turn everything off.
player->onPowerBrownOutChange( TRUE );
//Note: Player::update() will check to turn it back on again once the timer expires.
}
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SabotagePowerPlantCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SabotagePowerPlantCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SabotagePowerPlantCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,193 @@
/*
** 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: SabotageSuperweaponCrateCollide.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/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/SabotageSuperweaponCrateCollide.h"
#include "GameLogic/Module/SpecialPowerModule.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageSuperweaponCrateCollide::SabotageSuperweaponCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageSuperweaponCrateCollide::~SabotageSuperweaponCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageSuperweaponCrateCollide::isValidToExecute( const Object *other ) const
{
if( !CrateCollide::isValidToExecute(other) )
{
//Extend functionality.
return FALSE;
}
if( other->isEffectivelyDead() )
{
//Can't sabotage dead structures
return FALSE;
}
if( !other->isKindOf( KINDOF_FS_SUPERWEAPON ) && !other->isKindOf( KINDOF_FS_STRATEGY_CENTER ) )
{
//We can only sabotage superweapon structures.
return FALSE;
}
Relationship r = getObject()->getRelationship( other );
if( r != ENEMIES )
{
//Can only sabotage enemy buildings.
return FALSE;
}
return TRUE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageSuperweaponCrateCollide::executeCrateBehavior( Object *other )
{
//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.
//Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
Object *obj = getObject();
AIUpdateInterface* ai = obj->getAIUpdateInterface();
if (ai && ai->getGoalObject() != other)
{
return false;
}
TheRadar->tryInfiltrationEvent( other );
doSabotageFeedbackFX( other, CrateCollide::SAB_VICTIM_SUPERWEAPON );
//When the sabotage occurs, play the appropriate EVA
//event if the local player is the victim!
if( other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_BuildingSabotaged );
}
//Reset ALL special powers!
for( BehaviorModule **m = other->getBehaviorModules(); *m; ++m )
{
SpecialPowerModuleInterface* sp = (*m)->getSpecialPower();
if( !sp )
{
continue;
}
sp->startPowerRecharge();
}
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SabotageSuperweaponCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SabotageSuperweaponCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SabotageSuperweaponCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,221 @@
/*
** 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: SabotageSupplyCenterCrateCollide.cpp
// Author: Kris Morness, June 2003
// Desc: A crate (actually a saboteur - mobile crate) that steals cash from the target supply center.
//
///////////////////////////////////////////////////////////////////////////////////////////////////
// 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/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/Eva.h"
#include "GameClient/GameText.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/SabotageSupplyCenterCrateCollide.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageSupplyCenterCrateCollide::SabotageSupplyCenterCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageSupplyCenterCrateCollide::~SabotageSupplyCenterCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageSupplyCenterCrateCollide::isValidToExecute( const Object *other ) const
{
if( !CrateCollide::isValidToExecute(other) )
{
//Extend functionality.
return FALSE;
}
if( other->isEffectivelyDead() )
{
//Can't sabotage dead structures
return FALSE;
}
if( !other->isKindOf( KINDOF_FS_SUPPLY_CENTER ) )
{
//We can only sabotage supply dropzones.
return FALSE;
}
Relationship r = getObject()->getRelationship( other );
if( r != ENEMIES )
{
//Can only sabotage enemy buildings.
return FALSE;
}
return TRUE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageSupplyCenterCrateCollide::executeCrateBehavior( Object *other )
{
//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.
//Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
Object *obj = getObject();
AIUpdateInterface* ai = obj->getAIUpdateInterface();
if (ai && ai->getGoalObject() != other)
{
return false;
}
TheRadar->tryInfiltrationEvent( other );
doSabotageFeedbackFX( other, CrateCollide::SAB_VICTIM_SUPPLY_CENTER );
//Steal cash!
Money *targetMoney = other->getControllingPlayer()->getMoney();
Money *objectMoney = obj->getControllingPlayer()->getMoney();
if( targetMoney && objectMoney )
{
UnsignedInt cash = targetMoney->countMoney();
UnsignedInt desiredAmount = getSabotageSupplyCenterCrateCollideModuleData()->m_stealCashAmount;
//Check to see if they have the cash, otherwise, take the remainder!
cash = min( desiredAmount, cash );
if( cash > 0 )
{
//Steal the cash
targetMoney->withdraw( cash );
objectMoney->deposit( cash );
Player* controller = obj->getControllingPlayer();
if (controller)
controller->getScoreKeeper()->addMoneyEarned( cash );
//Play the "cash stolen" EVA event if the local player is the victim!
if( other && other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_CashStolen );
}
//Display cash income floating over the about to be deleted saboteur.
UnicodeString moneyString;
moneyString.format( TheGameText->fetch( "GUI:AddCash" ), cash );
Coord3D pos;
pos.set( obj->getPosition() );
pos.z += 20.0f; //add a little z to make it show up above the unit.
TheInGameUI->addFloatingText( moneyString, &pos, GameMakeColor( 0, 255, 0, 255 ) );
//Display cash lost floating over the target
moneyString.format( TheGameText->fetch( "GUI:LoseCash" ), cash );
pos.set( other->getPosition() );
pos.z += 30.0f; //add a little z to make it show up above the unit.
TheInGameUI->addFloatingText( moneyString, &pos, GameMakeColor( 255, 0, 0, 255 ) );
}
else
{
if( other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_BuildingSabotaged );
}
}
}
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SabotageSupplyCenterCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SabotageSupplyCenterCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SabotageSupplyCenterCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,232 @@
/*
** 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: SabotageSupplyDropzoneCrateCollide.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/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/Eva.h"
#include "GameClient/GameText.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/SabotageSupplyDropzoneCrateCollide.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageSupplyDropzoneCrateCollide::SabotageSupplyDropzoneCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SabotageSupplyDropzoneCrateCollide::~SabotageSupplyDropzoneCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageSupplyDropzoneCrateCollide::isValidToExecute( const Object *other ) const
{
if( !CrateCollide::isValidToExecute(other) )
{
//Extend functionality.
return FALSE;
}
if( other->isEffectivelyDead() )
{
//Can't sabotage dead structures
return FALSE;
}
if( !other->isKindOf( KINDOF_FS_SUPPLY_DROPZONE ) )
{
//We can only sabotage supply dropzones.
return FALSE;
}
Relationship r = getObject()->getRelationship( other );
if( r != ENEMIES )
{
//Can only sabotage enemy buildings.
return FALSE;
}
return TRUE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool SabotageSupplyDropzoneCrateCollide::executeCrateBehavior( Object *other )
{
//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.
//Assume ai is valid because CrateCollide::isValidToExecute(other) checks it.
Object *obj = getObject();
AIUpdateInterface* ai = obj->getAIUpdateInterface();
if (ai && ai->getGoalObject() != other)
{
return false;
}
TheRadar->tryInfiltrationEvent( other );
doSabotageFeedbackFX( other, CrateCollide::SAB_VICTIM_DROP_ZONE );
//Reset the timer on the dropzone. Um... only the dropzone has an OCLUpdate and one, so
//we can "assume" it's going to be safe. Otherwise, we'll have to write code to search for
//a specific OCLUpdate.
static NameKeyType key_ocl = NAMEKEY( "OCLUpdate" );
OCLUpdate *update = (OCLUpdate*)other->findUpdateModule( key_ocl );
if( update )
{
update->resetTimer();
}
//Steal cash!
Money *targetMoney = other->getControllingPlayer()->getMoney();
Money *objectMoney = obj->getControllingPlayer()->getMoney();
if( targetMoney && objectMoney )
{
UnsignedInt cash = targetMoney->countMoney();
UnsignedInt desiredAmount = getSabotageSupplyDropzoneCrateCollideModuleData()->m_stealCashAmount;
//Check to see if they have the cash, otherwise, take the remainder!
cash = min( desiredAmount, cash );
if( cash > 0 )
{
//Steal the cash
targetMoney->withdraw( cash );
objectMoney->deposit( cash );
Player* controller = obj->getControllingPlayer();
if (controller)
controller->getScoreKeeper()->addMoneyEarned( cash );
//Play the "cash stolen" EVA event if the local player is the victim!
if( other && other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_CashStolen );
}
//Display cash income floating over the about to be deleted saboteur.
UnicodeString moneyString;
moneyString.format( TheGameText->fetch( "GUI:AddCash" ), cash );
Coord3D pos;
pos.set( obj->getPosition() );
pos.z += 20.0f; //add a little z to make it show up above the unit.
TheInGameUI->addFloatingText( moneyString, &pos, GameMakeColor( 0, 255, 0, 255 ) );
//Display cash lost floating over the target
moneyString.format( TheGameText->fetch( "GUI:LoseCash" ), cash );
pos.set( other->getPosition() );
pos.z += 30.0f; //add a little z to make it show up above the unit.
TheInGameUI->addFloatingText( moneyString, &pos, GameMakeColor( 255, 0, 0, 255 ) );
}
else
{
if( other->isLocallyControlled() )
{
TheEva->setShouldPlay( EVA_BuildingSabotaged );
}
}
}
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SabotageSupplyDropzoneCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SabotageSupplyDropzoneCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SabotageSupplyDropzoneCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,300 @@
/*
** 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: SalvageCrateCollide.cpp ///////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, March 2002
// Desc: The Savlage system can give a Weaponset bonus, a level, or money. Salvagers create them
// by killing marked units, and only WeaponSalvagers can get the WeaponSet bonus
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameLogic/Module/SalvageCrateCollide.h"
#include "Common/AudioEventRTS.h"
#include "Common/MiscAudio.h"
#include "Common/Kindof.h"
#include "Common/RandomValue.h"
#include "Common/Player.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/GameText.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Object.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SalvageCrateCollide::SalvageCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SalvageCrateCollide::~SalvageCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
Bool SalvageCrateCollide::isValidToExecute( const Object *other ) const
{
if( ! CrateCollide::isValidToExecute( other ) )
return FALSE;
// Only salvage units can pick up a Salvage crate
if( ! other->getTemplate()->isKindOf( KINDOF_SALVAGER ) )
return FALSE;
return TRUE;
}
//-------------------------------------------------------------------------------------------------
Bool SalvageCrateCollide::executeCrateBehavior( Object *other )
{
if( eligibleForArmorSet(other) )// No percent chance on this one, if you can get it, you get it.
{
doArmorSet(other);
//Play the salvage installation crate pickup sound.
AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_crateSalvage;
soundToPlay.setObjectID( other->getID() );
TheAudio->addAudioEvent( &soundToPlay );
}
else if( eligibleForWeaponSet( other ) && testWeaponChance() )
{
doWeaponSet( other );
//Play the salvage installation crate pickup sound.
AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_crateSalvage;
soundToPlay.setObjectID( other->getID() );
TheAudio->addAudioEvent( &soundToPlay );
//Play the unit voice acknowledgement for upgrading weapons.
//Already handled by the "move order"
//const AudioEventRTS *soundToPlayPtr = other->getTemplate()->getPerUnitSound( "VoiceSalvage" );
//soundToPlay = *soundToPlayPtr;
//soundToPlay.setObjectID( other->getID() );
//TheAudio->addAudioEvent( &soundToPlay );
}
else if( eligibleForLevel( other ) && testLevelChance() )
{
doLevelGain( other );
//Sound will play in
//soundToPlay = TheAudio->getMiscAudio()->m_unitPromoted;
}
else // just assume the testMoneyChance
{
doMoney( other );
AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_crateMoney;
soundToPlay.setObjectID( other->getID() );
TheAudio->addAudioEvent(&soundToPlay);
}
other->getControllingPlayer()->getAcademyStats()->recordSalvageCollected();
return TRUE;
}
// ------------------------------------------------------------------------------------------------
Bool SalvageCrateCollide::eligibleForWeaponSet( Object *other )
{
if( other == NULL )
return FALSE;
// A kindof marks eligibility, and you must not be fully upgraded
if( !other->isKindOf(KINDOF_WEAPON_SALVAGER) )
return FALSE;
if( other->testWeaponSetFlag(WEAPONSET_CRATEUPGRADE_TWO) )
return FALSE;
return TRUE;
}
// ------------------------------------------------------------------------------------------------
Bool SalvageCrateCollide::eligibleForArmorSet( Object *other )
{
if( other == NULL )
return FALSE;
// A kindof marks eligibility, and you must not be fully upgraded
if( !other->isKindOf(KINDOF_ARMOR_SALVAGER) )
return FALSE;
if( other->testArmorSetFlag(ARMORSET_CRATE_UPGRADE_TWO) )
return FALSE;
return TRUE;
}
// ------------------------------------------------------------------------------------------------
Bool SalvageCrateCollide::eligibleForLevel( Object *other )
{
if( other == NULL )
return FALSE;
// Sorry, you are max level
if( other->getExperienceTracker()->getVeterancyLevel() == LEVEL_HEROIC )
return FALSE;
// Sorry, you can't gain levels
if( !other->getExperienceTracker()->isTrainable() )
return FALSE;
return TRUE;
}
// ------------------------------------------------------------------------------------------------
Bool SalvageCrateCollide::testWeaponChance()
{
const SalvageCrateCollideModuleData *md = getSalvageCrateCollideModuleData();
if( md->m_weaponChance == 1.0f )
return TRUE; // don't waste a random number for a 100%
Real randomNumber = GameLogicRandomValueReal( 0, 1 );
if( randomNumber < md->m_weaponChance )
return TRUE;
return FALSE;
}
// ------------------------------------------------------------------------------------------------
Bool SalvageCrateCollide::testLevelChance()
{
const SalvageCrateCollideModuleData *md = getSalvageCrateCollideModuleData();
if( md->m_levelChance == 1.0f )
return TRUE; // don't waste a random number for a 100%
Real randomNumber = GameLogicRandomValueReal( 0, 1 );
if( randomNumber < md->m_levelChance )
return TRUE;
return FALSE;
}
// ------------------------------------------------------------------------------------------------
void SalvageCrateCollide::doWeaponSet( Object *other )
{
if( other->testWeaponSetFlag( WEAPONSET_CRATEUPGRADE_ONE ) )
{
other->clearWeaponSetFlag( WEAPONSET_CRATEUPGRADE_ONE );
other->setWeaponSetFlag( WEAPONSET_CRATEUPGRADE_TWO );
}
else
{
other->setWeaponSetFlag( WEAPONSET_CRATEUPGRADE_ONE );
}
}
// ------------------------------------------------------------------------------------------------
void SalvageCrateCollide::doArmorSet( Object *other )
{
if( other->testArmorSetFlag( ARMORSET_CRATE_UPGRADE_ONE ) )
{
other->clearArmorSetFlag( ARMORSET_CRATE_UPGRADE_ONE );
other->setArmorSetFlag( ARMORSET_CRATE_UPGRADE_TWO );
other->clearAndSetModelConditionState(MODELCONDITION_ARMORSET_CRATEUPGRADE_ONE, MODELCONDITION_ARMORSET_CRATEUPGRADE_TWO);
}
else
{
other->setArmorSetFlag( ARMORSET_CRATE_UPGRADE_ONE );
other->setModelConditionState(MODELCONDITION_ARMORSET_CRATEUPGRADE_ONE);
}
}
// ------------------------------------------------------------------------------------------------
void SalvageCrateCollide::doLevelGain( Object *other )
{
other->getExperienceTracker()->gainExpForLevel( 1 );
}
// ------------------------------------------------------------------------------------------------
void SalvageCrateCollide::doMoney( Object *other )
{
const SalvageCrateCollideModuleData *md = getSalvageCrateCollideModuleData();
Int money;
if( md->m_minimumMoney != md->m_maximumMoney )// Random value doesn't like to get a constant range
money = GameLogicRandomValue( md->m_minimumMoney, md->m_maximumMoney );
else
money = md->m_minimumMoney;
if( money > 0 )
{
other->getControllingPlayer()->getMoney()->deposit( money );
other->getControllingPlayer()->getScoreKeeper()->addMoneyEarned( money );
//Display cash income floating over the crate. Position is me, everything else is them.
UnicodeString moneyString;
moneyString.format( TheGameText->fetch( "GUI:AddCash" ), money );
Coord3D pos;
pos.set( getObject()->getPosition() );
pos.z += 10.0f; //add a little z to make it show up above the unit.
Color color = other->getControllingPlayer()->getPlayerColor() | GameMakeColor( 0, 0, 0, 230 );
TheInGameUI->addFloatingText( moneyString, &pos, color );
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SalvageCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SalvageCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SalvageCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,105 @@
/*
** 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: ShroudCrateCollide.cpp ///////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, March 2002
// Desc: A crate that clears the shroud for the pickerupper
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/AudioEventRTS.h"
#include "Common/MiscAudio.h"
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/Module/ShroudCrateCollide.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ShroudCrateCollide::ShroudCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ShroudCrateCollide::~ShroudCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
Bool ShroudCrateCollide::executeCrateBehavior( Object *other )
{
Player* cratePlayer = other->getControllingPlayer();
ThePartitionManager->revealMapForPlayer( cratePlayer->getPlayerIndex() );
//Play a crate pickup sound.
AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_crateShroud;
soundToPlay.setObjectID( other->getID() );
TheAudio->addAudioEvent(&soundToPlay);
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void ShroudCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void ShroudCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void ShroudCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,132 @@
/*
** 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: UnitCrateCollide.cpp ///////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, March 2002
// Desc: A crate that gives n units of type m the the pickerupper
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/AudioEventRTS.h"
#include "Common/MiscAudio.h"
#include "Common/Player.h"
#include "Common/ThingFactory.h"
#include "Common/Xfer.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/Module/UnitCrateCollide.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UnitCrateCollide::UnitCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UnitCrateCollide::~UnitCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
Bool UnitCrateCollide::executeCrateBehavior( Object *other )
{
UnsignedInt unitCount = getUnitCrateCollideModuleData()->m_unitCount;
ThingTemplate const *unitType = TheThingFactory->findTemplate( getUnitCrateCollideModuleData()->m_unitType );
if( unitType == NULL )
{
return FALSE;
}
for( Int unitIndex = 0; unitIndex < unitCount; unitIndex++ )
{
Team *creationTeam = other->getControllingPlayer()->getDefaultTeam();
Object *newObj = TheThingFactory->newObject( unitType, creationTeam );
if( newObj )
{
Coord3D creationPoint = *other->getPosition();
/// @todo As a user of the future findLegalPositionAround, I wouldn't mind not having to specify range. I just want a non colliding point.
FindPositionOptions fpOptions;
fpOptions.minRadius = 0.0f;
fpOptions.maxRadius = 20.0f;
ThePartitionManager->findPositionAround( &creationPoint,
&fpOptions,
&creationPoint );
newObj->setOrientation( other->getOrientation() );
newObj->setPosition( &creationPoint );
}
}
//Play a crate pickup sound.
AudioEventRTS soundToPlay = TheAudio->getMiscAudio()->m_crateFreeUnit;
soundToPlay.setObjectID( other->getID() );
TheAudio->addAudioEvent(&soundToPlay);
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void UnitCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void UnitCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void UnitCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,210 @@
/*
** 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: VeterancyCrateCollide.cpp ///////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, March 2002
// Desc: A crate that gives a level of experience to all within n distance
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/Module/VeterancyCrateCollide.h"
#include "GameLogic/ScriptEngine.h"
#include "GameLogic/Module/AIUpdate.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
VeterancyCrateCollide::VeterancyCrateCollide( Thing *thing, const ModuleData* moduleData ) : CrateCollide( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
VeterancyCrateCollide::~VeterancyCrateCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Int VeterancyCrateCollide::getLevelsToGain() const
{
const VeterancyCrateCollideModuleData* d = getVeterancyCrateCollideModuleData();
if (!d || !d->m_addsOwnerVeterancy)
return 1;
// this requires that "regular" is 0, vet is 1, etc.
return (Int)getObject()->getVeterancyLevel();
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool VeterancyCrateCollide::isValidToExecute( const Object *other ) const
{
const VeterancyCrateCollideModuleData* d = getVeterancyCrateCollideModuleData();
if( !d )
{
return false;
}
if(!CrateCollide::isValidToExecute(other))
return false;
if (other->isEffectivelyDead())
return false;
if( other->isSignificantlyAboveTerrain() )
{
return false;
}
Int levelsToGain = getLevelsToGain();
if (levelsToGain <= 0)
return false;
const ExperienceTracker *et = other->getExperienceTracker();
if( !et || !et->isTrainable() )
{
//If the other unit can't gain experience, then we can't help promote it!
return false;
}
if (!et || !et->canGainExpForLevel(levelsToGain))
return false;
if( d->m_isPilot )
{
if( other->getControllingPlayer() != getObject()->getControllingPlayer() )
{
//This is a pilot and we are checking to make sure the pilot is entering a vehicle on
//the same team. If it's not, then don't allow it.. this is particularly the case for
//pilots attempting to enter civilian vehicles.
return false;
}
if( other->isUsingAirborneLocomotor() )
{
// Can't upgrade a helicopter or plane, but we will think we can for a moment while it
// is on the ground from being built.
return false;
}
}
return true;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Bool VeterancyCrateCollide::executeCrateBehavior( Object *other )
{
//Make sure the pilot is actually *TRYING* to enter the object
//unlike other crates
AIUpdateInterface *ai = (AIUpdateInterface*)getObject()->getAIUpdateInterface();
const VeterancyCrateCollideModuleData *md = getVeterancyCrateCollideModuleData();
if( !ai || ai->getGoalObject() != other )
{
return false;
}
Int levelsToGain = getLevelsToGain();
Real range = md->m_rangeOfEffect;
if (range == 0)
{
// do just the collider
if (other != NULL)
{
other->getExperienceTracker()->gainExpForLevel( levelsToGain, ( ! md->m_isPilot) );
}
}
else
{
PartitionFilterSamePlayer othersPlayerFilter( other->getControllingPlayer() );
PartitionFilterSameMapStatus filterMapStatus(other);
PartitionFilter *filters[] = { &othersPlayerFilter, &filterMapStatus, NULL };
ObjectIterator *iter = ThePartitionManager->iterateObjectsInRange( other, range, FROM_CENTER_2D, filters, ITER_FASTEST );
MemoryPoolObjectHolder hold(iter);
for( Object *potentialObject = iter->first(); potentialObject; potentialObject = iter->next() )
{
// This function will give just enough exp for the Object to gain a level, if it can
potentialObject->getExperienceTracker()->gainExpForLevel( levelsToGain, ( ! md->m_isPilot) );
}
}
//In order to make things easier for the designers, we are going to transfer the terrorist name
//to the car... so the designer can control the car with their scripts.
if( md->m_isPilot )
{
TheScriptEngine->transferObjectName( getObject()->getName(), other );
}
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void VeterancyCrateCollide::crc( Xfer *xfer )
{
// extend base class
CrateCollide::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void VeterancyCrateCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CrateCollide::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void VeterancyCrateCollide::loadPostProcess( void )
{
// extend base class
CrateCollide::loadPostProcess();
} // end loadPostProcess

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: FireWeaponCollide.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood April 2002
// Desc: Shoot something that collides with me every frame with my weapon
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_OBJECT_STATUS_NAMES
#include "Common/Xfer.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/FireWeaponCollide.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void FireWeaponCollideModuleData::buildFieldParse(MultiIniFieldParse& p)
{
CollideModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "CollideWeapon", INI::parseWeaponTemplate, NULL, offsetof( FireWeaponCollideModuleData, m_collideWeaponTemplate ) },
{ "FireOnce", INI::parseBool, NULL, offsetof( FireWeaponCollideModuleData, m_fireOnce ) },
{ "RequiredStatus", ObjectStatusMaskType::parseFromINI, NULL, offsetof( FireWeaponCollideModuleData, m_requiredStatus ) },
{ "ForbiddenStatus", ObjectStatusMaskType::parseFromINI, NULL, offsetof( FireWeaponCollideModuleData, m_forbiddenStatus ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
FireWeaponCollide::FireWeaponCollide( Thing *thing, const ModuleData* moduleData ) :
CollideModule( thing, moduleData ),
m_collideWeapon(NULL)
{
m_collideWeapon = TheWeaponStore->allocateNewWeapon(getFireWeaponCollideModuleData()->m_collideWeaponTemplate, PRIMARY_WEAPON);
m_everFired = FALSE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
FireWeaponCollide::~FireWeaponCollide( void )
{
if (m_collideWeapon)
m_collideWeapon->deleteInstance();
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void FireWeaponCollide::onCollide( Object *other, const Coord3D *loc, const Coord3D *normal )
{
if( other == NULL )
return; //Don't shoot the ground
Object *me = getObject();
// This will fire at you every frame, because multiple people could be colliding and we want
// to hurt them all. Another solution would be to keep a Map of other->objetIDs and
// delays for each individually. However, this solution here is so quick and simple that it
// warrants the "do it eventually if we need it" clause.
if( shouldFireWeapon() )
{
m_collideWeapon->loadAmmoNow( me );
m_collideWeapon->fireWeapon( me, other );
}
}
//-------------------------------------------------------------------------------------------------
Bool FireWeaponCollide::shouldFireWeapon()
{
const FireWeaponCollideModuleData *d = getFireWeaponCollideModuleData();
ObjectStatusMaskType status = getObject()->getStatusBits();
//We need all required status or else we fail
if( !status.testForAll( d->m_requiredStatus ) )
return FALSE;
//If we have any forbidden statii, then fail
if( status.testForAny( d->m_forbiddenStatus ) )
return FALSE;
if( m_everFired && d->m_fireOnce )
return FALSE;// can only fire once ever
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void FireWeaponCollide::crc( Xfer *xfer )
{
// extend base class
CollideModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void FireWeaponCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CollideModule::xfer( xfer );
// weapon
Bool collideWeaponPresent = m_collideWeapon ? TRUE : FALSE;
xfer->xferBool( &collideWeaponPresent );
if( collideWeaponPresent )
{
DEBUG_ASSERTCRASH( m_collideWeapon != NULL,
("FireWeaponCollide::xfer - m_collideWeapon present mismatch\n") );
xfer->xferSnapshot( m_collideWeapon );
} // end else
else
{
DEBUG_ASSERTCRASH( m_collideWeapon == NULL,
("FireWeaponCollide::Xfer - m_collideWeapon missing mismatch\n" ));
} // end else
// ever fired
xfer->xferBool( &m_everFired );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void FireWeaponCollide::loadPostProcess( void )
{
// extend base class
CollideModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
////////////////////////////////////////////////////////////////////////////////
// //
// (c) 2001-2003 Electronic Arts Inc. //
// //
////////////////////////////////////////////////////////////////////////////////
// FILE: SquishCollide.cpp //////////////////////////////////////////////////////////////////////////
// Author:
// Desc:
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/ThingFactory.h"
#include "Common/Xfer.h"
#include "GameLogic/Damage.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/SquishCollide.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#include "GameLogic/Module/HijackerUpdate.h"
#include "GameLogic/Module/SpecialAbilityUpdate.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/PartitionManager.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SquishCollide::SquishCollide( Thing *thing, const ModuleData* moduleData ) : CollideModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SquishCollide::~SquishCollide( void )
{
}
//-------------------------------------------------------------------------------------------------
/** Do the collision */
//-------------------------------------------------------------------------------------------------
void SquishCollide::onCollide( Object *other, const Coord3D *loc, const Coord3D *normal )
{
// Note that other == null means "collide with ground"
if (other == NULL)
return;
Object *self = getObject();
AIUpdateInterface *ai = self->getAI();
if( ai && ai->getGoalObject() == other )
{
//We are actually targeting the other object to do something to! Don't allow them to crush us in the following
//special circumstances:
//Hijacking!
static NameKeyType key_HijackerUpdate = NAMEKEY( "HijackerUpdate" );
HijackerUpdate *hijackUpdate = (HijackerUpdate*)self->findUpdateModule( key_HijackerUpdate );
if( hijackUpdate )
{
return;
}
//TNT hunter placing a charge!
SpecialAbilityUpdate *saUpdate = self->findSpecialAbilityUpdate( SPECIAL_TANKHUNTER_TNT_ATTACK );
if( saUpdate && saUpdate->isActive() )
{
return;
}
}
// order matters: we want to know if IT considers ME to be an ally (a reversal of the usual situation)
if( other->getCrusherLevel() > 0 && other->getRelationship(getObject()) != ALLIES)
{
PhysicsBehavior *otherPhysics = other->getPhysics();
if (otherPhysics == NULL)
return;
// use a 1.0 crush radius so the tank has to actually hit the infantry.
GeometryInfo myGeom = self->getGeometryInfo();
myGeom.setMajorRadius(1.0f);
myGeom.setMinorRadius(1.0f);
if (!ThePartitionManager->geomCollidesWithGeom(other->getPosition(), other->getGeometryInfo(), other->getOrientation(),
self->getPosition(), myGeom, self->getOrientation())) {
return;
}
// only squish if tank is moving toward victim
const Coord3D *vel = otherPhysics->getVelocity();
const Coord3D *pos = other->getPosition();
const Coord3D *myPos = getObject()->getPosition();
Coord3D to;
to.x = myPos->x - pos->x;
to.y = myPos->y - pos->y;
to.z = myPos->z - pos->z;
if (to.x * vel->x + to.y * vel->y > 0.0f)
{
DamageInfo damageInfo;
damageInfo.in.m_damageType = DAMAGE_CRUSH;
damageInfo.in.m_deathType = DEATH_CRUSHED;
damageInfo.in.m_sourceID = other->getID();
damageInfo.in.m_amount = HUGE_DAMAGE_AMOUNT; // make sure they die
getObject()->attemptDamage( &damageInfo );
getObject()->friend_setUndetectedDefector( FALSE );// My secret is out
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SquishCollide::crc( Xfer *xfer )
{
// extend base class
CollideModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SquishCollide::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CollideModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SquishCollide::loadPostProcess( void )
{
// extend base class
CollideModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,436 @@
/*
** 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: CaveContain.cpp ////////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, July 2002
// Desc: A version of OpenContain that overrides where the passengers are stored: one of CaveSystem's
// entries. Changing entry is a script or ini command. All queries about capacity and
// contents are also redirected. They change sides like Garrison too.
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameState.h"
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/Team.h"
#include "Common/ThingTemplate.h"
#include "Common/TunnelTracker.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/CaveContain.h"
#include "GameLogic/CaveSystem.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
///////////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CaveContain::CaveContain( Thing *thing, const ModuleData* moduleData ) : OpenContain( thing, moduleData )
{
m_needToRunOnBuildComplete = true;
m_caveIndex = 0;
m_originalTeam = NULL;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CaveContain::~CaveContain()
{
}
void CaveContain::addToContainList( Object *obj )
{
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
myTracker->addToContainList( obj );
}
//-------------------------------------------------------------------------------------------------
/** Remove 'obj' from the m_containList of objects in this module.
* This will trigger an onRemoving event for the object that this module
* is a part of and an onRemovedFrom event for the object being removed */
//-------------------------------------------------------------------------------------------------
void CaveContain::removeFromContain( Object *obj, Bool exposeStealthUnits )
{
// sanity
if( obj == NULL )
return;
//
// we can only remove this object from the contains list of this module if
// it is actually contained by this module
//
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
if( ! myTracker->isInContainer( obj ) )
{
return;
}
// This must come before the onRemov*, because CaveContain's version has a edge-0 triggered event.
// If that were to go first, the number would still be 1 at that time. Noone else cares about
// order.
myTracker->removeFromContain( obj, exposeStealthUnits );
// trigger an onRemoving event for 'm_object' no longer containing 'itemToRemove->m_object'
if (getObject()->getContain())
getObject()->getContain()->onRemoving( obj );
// trigger an onRemovedFrom event for 'remove'
obj->onRemovedFrom( getObject() );
}
//-------------------------------------------------------------------------------------------------
/** Remove all contained objects from the contained list */
//-------------------------------------------------------------------------------------------------
void CaveContain::removeAllContained( Bool exposeStealthUnits )
{
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
const ContainedItemsList *fullList = myTracker->getContainedItemsList();
Object *obj;
ContainedItemsList::const_iterator it;
it = (*fullList).begin();
while( it != (*fullList).end() )
{
obj = *it;
it++;
removeFromContain( obj, exposeStealthUnits );
}
}
//-------------------------------------------------------------------------------------------------
/** Iterate the contained list and call the callback on each of the objects */
//-------------------------------------------------------------------------------------------------
void CaveContain::iterateContained( ContainIterateFunc func, void *userData, Bool reverse )
{
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
myTracker->iterateContained( func, userData, reverse );
}
//-------------------------------------------------------------------------------------------------
void CaveContain::onContaining( Object *obj, Bool wasSelected )
{
OpenContain::onContaining( obj, wasSelected );
// objects inside a building are held
obj->setDisabled( DISABLED_HELD );
//
// the team of the building is now the same as those that have garrisoned it, be sure
// to save our original team tho so that we can revert back to it when all the
// occupants are gone
//
recalcApparentControllingPlayer();
}
//-------------------------------------------------------------------------------------------------
void CaveContain::onRemoving( Object *obj )
{
OpenContain::onRemoving(obj);
// object is no longer held inside a garrisoned building
obj->clearDisabled( DISABLED_HELD );
/// place the object in the world at position of the container m_object
ThePartitionManager->registerObject( obj );
obj->setPosition( getObject()->getPosition() );
if( obj->getDrawable() )
{
obj->getDrawable()->setDrawableHidden( false );
}
doUnloadSound();
if( getContainCount() == 0 )
{
// put us back on our original team
// (hokey exception: if our team is null, don't bother -- this
// usually means we are being called during game-teardown and
// the teams are no longer valid...)
if (getObject()->getTeam() != NULL)
{
changeTeamOnAllConnectedCaves( m_originalTeam, FALSE );
m_originalTeam = NULL;
}
// change the state back from garrisoned
Drawable *draw = getObject()->getDrawable();
if( draw )
{
draw->clearModelConditionState( MODELCONDITION_GARRISONED );
}
} // end if
}
Bool CaveContain::isValidContainerFor(const Object* obj, Bool checkCapacity) const
{
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
return myTracker->isValidContainerFor( obj, checkCapacity );
}
UnsignedInt CaveContain::getContainCount() const
{
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
return myTracker->getContainCount();
}
Int CaveContain::getContainMax( void ) const
{
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
return myTracker->getContainMax();
}
const ContainedItemsList* CaveContain::getContainedItemsList() const
{
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
return myTracker->getContainedItemsList();
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void CaveContain::onDie( const DamageInfo * damageInfo )
{
// override the onDie we inherit from OpenContain. no super call.
if (!getCaveContainModuleData()->m_dieMuxData.isDieApplicable(getObject(), damageInfo))
return;
if( getObject()->getStatusBits().test( OBJECT_STATUS_UNDER_CONSTRUCTION ) )
return;//it never registered itself as a tunnel
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
TheCaveSystem->unregisterCave( m_caveIndex );
myTracker->onTunnelDestroyed( getObject() );
}
//-------------------------------------------------------------------------------------------------
void CaveContain::onCreate( void )
{
m_caveIndex = getCaveContainModuleData()->m_caveIndexData;
}
//-------------------------------------------------------------------------------------------------
void CaveContain::onBuildComplete( void )
{
if( ! shouldDoOnBuildComplete() )
return;
m_needToRunOnBuildComplete = false;
TheCaveSystem->registerNewCave( m_caveIndex );
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
myTracker->onTunnelCreated( getObject() );
}
//-------------------------------------------------------------------------------------------------
void CaveContain::tryToSetCaveIndex( Int newIndex )
{
if( TheCaveSystem->canSwitchIndexToIndex( m_caveIndex, newIndex ) )
{
TunnelTracker *myOldTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
TheCaveSystem->unregisterCave( m_caveIndex );
myOldTracker->onTunnelDestroyed( getObject() );
m_caveIndex = newIndex;
TheCaveSystem->registerNewCave( m_caveIndex );
TunnelTracker *myNewTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
myNewTracker->onTunnelCreated( getObject() );
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void CaveContain::recalcApparentControllingPlayer( void )
{
//Record original team first time through.
if( m_originalTeam == NULL )
{
m_originalTeam = getObject()->getTeam();
}
// (hokey trick: if our team is null, nuke originalTeam -- this
// usually means we are being called during game-teardown and
// the teams are no longer valid...)
if (getObject()->getTeam() == NULL)
m_originalTeam = NULL;
// This is called from onContaining, so a one is the edge trigger to do capture stuff
if( getContainCount() == 1 )
{
ContainedItemsList::const_iterator it = getContainedItemsList()->begin();
Object *rider = *it;
// This also gets called during reset from the PlayerList, so we might not actually have players.
// Check like the hokey trick mentioned above
if( rider->getControllingPlayer() )
changeTeamOnAllConnectedCaves( rider->getControllingPlayer()->getDefaultTeam(), TRUE );
}
else if( getContainCount() == 0 )
{
// And a 0 is the edge trigger to do uncapture stuff
changeTeamOnAllConnectedCaves( m_originalTeam, FALSE );
}
// Handle the team color that is rendered
const Player* controller = getApparentControllingPlayer(ThePlayerList->getLocalPlayer());
if (controller)
{
if (TheGlobalData->m_timeOfDay == TIME_OF_DAY_NIGHT)
getObject()->getDrawable()->setIndicatorColor( controller->getPlayerNightColor() );
else
getObject()->getDrawable()->setIndicatorColor( controller->getPlayerColor() );
}
}
/////////////////////////////////////////////////////////////////////////////////////
static CaveInterface* findCave(Object* obj)
{
for (BehaviorModule** i = obj->getBehaviorModules(); *i; ++i)
{
CaveInterface* c = (*i)->getCaveInterface();
if (c != NULL)
return c;
}
return NULL;
}
/////////////////////////////////////////////////////////////////////////////////////
void CaveContain::changeTeamOnAllConnectedCaves( Team *newTeam, Bool setOriginalTeams )
{
TunnelTracker *myTracker = TheCaveSystem->getTunnelTrackerForCaveIndex( m_caveIndex );
const std::list<ObjectID> *allCaves = myTracker->getContainerList();
for( std::list<ObjectID>::const_iterator iter = allCaves->begin(); iter != allCaves->end(); iter++ )
{
// For each ID, look it up and change its team. We all get captured together.
Object *currentCave = TheGameLogic->findObjectByID( *iter );
if( currentCave )
{
// This is a distributed Garrison in terms of capturing, so when one node
// triggers the change, he needs to tell everyone, so anyone can do the un-change.
CaveInterface *caveModule = findCave(currentCave);
if( caveModule == NULL )
continue;
if( setOriginalTeams )
caveModule->setOriginalTeam( currentCave->getTeam() );
else
caveModule->setOriginalTeam( NULL );
// Now do the actual switch for this one.
currentCave->defect( newTeam, 0 );
// currentCave->setTeam( newTeam );
}
}
}
/////////////////////////////////////////////////////////////////////////////////////
void CaveContain::setOriginalTeam( Team *oldTeam )
{
m_originalTeam = oldTeam;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void CaveContain::crc( Xfer *xfer )
{
// extend base class
OpenContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void CaveContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
OpenContain::xfer( xfer );
// need to run on build complete
xfer->xferBool( &m_needToRunOnBuildComplete );
// cave index
xfer->xferInt( &m_caveIndex );
// original team
TeamID teamID = m_originalTeam ? m_originalTeam->getID() : TEAM_ID_INVALID;
xfer->xferUser( &teamID, sizeof( TeamID ) );
if( xfer->getXferMode() == XFER_LOAD )
{
if( teamID != TEAM_ID_INVALID )
{
m_originalTeam = TheTeamFactory->findTeamByID( teamID );
if( m_originalTeam == NULL )
{
DEBUG_CRASH(( "CaveContain::xfer - Unable to find original team by id\n" ));
throw SC_INVALID_DATA;
} // end if
} // end if
else
m_originalTeam = NULL;
} // end if
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void CaveContain::loadPostProcess( void )
{
// extend base class
OpenContain::loadPostProcess();
} // end loadPostProcess

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,222 @@
/*
** 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: HealContain.cpp //////////////////////////////////////////////////////////////////////////
// Author: Colin Day
// Desc: Objects that are contained inside a heal contain ... get healed! oh my!
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameLogic/Damage.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/HealContain.h"
#include "GameLogic/Module/UpdateModule.h"
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
HealContainModuleData::HealContainModuleData( void )
{
m_framesForFullHeal = 0;
} // end HealContainModuleData
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void HealContainModuleData::buildFieldParse(MultiIniFieldParse& p)
{
OpenContainModuleData::buildFieldParse( p );
static const FieldParse dataFieldParse[] =
{
{ "TimeForFullHeal", INI::parseDurationUnsignedInt, NULL, offsetof( HealContainModuleData, m_framesForFullHeal ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
HealContain::HealContain( Thing *thing, const ModuleData *moduleData )
: OpenContain( thing, moduleData )
{
} // end HealContain
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
HealContain::~HealContain( void )
{
} // end ~HealContain
// ------------------------------------------------------------------------------------------------
/** Per frame update */
// ------------------------------------------------------------------------------------------------
UpdateSleepTime HealContain::update( void )
{
// extending functionality
/*UpdateSleepTime result =*/ OpenContain::update();
// get the module data
const HealContainModuleData *modData = getHealContainModuleData();
//
// for each of our objects, we give them a little health each frame so that when the
// the TimeTillHealed is up, the object will exit and be fully healed
//
Bool doneHealing;
Object *obj;
ContainedItemsList::const_iterator it = getContainList().begin();
while( it != getContainList().end() )
{
// get the object
obj = *it;
// increment the iterator, which allows failure to not cause an infinite loop
++it;
// do the healing on this object
doneHealing = doHeal( obj, modData->m_framesForFullHeal );
// if we're done healing, we need to remove us from the healing container
if( doneHealing == TRUE )
{
ExitDoorType exitDoor = reserveDoorForExit(obj->getTemplate(), obj);
if (exitDoor != DOOR_NONE_AVAILABLE)
exitObjectViaDoor( obj, exitDoor );
} // end if
} // end for, it
return UPDATE_SLEEP_NONE;
} // end update
// ------------------------------------------------------------------------------------------------
/** Do the healing for a single object for a single frame. */
// ------------------------------------------------------------------------------------------------
Bool HealContain::doHeal( Object *obj, UnsignedInt framesForFullHeal )
{
Bool doneHealing = FALSE;
// setup the healing damageInfo structure with all but the amount
DamageInfo healInfo;
healInfo.in.m_damageType = DAMAGE_HEALING;
healInfo.in.m_deathType = DEATH_NONE;
healInfo.in.m_sourceID = getObject()->getID();
// get body module of the thing to heal
BodyModuleInterface *body = obj->getBodyModule();
// if we've been in here long enough ... set our health to max
if( TheGameLogic->getFrame() - obj->getContainedByFrame() >= framesForFullHeal )
{
// set the amount to max just to be sure we're at the top
healInfo.in.m_amount = body->getMaxHealth();
// set max health
body->attemptHealing( &healInfo );
// we're done healing
doneHealing = TRUE;
} // end if
else
{
//
// given the *whole* time it would take to heal this object, lets pretend that the
// object is at zero health ... and give it a sliver of health as if it were at 0 health
// and would be fully healed at 'framesForFullHeal'
//
healInfo.in.m_amount = body->getMaxHealth() / (Real)framesForFullHeal;
// do the healing
body->attemptHealing( &healInfo );
} // end else
// return if we're done healing
return doneHealing;
} // end doHeal
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void HealContain::crc( Xfer *xfer )
{
// extend base class
OpenContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void HealContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
OpenContain::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void HealContain::loadPostProcess( void )
{
// extend base class
OpenContain::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,481 @@
/*
** 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: HelixContain.cpp ////////////////////////////////////////////////////////////////////////
// Author: Mark Lorenzen, April, 2003
//
// Desc:
//
///////////////////////////////////////////////////////////////////////////////////////////////////
// USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "Common/ThingTemplate.h"
#include "Common/ThingFactory.h"
#include "GameClient/ControlBar.h"
#include "GameClient/Drawable.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/HelixContain.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Weapon.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
HelixContainModuleData::HelixContainModuleData()
{
// m_initialPayload.count = 0;
m_drawPips = TRUE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void HelixContainModuleData::buildFieldParse(MultiIniFieldParse& p)
{
TransportContainModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "PayloadTemplateName", INI::parseAsciiStringVectorAppend, NULL, offsetof(HelixContainModuleData, m_payloadTemplateNameData) },
{"ShouldDrawPips", INI::parseBool, NULL, offsetof(HelixContainModuleData, m_drawPips) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
//void HelixContainModuleData::parseInitialPayload( INI* ini, void *instance, void *store, const void* /*userData*/ )
//{
// HelixContainModuleData* self = (HelixContainModuleData*)instance;
// const char* name = ini->getNextToken();
// const char* countStr = ini->getNextTokenOrNull();
// Int count = countStr ? INI::scanInt(countStr) : 1;
// self->m_initialPayload.name.set(name);
// self->m_initialPayload.count = count;
//}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
HelixContain::HelixContain( Thing *thing, const ModuleData *moduleData ) :
TransportContain( thing, moduleData )
{
m_payloadCreated = FALSE;
m_portableStructureID = INVALID_ID;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
HelixContain::~HelixContain( void )
{
}
void HelixContain::onObjectCreated( void )
{
HelixContain::createPayload();
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime HelixContain::update()
{
Object *portable = getPortableStructure();
if ( portable )
{
portable->setPosition( getObject()->getPosition());
portable->setOrientation( getObject()->getOrientation());
}
return TransportContain::update(); // extend base
}
void HelixContain::redeployOccupants( void )
{
Coord3D firePos = *getObject()->getPosition();
firePos.z += 8;
for (ContainedItemsList::iterator it = m_containList.begin(); it != m_containList.end(); ++it)
{
Object* rider = *it;
if (rider)
rider->setPosition( &firePos );
}
}
//-------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void HelixContain::createPayload()
{
HelixContainModuleData* self = (HelixContainModuleData*)getHelixContainModuleData();
// Any number of different passengers can be loaded here at init time
Object* object = getObject();
ContainModuleInterface *contain = object->getContain();
if( contain )
{
contain->enableLoadSounds( FALSE );
TemplateNameList list = self->m_payloadTemplateNameData;
TemplateNameIterator iter = list.begin();
while ( iter != list.end() )
{
const ThingTemplate* temp = TheThingFactory->findTemplate( *iter );
if (temp)
{
Object* payload = TheThingFactory->newObject( temp, object->getTeam() );
if( contain->isValidContainerFor( payload, true ) )
{
contain->addToContain( payload );
}
else
{
DEBUG_CRASH( ( "HelixContain::createPayload: %s is full, or not valid for the payload %s!", object->getName().str(), self->m_initialPayload.name.str() ) );
}
}
++iter;
}
contain->enableLoadSounds( TRUE );
} // endif contain
m_payloadCreated = TRUE;
}
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void HelixContain::onBodyDamageStateChange( const DamageInfo* damageInfo,
BodyDamageType oldState,
BodyDamageType newState) ///< state change callback
{
// Need to apply state change to the portable structure
Object *portable = getPortableStructure();
if ( newState != BODY_RUBBLE && portable )
{
portable->getBodyModule()->setDamageState( newState );
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Object* HelixContain::getPortableStructure( void )
{
return TheGameLogic->findObjectByID( m_portableStructureID );
}
//-------------------------------------------------------------------------------------------------
void HelixContain::onDie( const DamageInfo *damageInfo )
{
Object *portable = getPortableStructure();
if ( portable )
portable->kill();
TransportContain::onDie( damageInfo );//extend base class
}
//-------------------------------------------------------------------------------------------------
void HelixContain::onDelete( void )
{
Object *portable = getPortableStructure();
if ( portable )
TheGameLogic->destroyObject( portable );
TransportContain::onDelete( );
}
// ------------------------------------------------------------------------------------------------
void HelixContain::onCapture( Player *oldOwner, Player *newOwner )
{
// Need to setteam() the portable structure, that's all;
Object *portable = getPortableStructure();
if ( portable )
portable->setTeam( newOwner->getDefaultTeam() );
}
//-------------------------------------------------------------------------------------------------
void HelixContain::addToContainList( Object *obj )
{
if ( obj->isKindOf( KINDOF_PORTABLE_STRUCTURE ) && m_portableStructureID == INVALID_ID)
{
Object *portable = getPortableStructure();
if ( portable )
TheGameLogic->destroyObject( portable );
m_portableStructureID = obj->getID();
obj->friend_setContainedBy( getObject() );//fool portable into thinking my object is his container
}
else
TransportContain::addToContainList( obj );
}
//-------------------------------------------------------------------------------------------------
void HelixContain::addToContain( Object *obj )
{
if ( obj->isKindOf( KINDOF_PORTABLE_STRUCTURE ) && m_portableStructureID == INVALID_ID)
{
Object *portable = getPortableStructure();
if ( portable )
TheGameLogic->destroyObject( portable );
m_portableStructureID = obj->getID();
obj->friend_setContainedBy( getObject() );//fool portable into thinking my object is his container
}
else
TransportContain::addToContain( obj );
}
//-------------------------------------------------------------------------------------------------
void HelixContain::removeFromContain( Object *obj, Bool exposeStealthUnits )
{
if ( obj->isKindOf( KINDOF_PORTABLE_STRUCTURE ) && obj->getID() == m_portableStructureID )
{
Object *portable = getPortableStructure();
if ( portable )
m_portableStructureID = INVALID_ID;
//portable->kill();
}
else
{
TransportContain::removeFromContain( obj, exposeStealthUnits );
}
}
//-------------------------------------------------------------------------------------------------
Bool HelixContain::isValidContainerFor(const Object* obj, Bool checkCapacity) const
{
if ( obj->isKindOf( KINDOF_PORTABLE_STRUCTURE ) && INVALID_ID == m_portableStructureID )
return TRUE;
return TransportContain::isValidContainerFor( obj, checkCapacity );
}
//-------------------------------------------------------------------------------------------------
const Object *HelixContain::friend_getRider() const
{
// The draw order dependency bug for riders means that our draw module needs to cheat to get around it.
if ( m_portableStructureID != INVALID_ID )
{
const Object *portableAsRider = TheGameLogic->findObjectByID( m_portableStructureID );
return portableAsRider;
}
return NULL;
}
//-------------------------------------------------------------------------------------------------
Bool HelixContain::isEnclosingContainerFor( const Object *obj ) const
{
if ( m_portableStructureID == obj->getID() )
{
const Object *portableAsRider = TheGameLogic->findObjectByID( m_portableStructureID );
if ( portableAsRider == obj )
return FALSE;
}
return TransportContain::isEnclosingContainerFor( obj );
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
// if my object gets selected, then my visible passengers should, too
// this gets called from
void HelixContain::clientVisibleContainedFlashAsSelected()
{
if ( m_portableStructureID != INVALID_ID)
{
Object *portable = getPortableStructure();
if ( portable && portable->isKindOf(KINDOF_PORTABLE_STRUCTURE) )
{
Drawable *draw = portable->getDrawable();
if ( draw )
{
draw->flashAsSelected(); //WOW!
}
}
}
}
Bool HelixContain::isPassengerAllowedToFire( ObjectID id ) const
{
// WHETHER WE ARE ALLOWED TO FIRE DEPENDS ON WHO WE ARE
// PASSENGERS (PROPER) MAY ONLY IF THE FLAG IS TRUE
// RIDERS ARE ALWAYS ALLOWED TO FIRE (GATTLING CANNONS)
if ( getObject() && getObject()->getContainedBy() ) // nested containment voids firing, always
return FALSE;
if ( m_portableStructureID != INVALID_ID && m_portableStructureID == id )
return TRUE;
else
{
const Object *rider = TheGameLogic->findObjectByID( id );
if ( rider && rider->isKindOf( KINDOF_INFANTRY ))
return TransportContain::isPassengerAllowedToFire( id );//extend
}
return FALSE;
}
//-------------------------------------------------------------------------------------------------
void HelixContain::onContaining( Object *obj, Bool wasSelected )
{
// extend base class
TransportContain::onContaining( obj, wasSelected );
// give the object a garrisoned version of its weapon
obj->setWeaponBonusCondition( WEAPONBONUSCONDITION_GARRISONED );
obj->setDisabled( DISABLED_HELD );
if ( obj->isKindOf( KINDOF_PORTABLE_STRUCTURE ) && getObject()->testStatus( OBJECT_STATUS_STEALTHED ) )
{
StealthUpdate *myStealth = obj->getStealth();
if ( myStealth )
{
myStealth->receiveGrant( true );
// note to anyone... once stealth is granted to this gattlingcannon ( or such )
// let its own stealthupdate govern the allowedtostealth cases
// a portable structure never gets removed, so...
}
}
} // end onContaining
void HelixContain::onRemoving( Object *obj )
{
// extend base class
TransportContain::onRemoving(obj);
// give the object back a regular weapon
obj->clearWeaponBonusCondition( WEAPONBONUSCONDITION_GARRISONED );
obj->clearDisabled( DISABLED_HELD );
} // end onRemoving
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void HelixContain::crc( Xfer *xfer )
{
// extend base class
TransportContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void HelixContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 2;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
if (version >= 2)
xfer->xferObjectID( &m_portableStructureID );
// extend base class
TransportContain::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void HelixContain::loadPostProcess( void )
{
// extend base class
TransportContain::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,131 @@
/*
** 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: InternetHackContain.cpp //////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, June 2003
// Desc: Contain module that just gives aiHackInternet command to passengers
///////////////////////////////////////////////////////////////////////////////////////////////////
// USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "GameLogic/Module/InternetHackContain.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/AIUpdate.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
InternetHackContainModuleData::InternetHackContainModuleData()
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void InternetHackContainModuleData::buildFieldParse(MultiIniFieldParse& p)
{
TransportContainModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
// PRIVATE ////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC /////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
InternetHackContain::InternetHackContain( Thing *thing, const ModuleData *moduleData ) :
TransportContain( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
InternetHackContain::~InternetHackContain( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void InternetHackContain::onContaining( Object *rider, Bool wasSelected )
{
TransportContain::onContaining( rider, wasSelected );
if( rider->getAI() )
rider->getAI()->aiHackInternet(CMD_FROM_AI);
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void InternetHackContain::crc( Xfer *xfer )
{
// extend base class
TransportContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void InternetHackContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
TransportContain::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void InternetHackContain::loadPostProcess( void )
{
// extend base class
TransportContain::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,497 @@
/*
** 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: MobNexusContain.cpp //////////////////////////////////////////////////////////////////////
// Author: Mark Lorenzen, August 2002
// Desc: Contain module for mob units.
///////////////////////////////////////////////////////////////////////////////////////////////////
// USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/ThingTemplate.h"
#include "Common/ThingFactory.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/AI.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/Locomotor.h"
#include "GameLogic/Module/StealthUpdate.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/MobNexusContain.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#include "GameLogic/Object.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
MobNexusContainModuleData::MobNexusContainModuleData()
{
m_slotCapacity = 0;
m_scatterNearbyOnExit = true;
m_orientLikeContainerOnExit = false;
m_keepContainerVelocityOnExit = false;
m_exitPitchRate = 0.0f;
m_initialPayload.count = 0;
m_healthRegen = 0.0f;
//
// by default we say that MobNexae can have infantry inside them, this will be totally
// overwritten by any data provided from the INI entry tho
//
m_allowInsideKindOf = MAKE_KINDOF_MASK( KINDOF_INFANTRY );
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void MobNexusContainModuleData::parseInitialPayload( INI* ini, void *instance, void *store, const void* /*userData*/ )
{
MobNexusContainModuleData* self = (MobNexusContainModuleData*)instance;
const char* name = ini->getNextToken();
const char* countStr = ini->getNextTokenOrNull();
Int count = countStr ? INI::scanInt(countStr) : 1;
self->m_initialPayload.name.set(name);
self->m_initialPayload.count = count;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void MobNexusContainModuleData::buildFieldParse(MultiIniFieldParse& p)
{
OpenContainModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "Slots", INI::parseInt, NULL, offsetof( MobNexusContainModuleData, m_slotCapacity ) },
{ "ScatterNearbyOnExit", INI::parseBool, NULL, offsetof( MobNexusContainModuleData, m_scatterNearbyOnExit ) },
{ "OrientLikeContainerOnExit", INI::parseBool, NULL, offsetof( MobNexusContainModuleData, m_orientLikeContainerOnExit ) },
{ "KeepContainerVelocityOnExit", INI::parseBool, NULL, offsetof( MobNexusContainModuleData, m_keepContainerVelocityOnExit ) },
{ "ExitBone", INI::parseAsciiString, NULL, offsetof( MobNexusContainModuleData, m_exitBone ) },
{ "ExitPitchRate", INI::parseAngularVelocityReal, NULL, offsetof( MobNexusContainModuleData, m_exitPitchRate ) },
{ "InitialPayload", parseInitialPayload, NULL, 0 },
{ "HealthRegen%PerSec", INI::parseReal, NULL, offsetof( MobNexusContainModuleData, m_healthRegen ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
// PRIVATE ////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Int MobNexusContain::getContainMax( void ) const
{
if (getMobNexusContainModuleData())
return getMobNexusContainModuleData()->m_slotCapacity;
return 0;
}
// PUBLIC /////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
MobNexusContain::MobNexusContain( Thing *thing, const ModuleData *moduleData ) :
OpenContain( thing, moduleData )
{
m_extraSlotsInUse = 0;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
MobNexusContain::~MobNexusContain( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/**
can this container contain this kind of object?
and, if checkCapacity is TRUE, does this container have enough space left to hold the given unit?
*/
Bool MobNexusContain::isValidContainerFor(const Object* rider, Bool checkCapacity) const
{
// sanity
if (!rider)
return false;
//The point of this new code is to determine when something has a negative 1 contain max, to
//look at the object inside of it to use that as the valid check. There is a case, when a
//paratrooper (an infantry contained in a parachute). In this case, when we pass this object
//to contain in a --- plane, we want to check the infantry, not the parachute.
// const MobNexusContainModuleData *modData = getMobNexusContainModuleData();
//Check if we are a fake container, and if so, get an object inside it to see what kind this object *is*.
if( rider->getContain() && rider->getContain()->isSpecialZeroSlotContainer() )
{
//Report the first thing inside it!
const ContainedItemsList *items = rider->getContain()->getContainedItemsList();
if( items )
{
ContainedItemsList::const_iterator it;
it = items->begin();
if( *it )
{
//Replace the object we are checking with the *first* object contained within it.
rider = *it;
}
}
}
else
{
//blech! This case may or may not occur... in which case, just use the supplied object.
}
// extend functionality
if( OpenContain::isValidContainerFor( rider, checkCapacity ) == false )
return false;
// only allied objects can be MobNexused.
// order matters: we want to know if IT considers ME to be an ally (a reversal of the usual situation)
if (rider->getRelationship(getObject()) != ALLIES)
return false;
Int mobNexusSlotCount = rider->getTransportSlotCount();
// if 0, this object isn't MobNexusable.
if (mobNexusSlotCount == 0)
return false;
if (checkCapacity)
{
return (m_extraSlotsInUse + getContainCount() + mobNexusSlotCount <= getContainMax());
}
else
{
return true;
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void MobNexusContain::onContaining( Object *rider, Bool wasSelected )
{
OpenContain::onContaining( rider, wasSelected );
// objects inside a MobNexus are held
rider->setDisabled( DISABLED_HELD );
Int mobNexusSlotCount = rider->getTransportSlotCount();
DEBUG_ASSERTCRASH(mobNexusSlotCount > 0, ("Hmm, this object isnt MobNexusable"));
m_extraSlotsInUse += mobNexusSlotCount - 1;
DEBUG_ASSERTCRASH(m_extraSlotsInUse >= 0 && m_extraSlotsInUse + getContainCount() <= getContainMax(), ("Hmm, bad slot count"));
//
// when we go from holding nothing to holding something we have a model condition
// to visually show the change
//
if( getContainCount() == 1 )
{
Drawable *draw = getObject()->getDrawable();
if( draw )
draw->setModelConditionState( MODELCONDITION_LOADED );
} // end if
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void MobNexusContain::onRemoving( Object *rider )
{
OpenContain::onRemoving(rider);
// object is no longer held inside a MobNexus
rider->clearDisabled( DISABLED_HELD );
const MobNexusContainModuleData* d = getMobNexusContainModuleData();
if (!d->m_exitBone.isEmpty())
{
Drawable* draw = getObject()->getDrawable();
if (draw)
{
Coord3D bonePos, worldPos;
if (draw->getPristineBonePositions(d->m_exitBone.str(), 0, &bonePos, NULL, 1) == 1)
{
getObject()->convertBonePosToWorldPos(&bonePos, NULL, &worldPos, NULL);
rider->setPosition(&worldPos);
}
}
}
if (d->m_orientLikeContainerOnExit)
{
rider->setOrientation(getObject()->getOrientation());
}
if (d->m_keepContainerVelocityOnExit)
{
PhysicsBehavior* parent = getObject()->getPhysics();
PhysicsBehavior* child = rider->getPhysics();
if (parent && child)
{
Coord3D startingForce = *parent->getVelocity();
Real mass = child->getMass();
startingForce.x *= mass;
startingForce.y *= mass;
startingForce.z *= mass;
child->applyMotiveForce( &startingForce );
Real pitchRate = child->getCenterOfMassOffset() * d->m_exitPitchRate;
child->setPitchRate( pitchRate );
}
}
if (d->m_scatterNearbyOnExit)
scatterToNearbyPosition(rider);
Int mobNexusSlotCount = rider->getTransportSlotCount();
DEBUG_ASSERTCRASH(mobNexusSlotCount > 0, ("This object isnt MobNexusable"));
m_extraSlotsInUse -= mobNexusSlotCount - 1;
DEBUG_ASSERTCRASH(m_extraSlotsInUse >= 0 && m_extraSlotsInUse + getContainCount() <= getContainMax(), ("Bad slot count, MobNexus"));
// when we are empty again, clear the model condition for loaded
if( getContainCount() == 0 )
{
Drawable *draw = getObject()->getDrawable();
if( draw )
draw->clearModelConditionState( MODELCONDITION_LOADED );
} // end if
if (getObject()->isAboveTerrain())
{
// temporarily mark the guy as being allowed to fall
// (overriding his locomotor's stick-to-ground attribute).
// this will be reset (by PhysicsBehavior) when he touches the ground.
PhysicsBehavior* physics = rider->getPhysics();
if (physics)
physics->setAllowToFall(true);
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void MobNexusContain::onObjectCreated()
{
MobNexusContainModuleData* self = (MobNexusContainModuleData*)getMobNexusContainModuleData();
Int count = self->m_initialPayload.count;
const ThingTemplate* payloadTemplate = TheThingFactory->findTemplate( self->m_initialPayload.name );
Object* object = getObject();
for( int i = 0; i < count; i++ )
{
//We are creating a MobNexus that comes with a initial payload, so add it now!
Object* payload = TheThingFactory->newObject( payloadTemplate, object->getControllingPlayer()->getDefaultTeam() );
if( object->getContain() && object->getContain()->isValidContainerFor( payload, true ) )
{
object->getContain()->addToContain( payload );
}
else
{
DEBUG_CRASH( ( "DeliverPayload: PutInContainer %s is full, or not valid for the payload %s!", object->getName().str(), self->m_initialPayload.name.str() ) );
}
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime MobNexusContain::update()
{
MobNexusContainModuleData *moduleData = (MobNexusContainModuleData*)getModuleData();
if( moduleData && moduleData->m_healthRegen )
{
ContainModuleInterface *contain = getObject()->getContain();
if( contain )
{
//This MobNexus has a health regeneration value, so go through and heal all inside.
const ContainedItemsList* items = contain->getContainedItemsList();
if( items )
{
ContainedItemsList::const_iterator it;
it = items->begin();
while( *it )
{
Object *object = *it;
//Advance to the next iterator
it++;
//Determine if we need healing or not.
BodyModuleInterface *body = object->getBodyModule();
if( body->getHealth() < body->getMaxHealth() )
{
//Calculate the health to be regenerated on each unit.
Real regen = body->getMaxHealth() * moduleData->m_healthRegen / 100.0f * SECONDS_PER_LOGICFRAME_REAL;
//Perform the actual healing for this frame.
// DamageInfo damageInfo;
// damageInfo.in.m_damageType = DAMAGE_HEALING;
// damageInfo.in.m_deathType = DEATH_NONE;
// damageInfo.in.m_sourceID = getObject()->getID();
// damageInfo.in.m_amount = regen;
// object->attemptDamage( &damageInfo );
object->attemptHealing( regen, getObject() );
}
}
}
}
}
return OpenContain::update(); //extend
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
ExitDoorType MobNexusContain::reserveDoorForExit( const ThingTemplate* objType, Object *specificObject )
{
if( specificObject == NULL )
return DOOR_1;// I can, in general, exit people.
// This is an override, not an extend. I will check for game legality for
// okaying the call to exitObjectViaDoor.
Object *me = getObject();
// this is present solely for some MobNexuss to override, so that they can land before
// allowing people to exit...
AIUpdateInterface* ai = me->getAIUpdateInterface();
if (ai && ai->getAiFreeToExit(me) != FREE_TO_EXIT)
return DOOR_NONE_AVAILABLE;
// I can always kick people out if I am in the air, I know what I'm doing
if( me->isUsingAirborneLocomotor() )
return DOOR_1;
const Coord3D *myPosition = me->getPosition();
if( !specificObject->getAIUpdateInterface() )
{
return DOOR_NONE_AVAILABLE;
}
const Locomotor *hisLocomotor = specificObject->getAIUpdateInterface()->getCurLocomotor();
// He can't get to this spot naturally, so I can't force him there. (amphib MobNexus)
if( ! TheAI->pathfinder()->validMovementTerrain(me->getLayer(), hisLocomotor, myPosition ) )
return DOOR_NONE_AVAILABLE;
return DOOR_1;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void MobNexusContain::unreserveDoorForExit( ExitDoorType exitDoor )
{
/* nothing */
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
Bool MobNexusContain::tryToEvacuate( Bool exposeStealthedUnits )
{
Bool exitedAnyone = false;
ContainedItemsList::const_iterator it = getContainList().begin();
while( it != getContainList().end() )
{
// get the object
Object *obj = *it;
// increment the iterator, since removal will pull this guy from the list somewhere else
// and we might not actually kick anyone so we don't want to loop forever.
++it;
ExitDoorType exitDoor = reserveDoorForExit(obj->getTemplate(), obj);
if(exitDoor != DOOR_NONE_AVAILABLE)
{
exitObjectViaDoor( obj, exitDoor );
exitedAnyone = true;
if( obj->isKindOf( KINDOF_STEALTH_GARRISON ) && exposeStealthedUnits )
{
StealthUpdate* stealth = obj->getStealth();
if( stealth )
{
stealth->markAsDetected();
}
}
}
}
return exitedAnyone;
}
// ------------------------------------------------------------------------------------------------
/** CRC - I like the word nexus */
// ------------------------------------------------------------------------------------------------
void MobNexusContain::crc( Xfer *xfer )
{
// extend base class
OpenContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void MobNexusContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
OpenContain::xfer( xfer );
// extra slots in use
xfer->xferInt( &m_extraSlotsInUse );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void MobNexusContain::loadPostProcess( void )
{
// extend base class
OpenContain::loadPostProcess();
} // end loadPostProcess

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,638 @@
/*
** 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: OverlordContain.cpp ////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, September, 2002
// Desc: Contain module that acts as transport normally, but when full it redirects queries to the first passenger
// All of this redirection stuff makes it so that while I am normally a transport
// for Overlord subObjects, once I have a passenger, _I_ become a transport of their type.
// So, the answer to this question depends on if it is my passenger asking, or theirs.
// As always, I can't use convience functions that get redirected on a ? like this.
///////////////////////////////////////////////////////////////////////////////////////////////////
// USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "Common/ThingTemplate.h"
#include "Common/ThingFactory.h"
#include "GameClient/ControlBar.h"
#include "GameClient/Drawable.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/OverlordContain.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
OverlordContainModuleData::OverlordContainModuleData()
{
// m_initialPayload.count = 0;
m_experienceSinkForRider = TRUE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void OverlordContainModuleData::buildFieldParse(MultiIniFieldParse& p)
{
TransportContainModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "PayloadTemplateName", INI::parseAsciiStringVectorAppend, NULL, offsetof(OverlordContainModuleData, m_payloadTemplateNameData) },
{ "ExperienceSinkForRider", INI::parseBool, NULL, offsetof(OverlordContainModuleData, m_experienceSinkForRider) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
//void OverlordContainModuleData::parseInitialPayload( INI* ini, void *instance, void *store, const void* /*userData*/ )
//{
// OverlordContainModuleData* self = (OverlordContainModuleData*)instance;
// const char* name = ini->getNextToken();
// const char* countStr = ini->getNextTokenOrNull();
// Int count = countStr ? INI::scanInt(countStr) : 1;
// self->m_initialPayload.name.set(name);
// self->m_initialPayload.count = count;
//}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
OverlordContain::OverlordContain( Thing *thing, const ModuleData *moduleData ) :
TransportContain( thing, moduleData )
{
m_redirectionActivated = FALSE;
m_payloadCreated = FALSE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
OverlordContain::~OverlordContain( void )
{
}
void OverlordContain::onObjectCreated( void )
{
OverlordContain::createPayload();
}
//-------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void OverlordContain::createPayload()
{
OverlordContainModuleData* self = (OverlordContainModuleData*)getOverlordContainModuleData();
// Any number of different passengers can be loaded here at init time
Object* object = getObject();
ContainModuleInterface *contain = object->getContain();
if( contain )
{
contain->enableLoadSounds( FALSE );
TemplateNameList list = self->m_payloadTemplateNameData;
TemplateNameIterator iter = list.begin();
while ( iter != list.end() )
{
const ThingTemplate* temp = TheThingFactory->findTemplate( *iter );
if (temp)
{
Object* payload = TheThingFactory->newObject( temp, object->getTeam() );
if( contain->isValidContainerFor( payload, true ) )
{
contain->addToContain( payload );
}
else
{
DEBUG_CRASH( ( "OverlordContain::createPayload: %s is full, or not valid for the payload %s!", object->getName().str(), self->m_initialPayload.name.str() ) );
}
}
++iter;
}
contain->enableLoadSounds( TRUE );
} // endif contain
m_payloadCreated = TRUE;
}
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void OverlordContain::onBodyDamageStateChange( const DamageInfo* damageInfo,
BodyDamageType oldState,
BodyDamageType newState) ///< state change callback
{
// I can't use any convienience functions, as they will all get routed to the bunker I may carry.
// I want just me.
// Oh, and I don't want this function trying to do death. That is more complicated and will be handled
// on my death.
if( newState != BODY_RUBBLE && m_containListSize == 1 )
{
Object *myGuy = m_containList.front();
myGuy->getBodyModule()->setDamageState( newState );
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ContainModuleInterface *OverlordContain::getRedirectedContain() const
{
// Naturally, I can not use a redirectible convienience function
// to answer if I am redirecting yet.
// If I am empty, say no.
if( m_containListSize < 1 )
return NULL;
if( !m_redirectionActivated )
return NULL;// Shut off early to allow death to happen without my bunker having
// trouble finding me to say goodbye as messages get sucked up the pipe to him.
Object *myGuy = m_containList.front();
if( myGuy )
return myGuy->getContain();
return NULL;// Or say no if they have no contain.
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void OverlordContain::onDie( const DamageInfo *damageInfo )
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
TransportContain::onDie( damageInfo );
return;
}
//Everything is fine if I am empty or carrying a regular guy. If I have a redirected contain
// set up, then I need to handle the order of death explicitly, or things will become confused
// when I stop redirecting in the middle of the process. Or I will get confused as my commands
// get sucked up the pipe.
// So this is an extend that lets me control the order of death.
deactivateRedirectedContain();
Object *myGuy = m_containList.front();
myGuy->kill();
TransportContain::onDie( damageInfo );
}
//-------------------------------------------------------------------------------------------------
void OverlordContain::onDelete( void )
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
TransportContain::onDelete( );
return;
}
// Without my throwing the redirect switch, teardown deletion will get confused and fire off a bunch of asserts
getRedirectedContain()->removeAllContained();
deactivateRedirectedContain();
removeAllContained();
TransportContain::onDelete( );
}
// ------------------------------------------------------------------------------------------------
void OverlordContain::onCapture( Player *oldOwner, Player *newOwner )
{
if( m_containListSize < 1 )
return;
// Need to capture our specific rider. He will then kick passengers out if he is a Transport
Object *myGuy = m_containList.front();
myGuy->setTeam( newOwner->getDefaultTeam() );
}
//-------------------------------------------------------------------------------------------------
Bool OverlordContain::isGarrisonable() const
{
if( getRedirectedContain() == NULL )
return FALSE;
return getRedirectedContain()->isGarrisonable();
}
//-------------------------------------------------------------------------------------------------
Bool OverlordContain::isKickOutOnCapture()
{
if( getRedirectedContain() == NULL )
return FALSE;// Me the Overlord doesn't want to
return getRedirectedContain()->isKickOutOnCapture();
}
//-------------------------------------------------------------------------------------------------
void OverlordContain::addToContainList( Object *obj )
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
TransportContain::addToContainList( obj );
return;
}
getRedirectedContain()->addToContainList( obj );
}
//-------------------------------------------------------------------------------------------------
void OverlordContain::addToContain( Object *obj )
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
TransportContain::addToContain( obj );
return;
}
getRedirectedContain()->addToContain( obj );
}
//-------------------------------------------------------------------------------------------------
/** Remove 'obj' from the m_containList of objects in this module.
* This will trigger an onRemoving event for the object that this module
* is a part of and an onRemovedFrom event for the object being removed */
//-------------------------------------------------------------------------------------------------
void OverlordContain::removeFromContain( Object *obj, Bool exposeStealthUnits )
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
TransportContain::removeFromContain( obj, exposeStealthUnits );
return;
}
getRedirectedContain()->removeFromContain( obj, exposeStealthUnits );
}
//-------------------------------------------------------------------------------------------------
/** Remove all contained objects from the contained list */
//-------------------------------------------------------------------------------------------------
void OverlordContain::removeAllContained( Bool exposeStealthUnits )
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
TransportContain::removeAllContained( exposeStealthUnits );
return;
}
const ContainedItemsList *fullList = getRedirectedContain()->getContainedItemsList();
Object *obj;
ContainedItemsList::const_iterator it;
it = (*fullList).begin();
while( it != (*fullList).end() )
{
obj = *it;
it++;
removeFromContain( obj, exposeStealthUnits );
}
}
//-------------------------------------------------------------------------------------------------
/** Iterate the contained list and call the callback on each of the objects */
//-------------------------------------------------------------------------------------------------
void OverlordContain::iterateContained( ContainIterateFunc func, void *userData, Bool reverse )
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
TransportContain::iterateContained( func, userData, reverse );
return;
}
getRedirectedContain()->iterateContained( func, userData, reverse );
}
//-------------------------------------------------------------------------------------------------
void OverlordContain::onContaining( Object *obj, Bool wasSelected )
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
TransportContain::onContaining( obj, wasSelected );
if ( obj->isKindOf( KINDOF_PORTABLE_STRUCTURE ) )
{
activateRedirectedContain();//Am now carrying something
// And this contain style explicitly sucks XP from our little friend.
if( getOverlordContainModuleData()->m_experienceSinkForRider && obj->getExperienceTracker() )
obj->getExperienceTracker()->setExperienceSink(getObject()->getID());
if ( obj->isKindOf( KINDOF_PORTABLE_STRUCTURE ) && getObject()->testStatus( OBJECT_STATUS_STEALTHED ) )
{
StealthUpdate *myStealth = obj->getStealth();
if ( myStealth )
{
myStealth->receiveGrant( true );
// note to anyone... once stealth is granted to this gattlingcannon ( or such )
// let its own stealthupdate govern the allowedtostealth cases
// a portable structure never gets removed, so...
}
}
}
return;
}
OpenContain::onContaining( obj, wasSelected );
getRedirectedContain()->onContaining( obj, wasSelected );
}
//-------------------------------------------------------------------------------------------------
void OverlordContain::killAllContained( void )
{
// This is a game call meant to clear actual passengers. We don't want it to kill our turret. That'd be wierd.
if( getRedirectedContain() )
{
getRedirectedContain()->killAllContained();
}
}
//-------------------------------------------------------------------------------------------------
void OverlordContain::onRemoving( Object *obj )
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
TransportContain::onRemoving( obj );
return;
}
OpenContain::onRemoving(obj);
getRedirectedContain()->onRemoving( obj );
}
//-------------------------------------------------------------------------------------------------
Bool OverlordContain::isValidContainerFor(const Object* obj, Bool checkCapacity) const
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
return TransportContain::isValidContainerFor( obj, checkCapacity );
return getRedirectedContain()->isValidContainerFor( obj, checkCapacity );
}
//-------------------------------------------------------------------------------------------------
UnsignedInt OverlordContain::getContainCount() const
{
ContainModuleInterface* redir = getRedirectedContain();
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( redir == NULL )
return TransportContain::getContainCount( );
return redir->getContainCount();
}
//-------------------------------------------------------------------------------------------------
Bool OverlordContain::getContainerPipsToShow(Int& numTotal, Int& numFull)
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
numTotal = 0;
numFull = 0;
return false;
}
else
{
return getRedirectedContain()->getContainerPipsToShow(numTotal, numFull);
}
}
//-------------------------------------------------------------------------------------------------
Int OverlordContain::getContainMax( ) const
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
return TransportContain::getContainMax( );
return getRedirectedContain()->getContainMax();
}
//-------------------------------------------------------------------------------------------------
const ContainedItemsList* OverlordContain::getContainedItemsList() const
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
return TransportContain::getContainedItemsList( );
return getRedirectedContain()->getContainedItemsList();
}
//-------------------------------------------------------------------------------------------------
Bool OverlordContain::isEnclosingContainerFor( const Object *obj ) const
{
// All of this redirection stuff makes it so that while I am normally a transport
// for Overlord subObjects, once I have a passenger, _I_ become a transport of their type.
// So, the answer to this question depends on if it is my passenger asking, or theirs.
// As always, I can't use convience functions that get redirected on a ? like this.
if( m_containListSize > 0 && obj == m_containList.front() )
return FALSE;
return TRUE;
}
//-------------------------------------------------------------------------------------------------
Bool OverlordContain::isDisplayedOnControlBar() const
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
return FALSE;//No need to call up inheritance, this is a module based question, and I say no.
return getRedirectedContain()->isDisplayedOnControlBar();
}
//-------------------------------------------------------------------------------------------------
const Object *OverlordContain::friend_getRider() const
{
// The draw order dependency bug for riders means that our draw module needs to cheat to get
//around it. So this is another function that knows it is getting around redirection to ask
// an Overlord specific function.
if( m_containListSize > 0 )
return m_containList.front();
return NULL;
}
//-------------------------------------------------------------------------------------------------
void OverlordContain::activateRedirectedContain()
{
m_redirectionActivated = TRUE;
}
//-------------------------------------------------------------------------------------------------
void OverlordContain::deactivateRedirectedContain()
{
m_redirectionActivated = FALSE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
// if my object gets selected, then my visible passengers should, too
// this gets called from
void OverlordContain::clientVisibleContainedFlashAsSelected()
{
// THIS OVERRIDES GRAHAMS NASTY OVERRIDE THING
// SO WE CAN FLASH THE PORTABLE BUNKER INSTEAD OF ITS OCCUPANTS
const ContainedItemsList* items = TransportContain::getContainedItemsList();
if( items )
{
ContainedItemsList::const_iterator it;
it = items->begin();
while( *it )
{
Object *object = *it;
if ( object && object->isKindOf( KINDOF_PORTABLE_STRUCTURE ) )
{
Drawable *draw = object->getDrawable();
if ( draw )
{
draw->flashAsSelected(); //WOW!
}
}
++it;
}
}
}
Bool OverlordContain::isPassengerAllowedToFire( ObjectID id ) const
{
Object *passenger = TheGameLogic->findObjectByID(id);
if(passenger != NULL)
{
//only allow infantry, and turrets and such. no vehicles.
if(passenger->isKindOf(KINDOF_INFANTRY) == FALSE && passenger->isKindOf(KINDOF_PORTABLE_STRUCTURE) == FALSE)
return FALSE;
}
if ( getObject() && getObject()->getContainedBy() ) // nested containment voids firing, always
return FALSE;
return TransportContain::isPassengerAllowedToFire();
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void OverlordContain::crc( Xfer *xfer )
{
// extend base class
TransportContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void OverlordContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
TransportContain::xfer( xfer );
// redirection activated
xfer->xferBool( &m_redirectionActivated );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void OverlordContain::loadPostProcess( void )
{
// extend base class
TransportContain::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,791 @@
/*
** 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: ParachuteContain.cpp //////////////////////////////////////////////////////////////////////
// Author: Steven Johnson, March 2002
// Desc: Contain module for transport units.
///////////////////////////////////////////////////////////////////////////////////////////////////
// USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/CRCDebug.h"
#include "Common/Player.h"
#include "Common/RandomValue.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/Locomotor.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/ParachuteContain.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameClient/Drawable.h"
const Real NO_START_Z = 1e10;
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// PRIVATE ////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
ParachuteContainModuleData::ParachuteContainModuleData() :
m_pitchRateMax(0),
m_rollRateMax(0),
m_lowAltitudeDamping(0.2f),
m_paraOpenDist(0.0f),
m_freeFallDamagePercent(0.5f),
m_killWhenLandingInWaterSlop(10.0f)
{
}
//-------------------------------------------------------------------------------------------------
void ParachuteContainModuleData::buildFieldParse(MultiIniFieldParse& p)
{
OpenContainModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "PitchRateMax", INI::parseAngularVelocityReal, NULL, offsetof( ParachuteContainModuleData, m_pitchRateMax ) },
{ "RollRateMax", INI::parseAngularVelocityReal, NULL, offsetof( ParachuteContainModuleData, m_rollRateMax ) },
{ "LowAltitudeDamping", INI::parseReal, NULL, offsetof( ParachuteContainModuleData, m_lowAltitudeDamping ) },
{ "ParachuteOpenDist", INI::parseReal, NULL, offsetof( ParachuteContainModuleData, m_paraOpenDist ) },
{ "KillWhenLandingInWaterSlop", INI::parseReal, NULL, offsetof( ParachuteContainModuleData, m_killWhenLandingInWaterSlop ) },
{ "FreeFallDamagePercent", INI::parsePercentToReal, NULL, offsetof( ParachuteContainModuleData, m_freeFallDamagePercent ) },
{ "ParachuteOpenSound", INI::parseAudioEventRTS, NULL, offsetof( ParachuteContainModuleData, m_parachuteOpenSound ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
// PUBLIC /////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ParachuteContain::ParachuteContain( Thing *thing, const ModuleData *moduleData ) :
OpenContain( thing, moduleData )
{
m_opened = false;
m_needToUpdateParaBones = true;
m_needToUpdateRiderBones = true;
m_pitch = 0;
m_roll = 0;
m_pitchRate = 0;
m_rollRate = 0;
m_isLandingOverrideSet = FALSE;
m_startZ = NO_START_Z;
//Added By Sadullah Nader
//Initializations
m_landingOverride.zero();
m_paraAttachBone.zero();
m_paraAttachOffset.zero();
m_paraSwayBone.zero();
m_paraSwayOffset.zero();
m_riderAttachBone.zero();
m_riderAttachOffset.zero();
m_riderSwayBone.zero();
m_riderSwayOffset.zero();
//
const ParachuteContainModuleData* d = getParachuteContainModuleData();
if (d)
{
m_pitchRate = GameLogicRandomValueReal(-d->m_pitchRateMax, d->m_pitchRateMax);
m_rollRate = GameLogicRandomValueReal(-d->m_rollRateMax, d->m_rollRateMax);
}
getObject()->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_PARACHUTING ) );
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
ParachuteContain::~ParachuteContain( void )
{
}
//-------------------------------------------------------------------------------------------------
/**
this is called whenever a drawable is bound to the object.
drawable is NOT guaranteed to be non-null.
*/
void ParachuteContain::onDrawableBoundToObject()
{
Drawable* draw = getObject()->getDrawable();
if (draw)
draw->setDrawableHidden(!m_opened);
}
//-------------------------------------------------------------------------------------------------
void ParachuteContain::calcSwayMtx(const Coord3D* offset, Matrix3D* mtx)
{
mtx->Make_Identity();
mtx->Translate(offset->x, offset->y, offset->z);
mtx->In_Place_Pre_Rotate_X(m_roll);
mtx->In_Place_Pre_Rotate_Y(m_pitch);
mtx->Translate(-offset->x, -offset->y, -offset->z);
}
//-------------------------------------------------------------------------------------------------
void ParachuteContain::updateBonePositions()
{
if (m_needToUpdateParaBones)
{
m_needToUpdateParaBones = false; // yeah, even if not found.
Drawable* parachuteDraw = getObject()->getDrawable();
if (parachuteDraw)
{
if (parachuteDraw->getPristineBonePositions( "PARA_COG", 0, &m_paraSwayBone, NULL, 1) != 1)
{
DEBUG_CRASH(("PARA_COG not found\n"));
m_paraSwayBone.zero();
}
if (parachuteDraw->getPristineBonePositions( "PARA_ATTCH", 0, &m_paraAttachBone, NULL, 1 ) != 1)
{
DEBUG_CRASH(("PARA_ATTCH not found\n"));
m_paraAttachBone.zero();
}
}
//DEBUG_LOG(("updating para bone positions %d...\n",TheGameLogic->getFrame()));
}
if (m_needToUpdateRiderBones)
{
m_needToUpdateRiderBones = false; // yeah, even if not found.
Object* rider = (getContainCount() > 0) ? getContainList().front() : NULL;
Drawable* riderDraw = rider ? rider->getDrawable() : NULL;
if (riderDraw)
{
if (riderDraw->getPristineBonePositions( "PARA_MAN", 0, &m_riderAttachBone, NULL, 1) != 1)
{
//DEBUG_LOG(("*** No parachute-attach bone... using object height!\n"));
m_riderAttachBone.zero();
m_riderAttachBone.z += riderDraw->getDrawableGeometryInfo().getMaxHeightAbovePosition();
}
}
//DEBUG_LOG(("updating rider bone positions %d...\n",TheGameLogic->getFrame()));
}
}
//-------------------------------------------------------------------------------------------------
void ParachuteContain::updateOffsetsFromBones()
{
const Coord3D* objPos = getObject()->getPosition();
getObject()->convertBonePosToWorldPos(&m_paraSwayBone, NULL, &m_paraSwayOffset, NULL);
m_paraSwayOffset.x -= objPos->x;
m_paraSwayOffset.y -= objPos->y;
m_paraSwayOffset.z -= objPos->z;
getObject()->convertBonePosToWorldPos(&m_paraAttachBone, NULL, &m_paraAttachOffset, NULL);
m_paraAttachOffset.x -= objPos->x;
m_paraAttachOffset.y -= objPos->y;
m_paraAttachOffset.z -= objPos->z;
Object* rider = (getContainCount() > 0) ? getContainList().front() : NULL;
if (rider)
{
const Coord3D* riderPos = rider->getPosition();
rider->convertBonePosToWorldPos(&m_riderAttachBone, NULL, &m_riderAttachOffset, NULL);
m_riderAttachOffset.x -= riderPos->x;
m_riderAttachOffset.y -= riderPos->y;
m_riderAttachOffset.z -= riderPos->z;
m_riderAttachOffset.x = m_paraAttachOffset.x - m_riderAttachOffset.x;
m_riderAttachOffset.y = m_paraAttachOffset.y - m_riderAttachOffset.y;
m_riderAttachOffset.z = m_paraAttachOffset.z - m_riderAttachOffset.z;
m_riderSwayOffset.x = m_paraSwayOffset.x - m_riderAttachOffset.x;
m_riderSwayOffset.y = m_paraSwayOffset.y - m_riderAttachOffset.y;
m_riderSwayOffset.z = m_paraSwayOffset.z - m_riderAttachOffset.z;
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/**
can this container contain this kind of object?
and, if checkCapacity is TRUE, does this container have enough space left to hold the given unit?
*/
Bool ParachuteContain::isValidContainerFor(const Object* rider, Bool checkCapacity) const
{
if (!rider)
return false;
// extend functionality
if( OpenContain::isValidContainerFor( rider, checkCapacity ) == false )
return false;
Int transportSlotCount = rider->getTransportSlotCount();
// if 0, this object isn't transportable.
// (exception: infantry are always transportable by parachutes, regardless
// of this.... this allows us to paradrop pilots, but not transport them
// by other means)
if (transportSlotCount == 0 && !rider->isKindOf(KINDOF_INFANTRY) && !rider->isKindOf(KINDOF_PARACHUTABLE))
return false;
// we can only "hold" one item at a time.
if (getContainCount() > 0)
return false;
return true;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void ParachuteContain::containReactToTransformChange()
{
// a bit of a cheese festival here... hidden is a flag, not a counter, so when we are dumped
// from a plane, we might get drawn for a frame before our update is called, meaning we
// should be briefly visible. put this here to ensure we stay hidden appropriately.
Drawable* draw = getObject()->getDrawable();
if (draw)
draw->setDrawableHidden(!m_opened);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UpdateSleepTime ParachuteContain::update( void )
{
OpenContain::update();
Object* parachute = getObject();
if( parachute->isDisabledByType( DISABLED_HELD ) )
{
return UPDATE_SLEEP_NONE; // my, that was easy
}
AIUpdateInterface *parachuteAI = parachute->getAI();
Drawable* draw = parachute->getDrawable();
const ParachuteContainModuleData* d = getParachuteContainModuleData();
Object* rider = (getContainCount() > 0) ? getContainList().front() : NULL;
if (m_startZ == NO_START_Z) {
m_startZ = parachute->getPosition()->z;
Real groundHeight = TheTerrainLogic->getGroundHeight(parachute->getPosition()->x, parachute->getPosition()->y);
if (m_startZ-groundHeight < 2*d->m_paraOpenDist) {
// Oh dear - we ejected too close to the ground, and there isn't enough
// room to open the chute. Well, since it's only a game, we'll fudge
// a little so that the pilot doesn't slam into the ground & stick.
m_startZ = groundHeight+2*d->m_paraOpenDist;
}
}
if (!m_opened)
{
// see if we need to open.
if (fabs(m_startZ - parachute->getPosition()->z) >= d->m_paraOpenDist)
{
m_opened = true;
parachute->clearAndSetModelConditionState(MODELCONDITION_FREEFALL, MODELCONDITION_PARACHUTING);
m_needToUpdateParaBones = true;
if (rider)
{
rider->clearAndSetModelConditionState(MODELCONDITION_FREEFALL, MODELCONDITION_PARACHUTING);
m_needToUpdateRiderBones = true;
AudioEventRTS soundToPlay = d->m_parachuteOpenSound;
soundToPlay.setObjectID( rider->getID() );
TheAudio->addAudioEvent( &soundToPlay );
}
// When a parachute opens, it should look for a good place to land. This could be explicitly set
// by a DeliverPayload, otherwise any place clear is good.
if( parachuteAI )
{
Coord3D target = *parachute->getPosition();
if( m_isLandingOverrideSet )
{
target = m_landingOverride;
if( parachuteAI->getCurLocomotor() )
parachuteAI->getCurLocomotor()->setUltraAccurate( TRUE );
}
else
{
FindPositionOptions fpOptions;
fpOptions.minRadius = 0.0f;
fpOptions.maxRadius = 100.0f;
fpOptions.relationshipObject = NULL;
fpOptions.flags = FPF_NONE;
ThePartitionManager->findPositionAround( &target, &fpOptions, &target );
}
parachuteAI->aiMoveToPosition( &target, CMD_FROM_AI );
}
}
else if ( rider )
rider->clearAndSetModelConditionState( MODELCONDITION_PARACHUTING, MODELCONDITION_FREEFALL );
}
draw->setDrawableHidden(!m_opened);
if (!m_opened || getContainCount() == 0)
{
// unopened, or empty, chutes, don't collide with anything, to simplify
// ejections, paradrops, landings, etc...
parachute->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_COLLISIONS ) );
if (rider)
rider->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_COLLISIONS ) );
}
else
{
// opened/nonempty chutes DO collide...
parachute->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_COLLISIONS ) );
if (rider)
rider->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_COLLISIONS ) );
}
AIUpdateInterface* ai = parachute->getAIUpdateInterface();
if (ai && !parachute->isEffectivelyDead())
{
ai->chooseLocomotorSet(m_opened ? LOCOMOTORSET_NORMAL : LOCOMOTORSET_FREEFALL);
Locomotor* locomotor = ai->getCurLocomotor();
if (locomotor)
{
// damp the swaying a bunch when we get close, so that things land vertically (or nearly so)
Real altitudeDamping = 0;
if (getContainCount() > 0)
{
Object* rider = getContainList().front();
const Real ALTITUDE_DAMP_START = 20.0f;
if (rider->getHeightAboveTerrain() <= ALTITUDE_DAMP_START)
altitudeDamping = d->m_lowAltitudeDamping;
}
if (m_opened)
{
const Real PITCH_STIFFNESS = locomotor->getPitchStiffness();
const Real ROLL_STIFFNESS = locomotor->getRollStiffness();
const Real PITCH_DAMPING = locomotor->getPitchDamping() + altitudeDamping;
const Real ROLL_DAMPING = locomotor->getRollDamping() + altitudeDamping;
m_pitchRate += ((-PITCH_STIFFNESS * m_pitch) + (-PITCH_DAMPING * m_pitchRate)); // spring/damper
m_rollRate += ((-ROLL_STIFFNESS * m_roll) + (-ROLL_DAMPING * m_rollRate)); // spring/damper
m_pitch += m_pitchRate;
m_roll += m_rollRate;
if( m_isLandingOverrideSet )
{
// Need to wait until after opening to do this, or else we are sending the message to the Freefall locomotor.
locomotor->setCloseEnoughDist( 10.0 );
locomotor->setCloseEnoughDist3D( FALSE );
}
}
if (draw)
{
updateBonePositions();
updateOffsetsFromBones();
Matrix3D tmp;
calcSwayMtx(&m_paraSwayOffset, &tmp);
draw->setInstanceMatrix(&tmp);
}
positionContainedObjectsRelativeToContainer();
}
}
// allow us to land on bridges!
const Coord3D* paraPos = getObject()->getPosition();
PathfindLayerEnum newLayer = TheTerrainLogic->getHighestLayerForDestination(paraPos);
getObject()->setLayer(newLayer);
if (rider)
rider->setLayer(newLayer);
// If we have lost our passenger for whatever reason, die early. Otherwise we just sit around forever.
if( getContainCount() == 0 )
getObject()->kill();
// the collide system doesn't always collide us with the ground if we fall into water.
// so force the issue.
Real waterZ;
if (!getObject()->isEffectivelyDead()
&& getObject()->getLayer() == LAYER_GROUND
&& TheTerrainLogic->isUnderwater(paraPos->x, paraPos->y, &waterZ)
&& (paraPos->z - waterZ) < d->m_killWhenLandingInWaterSlop)
{
getObject()->kill();
}
return UPDATE_SLEEP_NONE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void ParachuteContain::onContaining( Object *rider, Bool wasSelected )
{
OpenContain::onContaining( rider, wasSelected );
// objects inside a transport are held
rider->setDisabled( DISABLED_HELD );
rider->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_PARACHUTING ) );
rider->clearAndSetModelConditionState(MODELCONDITION_PARACHUTING, MODELCONDITION_FREEFALL);
m_needToUpdateRiderBones = true;
// position him correctly.
positionRider(rider);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void ParachuteContain::onRemoving( Object *rider )
{
OpenContain::onRemoving(rider);
const ParachuteContainModuleData* d = getParachuteContainModuleData();
// object is no longer held inside a transport
rider->clearDisabled( DISABLED_HELD );
rider->clearStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_PARACHUTING ) );
// mark parachute as "no-collisions"... it is just ephemeral at this point,
// and having the chute collide with the soldier (and both bounce apart) is
// just dumb-lookin'...
getObject()->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_NO_COLLISIONS ) );
// position him correctly.
positionRider(rider);
rider->clearModelConditionFlags(MAKE_MODELCONDITION_MASK2(MODELCONDITION_FREEFALL, MODELCONDITION_PARACHUTING));
m_needToUpdateRiderBones = true;
// temporarily mark the guy as being allowed to fall
// (overriding his locomotor's stick-to-ground attribute).
// this will be reset (by PhysicsBehavior) when he touches the ground.
PhysicsBehavior* physics = rider->getPhysics();
if (physics)
{
physics->setAllowToFall(true);
Coord3D force;
force.zero();
physics->applyForce(&force); // force its physics to wake up... should be done when DISABLED_HELD is cleared, but it not, and scared to do it now.
}
AIUpdateInterface* riderAI = rider->getAIUpdateInterface();
if (riderAI)
{
Player* controller = rider->getControllingPlayer();
if (controller && controller->isSkirmishAIPlayer())
{
riderAI->aiHunt(CMD_FROM_AI); // hunt, as per Dustin's request.
}
else
{
bool hasRallyPoint = false;
// Get the transport of the rider
Object *transport = TheGameLogic->findObjectByID(rider->getProducerID());
if (transport)
{
// Get the building that produced the transport
Object *transportProducer = TheGameLogic->findObjectByID(transport->getProducerID());
if (transportProducer)
{
// See if we need to set a rally point for the object being parachuted
ExitInterface* exitInterface = transportProducer->getObjectExitInterface();
if (exitInterface && exitInterface->useSpawnRallyPoint())
{
exitInterface->exitObjectViaDoor(rider, DOOR_1);
hasRallyPoint = true;
}
}
}
if (!hasRallyPoint)
riderAI->aiIdle(CMD_FROM_AI); // become idle.
}
}
// if we land in the water, we die. alas.
const Coord3D* riderPos = rider->getPosition();
Real waterZ, terrainZ;
if (TheTerrainLogic->isUnderwater(riderPos->x, riderPos->y, &waterZ, &terrainZ)
&& riderPos->z <= waterZ + d->m_killWhenLandingInWaterSlop
&& rider->getLayer() == LAYER_GROUND)
{
// don't call kill(); do it manually, so we can specify DEATH_FLOODED
DamageInfo damageInfo;
damageInfo.in.m_damageType = DAMAGE_WATER; // use this instead of UNRESISTABLE so we don't get a dusty damage effect
damageInfo.in.m_deathType = DEATH_FLOODED;
damageInfo.in.m_sourceID = INVALID_ID;
damageInfo.in.m_amount = HUGE_DAMAGE_AMOUNT;
rider->attemptDamage( &damageInfo );
}
// Kill if we landed on impassable ground
Int cellX = REAL_TO_INT( rider->getPosition()->x / PATHFIND_CELL_SIZE );
Int cellY = REAL_TO_INT( rider->getPosition()->y / PATHFIND_CELL_SIZE );
PathfindCell* cell = TheAI->pathfinder()->getCell( rider->getLayer(), cellX, cellY );
PathfindCell::CellType cellType = cell ? cell->getType() : PathfindCell::CELL_IMPASSABLE;
// If we land outside the map from a faulty parachute, we die too.
// Otherwise we exist outside the PartitionManger like a cheater.
if( rider->isOffMap()
|| (cellType == PathfindCell::CELL_CLIFF)
|| (cellType == PathfindCell::CELL_WATER)
|| (cellType == PathfindCell::CELL_IMPASSABLE) )
{
// The Paradrop command was legal, the parachute destination was legal, but the parachute
// can still fail to adjust back on the map. SO this is the place to cap the cheater.
rider->kill();
}
// Note: for future enhancement of this feature, we should test the object against the cell type he is on,
// using obj->getAI()->hasLocomotorForSurface( __ ). We cshould not assume here that the parachutist can not
// find happiness on cliffs or water or whatever.
}
//-------------------------------------------------------------------------------------------------
void ParachuteContain::positionRider(Object* rider)
{
updateBonePositions();
updateOffsetsFromBones();
Coord3D pos = *getObject()->getPosition();
///DUMPCOORD3D(&pos);
pos.x += m_riderAttachOffset.x;
pos.y += m_riderAttachOffset.y;
pos.z += m_riderAttachOffset.z;
//DUMPCOORD3D(&pos);
rider->setPosition(&pos);
Real alt = rider->getHeightAboveTerrain();
if (alt < 0.0f)
{
// don't let him go below ground.
pos.z -= alt;
rider->setPosition(&pos);
}
rider->setOrientation(getObject()->getOrientation());
Drawable* draw = rider->getDrawable();
if (draw)
{
if( rider->isDisabledByType( DISABLED_HELD ) )
{
Matrix3D tmp;
calcSwayMtx(&m_riderSwayOffset, &tmp);
draw->setInstanceMatrix(&tmp);
}
else
{
draw->setInstanceMatrix(NULL);
}
}
}
//-------------------------------------------------------------------------------------------------
void ParachuteContain::positionContainedObjectsRelativeToContainer()
{
for(ContainedItemsList::const_iterator it = getContainList().begin(); it != getContainList().end(); ++it)
{
positionRider(*it);
}
}
//-------------------------------------------------------------------------------------------------
void ParachuteContain::setOverrideDestination( const Coord3D *override )
{
// Instead of trying to float straight down, I am going to nail this spot.
m_landingOverride = *override;
m_isLandingOverrideSet = TRUE;
}
//-------------------------------------------------------------------------------------------------
void ParachuteContain::onDie( const DamageInfo * damageInfo )
{
// if we are airborne when killed, the guy falls screaming to his death...
if (getObject()->isSignificantlyAboveTerrain())
{
Object* rider = (getContainCount() > 0) ? getContainList().front() : NULL;
if (rider)
{
removeAllContained();
const ParachuteContainModuleData* d = getParachuteContainModuleData();
if (d->m_freeFallDamagePercent > 0.0f)
{
// do some damage just for losing your parachute.
// not very realistic, but practical to help ensure that
// you really do die from going "splat" on the ground.
DamageInfo extraDamageInfo;
extraDamageInfo.in.m_damageType = DAMAGE_FALLING;
extraDamageInfo.in.m_deathType = DEATH_SPLATTED;
extraDamageInfo.in.m_sourceID = damageInfo->in.m_sourceID;
extraDamageInfo.in.m_amount = rider->getBodyModule()->getMaxHealth() * d->m_freeFallDamagePercent;
rider->attemptDamage(&extraDamageInfo);
}
PhysicsBehavior* physics = rider->getPhysics();
if (physics)
{
physics->setAllowToFall(true);
physics->setIsInFreeFall(true); // bwah ha ha
Coord3D force;
force.zero();
physics->applyForce(&force); // force its physics to wake up... should be done when DISABLED_HELD is cleared, but it not, and scared to do it now.
}
}
}
OpenContain::onDie(damageInfo);
}
//-------------------------------------------------------------------------------------------------
void ParachuteContain::onCollide( Object *other, const Coord3D *loc, const Coord3D *normal )
{
// Note that other == null means "collide with ground"
if( other == NULL )
{
// if we're in a container (eg, a transport plane), just ignore this...
if( getObject()->getContainedBy() != NULL )
return;
removeAllContained();
// TheGameLogic->destroyObject(obj);
// kill it, so that the chute's SlowDeath will trigger!
getObject()->kill();
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void ParachuteContain::crc( Xfer *xfer )
{
// extend base class
OpenContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void ParachuteContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
OpenContain::xfer( xfer );
// pitch
xfer->xferReal( &m_pitch );
// roll
xfer->xferReal( &m_roll );
// pitch rage
xfer->xferReal( &m_pitchRate );
// roll rate
xfer->xferReal( &m_rollRate );
// start Z
xfer->xferReal( &m_startZ );
// is landing override set
xfer->xferBool( &m_isLandingOverrideSet );
// landing override
xfer->xferCoord3D( &m_landingOverride );
// rider attach bone
xfer->xferCoord3D( &m_riderAttachBone );
// rider sway bone
xfer->xferCoord3D( &m_riderSwayBone );
// para attach bone
xfer->xferCoord3D( &m_paraAttachBone );
// para sway bone
xfer->xferCoord3D( &m_paraSwayBone );
// rider attach offset
xfer->xferCoord3D( &m_riderAttachOffset );
// rider sway offset
xfer->xferCoord3D( &m_riderSwayOffset );
// para attach offset
xfer->xferCoord3D( &m_paraAttachOffset );
// para sway offset
xfer->xferCoord3D( &m_paraSwayOffset );
// need to update rider bones
xfer->xferBool( &m_needToUpdateRiderBones );
// need to update para bones
xfer->xferBool( &m_needToUpdateParaBones );
// opened
xfer->xferBool( &m_opened );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void ParachuteContain::loadPostProcess( void )
{
// extend base class
OpenContain::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,158 @@
/*
** 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: RailedTransportContain.cpp ///////////////////////////////////////////////////////////////
// Author: Colin Day, August 2002
// Desc: Railed Transport Contain Module
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/RailedTransportContain.h"
#include "GameLogic/Module/RailedTransportDockUpdate.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
static RailedTransportDockUpdateInterface *getRailedTransportDockUpdateInterface( Object *obj )
{
// sanity
if( obj == NULL )
return NULL;
// find us our dock interface
RailedTransportDockUpdateInterface *rtdui = NULL;
for( BehaviorModule **u = obj->getBehaviorModules(); *u; ++u )
if( (rtdui = (*u)->getRailedTransportDockUpdateInterface()) != NULL )
break;
return rtdui;
} // end getRailedTransportDockUpdateInterface
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
RailedTransportContain::RailedTransportContain( Thing *thing, const ModuleData *moduleData )
: TransportContain( thing, moduleData )
{
} // end RailedTransportContain
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
RailedTransportContain::~RailedTransportContain( void )
{
} // end ~RailedTransportContain
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void RailedTransportContain::onRemoving( Object *obj )
{
// extend functionality
TransportContain::onRemoving( obj );
// once we have removed all our contents we are "open" for docking again for more transportation
if( getContainCount() == 0 )
{
DockUpdateInterface *dui = getObject()->getDockUpdateInterface();
if( dui )
dui->setDockOpen( TRUE );
} // end if
} // end onRemoving
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
Bool RailedTransportContain::isSpecificRiderFreeToExit( Object *obj )
{
Object *us = getObject();
// if we are in transit we cannot exit, when we're in transit, the dock is closed
DockUpdateInterface *dui = us->getDockUpdateInterface();
if( dui && dui->isDockOpen() == FALSE )
return FALSE;
// we can now exit, note we're not extending the base class cause *we* handle it all
return TRUE;
} // end isSpecificRiderFreeToExit
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void RailedTransportContain::exitObjectViaDoor( Object *newObj, ExitDoorType exitDoor )
{
RailedTransportDockUpdateInterface *rtdui = getRailedTransportDockUpdateInterface();
if( rtdui == NULL )
return;
// tell the railed dock to exit ONE object, this one
rtdui->unloadSingleObject( newObj );
} // end exitObjectViaDoor
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void RailedTransportContain::crc( Xfer *xfer )
{
// extend base class
TransportContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void RailedTransportContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
TransportContain::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void RailedTransportContain::loadPostProcess( void )
{
// extend base class
TransportContain::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,495 @@
/*
** 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: RiderChangeContain.cpp //////////////////////////////////////////////////////////////////////
// Author: Kris Morness, May 2003
// Desc: Contain module for the combat bike (transport that switches units).
///////////////////////////////////////////////////////////////////////////////////////////////////
// USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_LOCOMOTORSET_NAMES //Gain access to TheLocomotorSetNames[]
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/ThingTemplate.h"
#include "Common/ThingFactory.h"
#include "Common/Xfer.h"
#include "GameClient/ControlBar.h"
#include "GameClient/Drawable.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/AI.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Object.h"
#include "GameLogic/Locomotor.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#include "GameLogic/Module/StealthUpdate.h"
#include "GameLogic/Module/RiderChangeContain.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
RiderChangeContainModuleData::RiderChangeContainModuleData()
{
m_scuttleFrames = 0;
m_scuttleState = MODELCONDITION_TOPPLED;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void RiderChangeContainModuleData::parseRiderInfo( INI* ini, void *instance, void *store, const void* /*userData*/ )
{
RiderInfo* rider = (RiderInfo*)store;
const char* name = ini->getNextToken();
//Template name
rider->m_templateName.format( name );
//Model condition state
INI::parseIndexList( ini, instance, &(rider->m_modelConditionFlagType), ModelConditionFlags::getBitNames() );
//Weaponset
INI::parseIndexList( ini, instance, &(rider->m_weaponSetFlag), WeaponSetFlags::getBitNames() );
//Object status
INI::parseIndexList( ini, instance, &(rider->m_objectStatusType), ObjectStatusMaskType::getBitNames() );
//Command set override
name = ini->getNextToken();
rider->m_commandSet.format( name );
//Locomotor set type
rider->m_locomotorSetType = (LocomotorSetType)INI::scanIndexList( ini->getNextToken(), TheLocomotorSetNames );
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void RiderChangeContainModuleData::buildFieldParse(MultiIniFieldParse& p)
{
TransportContainModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "Rider1", parseRiderInfo, NULL, offsetof( RiderChangeContainModuleData, m_riders[0] ) },
{ "Rider2", parseRiderInfo, NULL, offsetof( RiderChangeContainModuleData, m_riders[1] ) },
{ "Rider3", parseRiderInfo, NULL, offsetof( RiderChangeContainModuleData, m_riders[2] ) },
{ "Rider4", parseRiderInfo, NULL, offsetof( RiderChangeContainModuleData, m_riders[3] ) },
{ "Rider5", parseRiderInfo, NULL, offsetof( RiderChangeContainModuleData, m_riders[4] ) },
{ "Rider6", parseRiderInfo, NULL, offsetof( RiderChangeContainModuleData, m_riders[5] ) },
{ "Rider7", parseRiderInfo, NULL, offsetof( RiderChangeContainModuleData, m_riders[6] ) },
{ "Rider8", parseRiderInfo, NULL, offsetof( RiderChangeContainModuleData, m_riders[7] ) },
{ "ScuttleDelay", INI::parseDurationUnsignedInt, NULL, offsetof( RiderChangeContainModuleData, m_scuttleFrames ) },
{ "ScuttleStatus", INI::parseIndexList, ModelConditionFlags::getBitNames(), offsetof( RiderChangeContainModuleData, m_scuttleState ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
// PRIVATE ////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Int RiderChangeContain::getContainMax( void ) const
{
if (getRiderChangeContainModuleData())
return getRiderChangeContainModuleData()->m_slotCapacity;
return 0;
}
// PUBLIC /////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
RiderChangeContain::RiderChangeContain( Thing *thing, const ModuleData *moduleData ) :
TransportContain( thing, moduleData )
{
m_extraSlotsInUse = 0;
m_frameExitNotBusy = 0;
m_containing = FALSE;
m_scuttledOnFrame = 0;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
RiderChangeContain::~RiderChangeContain( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/**
can this container contain this kind of object?
and, if checkCapacity is TRUE, does this container have enough space left to hold the given unit?
*/
Bool RiderChangeContain::isValidContainerFor(const Object* rider, Bool checkCapacity) const
{
//Don't check capacity because our rider will kick the other rider out!
if( TransportContain::isValidContainerFor( rider, FALSE ) )
{
if( m_scuttledOnFrame != 0 )
{
//Scuttled... too late!
return FALSE;
}
//We can enter this bike... but now we need to extend the base functionality by limiting
//which infantry can enter.
const RiderChangeContainModuleData *data = getRiderChangeContainModuleData();
for( int i = 0; i < MAX_RIDERS; i++ )
{
const ThingTemplate *thing = TheThingFactory->findTemplate( data->m_riders[ i ].m_templateName );
if( thing->isEquivalentTo( rider->getTemplate() ) )
{
//We found a valid rider, so return success.
return TRUE;
}
}
}
return FALSE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void RiderChangeContain::onContaining( Object *rider, Bool wasSelected )
{
Object *obj = getObject();
m_containing = TRUE;
//Remove our existing rider
if( m_payloadCreated )
{
obj->getAI()->aiEvacuateInstantly( TRUE, CMD_FROM_AI );
}
//If the rider is currently selected, transfer selection to the container and preserve other units
//that may be already selected. Note that containing the rider will automatically cause it to be
//deselected, so all we have to do is select the container (if not already selected)!
Drawable *containDraw = getObject()->getDrawable();
if( containDraw && wasSelected && !containDraw->isSelected() )
{
//Create the selection message
GameMessage *teamMsg = TheMessageStream->appendMessage( GameMessage::MSG_CREATE_SELECTED_GROUP );
teamMsg->appendBooleanArgument( FALSE );// not creating new team so pass false
teamMsg->appendObjectIDArgument( getObject()->getID() );
TheInGameUI->selectDrawable( containDraw );
TheInGameUI->setDisplayedMaxWarning( FALSE );
}
//Find the rider in the list and set the appropriate model condition
const RiderChangeContainModuleData *data = getRiderChangeContainModuleData();
for( int i = 0; i < MAX_RIDERS; i++ )
{
const ThingTemplate *thing = TheThingFactory->findTemplate( data->m_riders[ i ].m_templateName );
if( thing->isEquivalentTo( rider->getTemplate() ) )
{
//This is our rider, so set the correct model condition.
obj->setModelConditionState( data->m_riders[ i ].m_modelConditionFlagType );
//Also set the correct weaponset flag
obj->setWeaponSetFlag( data->m_riders[ i ].m_weaponSetFlag );
//Also set the object status
obj->setStatus( MAKE_OBJECT_STATUS_MASK( data->m_riders[ i ].m_objectStatusType ) );
//Set the new commandset override
obj->setCommandSetStringOverride( data->m_riders[ i ].m_commandSet );
TheControlBar->markUIDirty(); // Refresh the UI in case we are selected
//Change the locomotor.
AIUpdateInterface* ai = obj->getAI();
if( ai )
{
ai->chooseLocomotorSet( data->m_riders[ i ].m_locomotorSetType );
}
if( obj->getStatusBits().test( OBJECT_STATUS_STEALTHED ) )
{
StealthUpdate* stealth = obj->getStealth();
if( stealth )
{
stealth->markAsDetected();
}
}
//Transfer experience from the rider to the bike.
ExperienceTracker *riderTracker = rider->getExperienceTracker();
ExperienceTracker *bikeTracker = obj->getExperienceTracker();
bikeTracker->setVeterancyLevel( riderTracker->getVeterancyLevel(), FALSE );
riderTracker->setExperienceAndLevel( 0, FALSE );
break;
}
}
//Extend base class
TransportContain::onContaining( rider, wasSelected );
m_containing = FALSE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void RiderChangeContain::onRemoving( Object *rider )
{
Object *bike = getObject();
//Note if the bike dies, the rider dies too.
if( bike->isEffectivelyDead() )
{
TheGameLogic->destroyObject( rider );
return;
}
if( m_payloadCreated )
{
//Extend base class
TransportContain::onRemoving( rider );
}
//Find the rider in the list and clear various data.
const RiderChangeContainModuleData *data = getRiderChangeContainModuleData();
for( int i = 0; i < MAX_RIDERS; i++ )
{
const ThingTemplate *thing = TheThingFactory->findTemplate( data->m_riders[ i ].m_templateName );
if( thing->isEquivalentTo( rider->getTemplate() ) )
{
//This is our rider, so clear the current model condition.
bike->clearModelConditionFlags( MAKE_MODELCONDITION_MASK2( data->m_riders[ i ].m_modelConditionFlagType, MODELCONDITION_DOOR_1_CLOSING ) );
//Also clear the current weaponset flag
bike->clearWeaponSetFlag( data->m_riders[ i ].m_weaponSetFlag );
//Also clear the object status
bike->clearStatus( MAKE_OBJECT_STATUS_MASK( data->m_riders[ i ].m_objectStatusType ) );
if( rider->getControllingPlayer() != NULL )
{
//Wow, completely unforseeable game teardown order crash. SetVeterancyLevel results in a call to player
//about upgrade masks. So if we have a null player, it is game teardown, so don't worry about transfering exp.
//Transfer experience from the bike to the rider.
ExperienceTracker *riderTracker = rider->getExperienceTracker();
ExperienceTracker *bikeTracker = bike->getExperienceTracker();
riderTracker->setVeterancyLevel( bikeTracker->getVeterancyLevel(), FALSE );
bikeTracker->setExperienceAndLevel( 0, FALSE );
}
break;
}
}
//If we're not replacing the rider, then if the cycle is selected, transfer selection
//to the rider getting off (because the bike is gonna blow).
if( !m_containing )
{
Drawable *containDraw = bike->getDrawable();
Drawable *riderDraw = rider->getDrawable();
if( containDraw && riderDraw )
{
//Create the selection message for the rider if it's ours and SELECTED!
if( bike->getControllingPlayer() == ThePlayerList->getLocalPlayer() && containDraw->isSelected() )
{
GameMessage *teamMsg = TheMessageStream->appendMessage( GameMessage::MSG_CREATE_SELECTED_GROUP );
teamMsg->appendBooleanArgument( FALSE );// not creating new team so pass false
teamMsg->appendObjectIDArgument( rider->getID() );
TheInGameUI->selectDrawable( riderDraw );
TheInGameUI->setDisplayedMaxWarning( FALSE );
//Create the de-selection message for the container
teamMsg = TheMessageStream->appendMessage( GameMessage::MSG_REMOVE_FROM_SELECTED_GROUP );
teamMsg->appendObjectIDArgument( bike->getID() );
TheInGameUI->deselectDrawable( containDraw );
}
//Finally, scuttle the bike so nobody else can use it! <Design Spec>
m_scuttledOnFrame = TheGameLogic->getFrame();
bike->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_UNSELECTABLE ) );
bike->setModelConditionState( data->m_scuttleState );
if( !bike->getAI()->isMoving() )
{
bike->setStatus( MAKE_OBJECT_STATUS_MASK( OBJECT_STATUS_IMMOBILE ) );
}
}
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void RiderChangeContain::createPayload()
{
// extend base class
TransportContain::createPayload();
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime RiderChangeContain::update()
{
if( m_scuttledOnFrame != 0 )
{
//Bike in the process of getting scuttled.
const RiderChangeContainModuleData *data = getRiderChangeContainModuleData();
UnsignedInt now = TheGameLogic->getFrame();
if( m_scuttledOnFrame + data->m_scuttleFrames <= now )
{
//We have scuttled the bike (at least as far as tipping it over via scuttle animation. Now
//kill the bike in a way that will cause it to sink into the ground without any real destruction.
getObject()->kill( DAMAGE_UNRESISTABLE, DEATH_TOPPLED ); //Sneaky, eh? Toppled heheh.
}
}
// extend base class
return TransportContain::update(); //extend
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void RiderChangeContain::unreserveDoorForExit( ExitDoorType exitDoor )
{
/* nothing */
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void RiderChangeContain::killRidersWhoAreNotFreeToExit()
{
// extend base class
TransportContain::killRidersWhoAreNotFreeToExit();
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
Bool RiderChangeContain::isSpecificRiderFreeToExit(Object* specificObject)
{
// extend base class
return TransportContain::isSpecificRiderFreeToExit( specificObject );
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
ExitDoorType RiderChangeContain::reserveDoorForExit( const ThingTemplate* objType, Object *specificObject )
{
// extend base class
return TransportContain::reserveDoorForExit( objType, specificObject );
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
Bool RiderChangeContain::isExitBusy() const ///< Contain style exiters are getting the ability to space out exits, so ask this before reserveDoor as a kind of no-commitment check.
{
// extend base class
return FALSE; //return TransportContain::isExitBusy();
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void RiderChangeContain::onCapture( Player *oldOwner, Player *newOwner )
{
// extend base class
TransportContain::onCapture( oldOwner, newOwner );
}
//-------------------------------------------------------------------------------------------------
Bool RiderChangeContain::getContainerPipsToShow(Int& numTotal, Int& numFull)
{
//Don't show any pips for motorcycles as they always have one rider unless dead!
numTotal = 0;
numFull = 0;
return false;
}
//-------------------------------------------------------------------------------------------------
const Object *RiderChangeContain::friend_getRider() const
{
if( m_containListSize > 0 ) // Yes, this does assume that infantry never ride double on the bike
return m_containList.front();
return NULL;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void RiderChangeContain::crc( Xfer *xfer )
{
// extend base class
TransportContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void RiderChangeContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
TransportContain::xfer( xfer );
// payload created
xfer->xferBool( &m_payloadCreated );
// extra slots in use
xfer->xferInt( &m_extraSlotsInUse );
// frame exit not busy
xfer->xferUnsignedInt( &m_frameExitNotBusy );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void RiderChangeContain::loadPostProcess( void )
{
// extend base class
TransportContain::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,709 @@
/*
** 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: TransportContain.cpp //////////////////////////////////////////////////////////////////////
// Author: Steven Johnson, March 2002
// Desc: Contain module for transport units.
///////////////////////////////////////////////////////////////////////////////////////////////////
// USER INCLUDES //////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/ThingTemplate.h"
#include "Common/ThingFactory.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/AI.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/Locomotor.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/PhysicsUpdate.h"
#include "GameLogic/Module/StealthUpdate.h"
#include "GameLogic/Module/TransportContain.h"
#include "GameLogic/Object.h"
#include "GameLogic/Weapon.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
TransportContainModuleData::TransportContainModuleData()
{
m_slotCapacity = 0;
m_scatterNearbyOnExit = true;
m_orientLikeContainerOnExit = false;
m_keepContainerVelocityOnExit = false;
m_goAggressiveOnExit = FALSE;
m_armedRidersUpgradeWeaponSet = FALSE;
m_resetMoodCheckTimeOnExit = true;
m_destroyRidersWhoAreNotFreeToExit = false;
m_exitPitchRate = 0.0f;
m_initialPayload.count = 0;
m_healthRegen = 0.0f;
m_exitDelay = 0;
m_isDelayExitInAir = FALSE;
//
// by default we say that transports can have infantry inside them, this will be totally
// overwritten by any data provided from the INI entry tho
//
m_allowInsideKindOf = MAKE_KINDOF_MASK( KINDOF_INFANTRY );
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TransportContainModuleData::parseInitialPayload( INI* ini, void *instance, void *store, const void* /*userData*/ )
{
TransportContainModuleData* self = (TransportContainModuleData*)instance;
const char* name = ini->getNextToken();
const char* countStr = ini->getNextTokenOrNull();
Int count = countStr ? INI::scanInt(countStr) : 1;
self->m_initialPayload.name.set(name);
self->m_initialPayload.count = count;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TransportContainModuleData::buildFieldParse(MultiIniFieldParse& p)
{
OpenContainModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "Slots", INI::parseInt, NULL, offsetof( TransportContainModuleData, m_slotCapacity ) },
{ "ScatterNearbyOnExit", INI::parseBool, NULL, offsetof( TransportContainModuleData, m_scatterNearbyOnExit ) },
{ "OrientLikeContainerOnExit", INI::parseBool, NULL, offsetof( TransportContainModuleData, m_orientLikeContainerOnExit ) },
{ "KeepContainerVelocityOnExit", INI::parseBool, NULL, offsetof( TransportContainModuleData, m_keepContainerVelocityOnExit ) },
{ "GoAggressiveOnExit", INI::parseBool, NULL, offsetof( TransportContainModuleData, m_goAggressiveOnExit ) },
{ "ResetMoodCheckTimeOnExit", INI::parseBool, NULL, offsetof( TransportContainModuleData, m_resetMoodCheckTimeOnExit ) },
{ "DestroyRidersWhoAreNotFreeToExit", INI::parseBool, NULL, offsetof( TransportContainModuleData, m_destroyRidersWhoAreNotFreeToExit ) },
{ "ExitBone", INI::parseAsciiString, NULL, offsetof( TransportContainModuleData, m_exitBone ) },
{ "ExitPitchRate", INI::parseAngularVelocityReal, NULL, offsetof( TransportContainModuleData, m_exitPitchRate ) },
{ "InitialPayload", parseInitialPayload, NULL, 0 },
{ "HealthRegen%PerSec", INI::parseReal, NULL, offsetof( TransportContainModuleData, m_healthRegen ) },
{ "ExitDelay", INI::parseDurationUnsignedInt, NULL, offsetof( TransportContainModuleData, m_exitDelay ) },
{ "ArmedRidersUpgradeMyWeaponSet", INI::parseBool, NULL, offsetof( TransportContainModuleData, m_armedRidersUpgradeWeaponSet ) },
{ "DelayExitInAir", INI::parseBool, NULL, offsetof( TransportContainModuleData, m_isDelayExitInAir ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
// PRIVATE ////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
Int TransportContain::getContainMax( void ) const
{
if (getTransportContainModuleData())
return getTransportContainModuleData()->m_slotCapacity;
return 0;
}
// PUBLIC /////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
TransportContain::TransportContain( Thing *thing, const ModuleData *moduleData ) :
OpenContain( thing, moduleData )
{
m_extraSlotsInUse = 0;
m_frameExitNotBusy = 0;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
TransportContain::~TransportContain( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/**
can this container contain this kind of object?
and, if checkCapacity is TRUE, does this container have enough space left to hold the given unit?
*/
Bool TransportContain::isValidContainerFor(const Object* rider, Bool checkCapacity) const
{
// sanity
if (!rider)
return false;
// The point of this new code is to determine when something is a "fake" container, to
// look at the object inside of it to use that as the valid check. There is a case, when a
// paratrooper (an infantry contained in a parachute). In this case, when we pass this object
// to contain in a transport plane, we want to check the infantry, not the parachute.
if (rider->getContain() && rider->getContain()->isSpecialZeroSlotContainer())
{
// Report the first thing inside it!
const ContainedItemsList *items = rider->getContain()->getContainedItemsList();
if (items && !items->empty())
{
if (items->front())
{
// Replace the object we are checking with the *first* object contained within it.
rider = items->front();
}
}
}
// extend functionality
if( OpenContain::isValidContainerFor( rider, checkCapacity ) == false )
return false;
// // only allied objects can be transported.
// // order matters: we want to know if I consider it to be an ally, not vice versa
// if (getObject()->getRelationship(rider) != ALLIES)
// return false;
// no... actually, only OUR OWN units can be transported.
if (rider->getControllingPlayer() != getObject()->getControllingPlayer())
return false;
Int transportSlotCount = rider->getTransportSlotCount();
// if 0, this object isn't transportable.
if (transportSlotCount == 0)
return false;
if (checkCapacity)
{
Int containMax = getContainMax();
Int containCount = getContainCount();
return (m_extraSlotsInUse + containCount + transportSlotCount <= containMax);
}
else
{
return true;
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void TransportContain::letRidersUpgradeWeaponSet( void )
{
const TransportContainModuleData * d = getTransportContainModuleData();
if ( ! d->m_armedRidersUpgradeWeaponSet )
return;
Object *self = getObject();
if ( self == NULL )
return;
Bool anyRiderHasViableWeapon = FALSE;
const ContainedItemsList* riderList = getContainedItemsList();
if( riderList )
{
ContainedItemsList::const_iterator it;
it = riderList->begin();
while( *it )
{
Object *rider = *it;
//Advance to the next iterator
it++;
if ( rider )
{
if(rider->isKindOf(KINDOF_INFANTRY) == false)
continue;
Weapon *weapon = NULL;
for ( Int w = PRIMARY_WEAPON; w < WEAPONSLOT_COUNT; ++ w )
{
weapon = rider->getWeaponInWeaponSlot( (WeaponSlotType)w );
if ( weapon )
{
if ( weapon->getTemplate()->isContactWeapon() == FALSE && weapon->isDamageWeapon() == TRUE ) // THIS MAY NEED TO CHECK MORE WEAPON ATTRIBUTES TO WORK BEST
{
anyRiderHasViableWeapon = TRUE;
break;
}
}
}
}//end if rider
}
}
if ( anyRiderHasViableWeapon )
self->setWeaponSetFlag( WEAPONSET_PLAYER_UPGRADE );
else
self->clearWeaponSetFlag( WEAPONSET_PLAYER_UPGRADE );
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void TransportContain::onContaining( Object *rider, Bool wasSelected )
{
OpenContain::onContaining( rider, wasSelected );
// objects inside a transport are held
rider->setDisabled( DISABLED_HELD );
Int transportSlotCount = rider->getTransportSlotCount();
DEBUG_ASSERTCRASH(transportSlotCount > 0, ("Hmm, this object isnt transportable"));
m_extraSlotsInUse += transportSlotCount - 1;
DEBUG_ASSERTCRASH(m_extraSlotsInUse >= 0 && m_extraSlotsInUse + getContainCount() <= getContainMax(), ("Hmm, bad slot count"));
//
// when we go from holding nothing to holding something we have a model condition
// to visually show the change
//
if( getContainCount() == 1 )
{
Drawable *draw = getObject()->getDrawable();
if( draw )
draw->setModelConditionState( MODELCONDITION_LOADED );
} // end if
if ( getTransportContainModuleData()->m_armedRidersUpgradeWeaponSet )
letRidersUpgradeWeaponSet();
//Kris: October 20, 2003 - Patch 1.01
//Force Jarmen Kell to transfer weapon timer for snipe to and from the combat bike.
if( getObject()->isKindOf( KINDOF_CLIFF_JUMPER ) && rider->isKindOf( KINDOF_HERO ) && rider->isKindOf( KINDOF_SALVAGER ) )
{
//Admittedly brutal hack, but considering the state of the game (post-ship), this is a particularly surgical fix.
Weapon *bikeWeapon = getObject()->getWeaponInWeaponSlot( SECONDARY_WEAPON );
Weapon *riderWeapon = rider->getWeaponInWeaponSlot( SECONDARY_WEAPON );
if( bikeWeapon && riderWeapon )
{
//Transfer the reload time from the rider to the bike
bikeWeapon->transferNextShotStatsFrom( *riderWeapon );
}
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void TransportContain::onRemoving( Object *rider )
{
OpenContain::onRemoving(rider);
// object is no longer held inside a transport
rider->clearDisabled( DISABLED_HELD );
const TransportContainModuleData* d = getTransportContainModuleData();
if (!d->m_exitBone.isEmpty())
{
Drawable* draw = getObject()->getDrawable();
if (draw)
{
Coord3D bonePos, worldPos;
if (draw->getPristineBonePositions(d->m_exitBone.str(), 0, &bonePos, NULL, 1) == 1)
{
getObject()->convertBonePosToWorldPos(&bonePos, NULL, &worldPos, NULL);
rider->setPosition(&worldPos);
}
}
}
if (d->m_orientLikeContainerOnExit)
{
rider->setOrientation(getObject()->getOrientation());
}
if (d->m_keepContainerVelocityOnExit)
{
PhysicsBehavior* parent = getObject()->getPhysics();
PhysicsBehavior* child = rider->getPhysics();
if (parent && child)
{
Coord3D startingForce = *parent->getVelocity();
Real mass = child->getMass();
startingForce.x *= mass;
startingForce.y *= mass;
startingForce.z *= mass;
child->applyMotiveForce( &startingForce );
Real pitchRate = child->getCenterOfMassOffset() * d->m_exitPitchRate;
child->setPitchRate( pitchRate );
}
}
Int transportSlotCount = rider->getTransportSlotCount();
DEBUG_ASSERTCRASH(transportSlotCount > 0, ("Hmm, this object isnt transportable"));
m_extraSlotsInUse -= transportSlotCount - 1;
#if (defined(_DEBUG) || defined(_INTERNAL))
UnsignedInt containCount = getContainCount();
UnsignedInt containMax = getContainMax();
DEBUG_ASSERTCRASH(m_extraSlotsInUse >= 0 && m_extraSlotsInUse + containCount <= containMax, ("Hmm, bad slot count"));
#endif
// when we are empty again, clear the model condition for loaded
if( getContainCount() == 0 )
{
Drawable *draw = getObject()->getDrawable();
if( draw )
draw->clearModelConditionState( MODELCONDITION_LOADED );
} // end if
if (getObject()->isAboveTerrain())
{
// temporarily mark the guy as being allowed to fall
// (overriding his locomotor's stick-to-ground attribute).
// this will be reset (by PhysicsBehavior) when he touches the ground.
PhysicsBehavior* physics = rider->getPhysics();
if (physics)
physics->setAllowToFall(true);
}
// AI might need help using this transport in a good way. Make the passengers aggressive.
//There is no computer player check since Aggressive only means something for computer players anyway
if( d->m_goAggressiveOnExit && rider->getAI() )
{
rider->getAI()->setAttitude( AI_AGGRESSIVE );
}
if (getObject()->isEffectivelyDead()) {
scatterToNearbyPosition(rider);
}
if( d->m_resetMoodCheckTimeOnExit && rider->getAI() )
{
rider->getAI()->wakeUpAndAttemptToTarget();
}
m_frameExitNotBusy = TheGameLogic->getFrame() + d->m_exitDelay;
if ( d->m_armedRidersUpgradeWeaponSet )
letRidersUpgradeWeaponSet();
//Kris: October 20, 2003 - Patch 1.01
//Force Jarmen Kell to transfer weapon timer for snipe to and from the combat bike.
if( getObject()->isKindOf( KINDOF_CLIFF_JUMPER ) && rider->isKindOf( KINDOF_HERO ) && rider->isKindOf( KINDOF_SALVAGER ) )
{
//Admittedly brutal hack, but considering the state of the game (post-ship), this is a particularly surgical fix.
Weapon *bikeWeapon = getObject()->getWeaponInWeaponSlot( SECONDARY_WEAPON );
Weapon *riderWeapon = rider->getWeaponInWeaponSlot( SECONDARY_WEAPON );
if( bikeWeapon && riderWeapon )
{
//Transfer the reload time from the bike to the rider
riderWeapon->transferNextShotStatsFrom( *bikeWeapon );
}
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TransportContain::createPayload()
{
TransportContainModuleData* self = (TransportContainModuleData*)getTransportContainModuleData();
Int count = self->m_initialPayload.count;
const ThingTemplate* payloadTemplate = TheThingFactory->findTemplate( self->m_initialPayload.name );
Object* object = getObject();
ContainModuleInterface *contain = object->getContain();
if( contain )
{
contain->enableLoadSounds( FALSE );
for( int i = 0; i < count; i++ )
{
//We are creating a transport that comes with a initial payload, so add it now!
Object* payload = TheThingFactory->newObject( payloadTemplate, object->getControllingPlayer()->getDefaultTeam() );
if( contain->isValidContainerFor( payload, true ) )
{
contain->addToContain( payload );
}
else
{
DEBUG_CRASH( ( "DeliverPayload: PutInContainer %s is full, or not valid for the payload %s!", object->getName().str(), self->m_initialPayload.name.str() ) );
}
}
contain->enableLoadSounds( TRUE );
}
m_payloadCreated = TRUE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime TransportContain::update()
{
const TransportContainModuleData *moduleData = getTransportContainModuleData();
if( m_payloadCreated == FALSE )
createPayload();
if( moduleData && moduleData->m_healthRegen )
{
ContainModuleInterface *contain = getObject()->getContain();
if( contain )
{
//This transport has a health regeneration value, so go through and heal all inside.
const ContainedItemsList* items = contain->getContainedItemsList();
if( items )
{
ContainedItemsList::const_iterator it;
it = items->begin();
while( *it )
{
Object *object = *it;
//Advance to the next iterator
it++;
//Determine if we need healing or not.
BodyModuleInterface *body = object->getBodyModule();
if( body->getHealth() < body->getMaxHealth() )
{
//Calculate the health to be regenerated on each unit.
Real regen = body->getMaxHealth() * moduleData->m_healthRegen / 100.0f * SECONDS_PER_LOGICFRAME_REAL;
//Perform the actual healing for this frame.
// DamageInfo damageInfo;
// damageInfo.in.m_damageType = DAMAGE_HEALING;
// damageInfo.in.m_deathType = DEATH_NONE;
// damageInfo.in.m_sourceID = getObject()->getID();
// damageInfo.in.m_amount = regen;
// object->attemptDamage( &damageInfo );
object->attemptHealing( regen, getObject() );
}
}
}
}
}
return OpenContain::update(); //extend
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TransportContain::unreserveDoorForExit( ExitDoorType exitDoor )
{
/* nothing */
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TransportContain::killRidersWhoAreNotFreeToExit()
{
const TransportContainModuleData* d = getTransportContainModuleData();
ContainedItemsList::const_iterator it = getContainList().begin();
while( it != getContainList().end() )
{
// get the object
Object *obj = *it;
// increment the iterator, since death will pull this guy from the list somewhere else
++it;
if (!isSpecificRiderFreeToExit(obj)) // only TransportContain has a meaningful failure for isFreeToExit
{
// If we cannot exit this guy legally, kill the bastard before removeAllContained forces him out.
if (d->m_destroyRidersWhoAreNotFreeToExit)
TheGameLogic->destroyObject(obj);
else
obj->kill();
}
}
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
Bool TransportContain::isSpecificRiderFreeToExit(Object* specificObject)
{
if( specificObject == NULL )
return TRUE; // I can, in general, exit people.
// This is a override, not an extend. I will check for game legality for
// okaying the call to exitObjectViaDoor.
const Object* me = getObject();
// this is present solely for some transports to override, so that they can land before
// allowing people to exit...
const AIUpdateInterface* ai = me->getAIUpdateInterface();
if (ai && ai->getAiFreeToExit(specificObject) != FREE_TO_EXIT)
return FALSE;
// I can always kick people out if I am in the air, I know what I'm doing
if (me->isUsingAirborneLocomotor())
return TRUE;
const Coord3D *myPosition = me->getPosition();
if (!specificObject->getAIUpdateInterface())
return FALSE;
const Locomotor *hisLocomotor = specificObject->getAIUpdateInterface()->getCurLocomotor();
if( hisLocomotor == FALSE )
return FALSE;
// He can't get to this spot naturally, so I can't force him there. (amphib transport)
if (!TheAI->pathfinder()->validMovementTerrain(me->getLayer(), hisLocomotor, myPosition))
return FALSE;
return TRUE;
}
Bool TransportContain::isPassengerAllowedToFire( ObjectID id ) const
{
Object *passenger = TheGameLogic->findObjectByID(id);
if( passenger != NULL )
{
//only allow infantry, and turrets and such. no vehicles.
if( passenger->isKindOf(KINDOF_INFANTRY) == FALSE )
return FALSE;
}
if ( ! getObject() )
return FALSE;
// but wait! I may be riding on an Overlord
// This code detects the case of whether the contained passenger is in a bunker riding on an overlord, inside a helix!
// Oh my God.
const Object *heWhoContainsMe = getObject()->getContainedBy();
if ( heWhoContainsMe)
{
ContainModuleInterface *hisContain = heWhoContainsMe->getContain();
DEBUG_ASSERTCRASH( hisContain,("TransportContain::isPassengerAllowedToFire()... CONTAINER WITHOUT A CONTAIN! AARRGH!") );
if ( hisContain && hisContain->isSpecialOverlordStyleContainer() )
return hisContain->isPassengerAllowedToFire( id );
}
return OpenContain::isPassengerAllowedToFire();
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
ExitDoorType TransportContain::reserveDoorForExit( const ThingTemplate* objType, Object *specificObject )
{
return isSpecificRiderFreeToExit(specificObject) ? DOOR_1 : DOOR_NONE_AVAILABLE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
Bool TransportContain::isExitBusy() const ///< Contain style exiters are getting the ability to space out exits, so ask this before reserveDoor as a kind of no-commitment check.
{
const TransportContainModuleData *data = getTransportContainModuleData();
if( data->m_isDelayExitInAir && getObject()->isAboveTerrain() )
return TRUE;
return TheGameLogic->getFrame() < m_frameExitNotBusy;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TransportContain::onCapture( Player *oldOwner, Player *newOwner )
{
if( oldOwner != newOwner )
{
if( getObject()->isDisabledByType( DISABLED_UNMANNED ) )
{
//If this vehicle was sniped, then instantly eject everyone (otherwise, the next guy to eject will recapture it).
removeAllContained();
}
else
{
//Use standard
orderAllPassengersToExit( CMD_FROM_AI, FALSE );
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void TransportContain::crc( Xfer *xfer )
{
// extend base class
OpenContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void TransportContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
OpenContain::xfer( xfer );
// payload created
xfer->xferBool( &m_payloadCreated );
// extra slots in use
xfer->xferInt( &m_extraSlotsInUse );
// frame exit not busy
xfer->xferUnsignedInt( &m_frameExitNotBusy );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void TransportContain::loadPostProcess( void )
{
// extend base class
OpenContain::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,552 @@
/*
** 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: TunnelContain.cpp ////////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, March 2002
// Desc: A version of OpenContain that overrides where the passengers are stored: the Owning Player's
// TunnelTracker. All queries about capacity and contents are also redirected.
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/RandomValue.h"
#include "Common/ThingTemplate.h"
#include "Common/TunnelTracker.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/OpenContain.h"
#include "GameLogic/Module/TunnelContain.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
///////////////////////////////////////////////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS ///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
TunnelContain::TunnelContain( Thing *thing, const ModuleData* moduleData ) : OpenContain( thing, moduleData )
{
m_needToRunOnBuildComplete = true;
m_isCurrentlyRegistered = FALSE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
TunnelContain::~TunnelContain()
{
}
void TunnelContain::addToContainList( Object *obj )
{
Player *owningPlayer = getObject()->getControllingPlayer();
owningPlayer->getTunnelSystem()->addToContainList( obj );
}
//-------------------------------------------------------------------------------------------------
/** Remove 'obj' from the m_containList of objects in this module.
* This will trigger an onRemoving event for the object that this module
* is a part of and an onRemovedFrom event for the object being removed */
//-------------------------------------------------------------------------------------------------
void TunnelContain::removeFromContain( Object *obj, Bool exposeStealthUnits )
{
// sanity
if( obj == NULL )
return;
// trigger an onRemoving event for 'm_object' no longer containing 'itemToRemove->m_object'
if( getObject()->getContain() )
{
getObject()->getContain()->onRemoving( obj );
}
// trigger an onRemovedFrom event for 'remove'
obj->onRemovedFrom( getObject() );
//
// we can only remove this object from the contains list of this module if
// it is actually contained by this module
//
Player *owningPlayer = getObject()->getControllingPlayer();
if( owningPlayer == NULL )
return; //game tear down. We do the onRemove* stuff first because this is allowed to fail but that still needs to be done
if( ! owningPlayer->getTunnelSystem()->isInContainer( obj ) )
{
return;
}
owningPlayer->getTunnelSystem()->removeFromContain( obj, exposeStealthUnits );
}
//--------------------------------------------------------------------------------------------------------
/** Force all contained objects in the contained list to exit, and kick them in the pants on the way out*/
//--------------------------------------------------------------------------------------------------------
void TunnelContain::harmAndForceExitAllContained( DamageInfo *info )
{
Player *owningPlayer = getObject()->getControllingPlayer();
const ContainedItemsList *fullList = owningPlayer->getTunnelSystem()->getContainedItemsList();
Object *obj;
ContainedItemsList::const_iterator it;
//Kris: Patch 1.01 -- November 6, 2003
//No longer advances the iterator and saves it. The iterator is fetched from the beginning after
//each loop. This is to prevent a crash where dropping a bunker buster on a tunnel network containing
//multiple units (if they have the suicide bomb upgrade - demo general). In this case, multiple bunker
//busters would hit the tunnel network in close succession. Missile #1 would iterate the list, killing
//infantry #1. Infantry #1 would explode and destroy Missile #2. Missile #2 would start iterating the
//same list, killing the remaining units. When Missile #1 picked up and continued processing the list
//it would crash because it's iterator was deleted from under it.
it = (*fullList).begin();
while( it != (*fullList).end() )
{
obj = *it;
removeFromContain( obj, true );
obj->attemptDamage( info );
it = (*fullList).begin();
} // end while
} // end removeAllContained
//-------------------------------------------------------------------------------------------------
/** Remove all contained objects from the contained list */
//-------------------------------------------------------------------------------------------------
void TunnelContain::killAllContained( void )
{
Player *owningPlayer = getObject()->getControllingPlayer();
const ContainedItemsList *fullList = owningPlayer->getTunnelSystem()->getContainedItemsList();
Object *obj;
ContainedItemsList::const_iterator it;
it = (*fullList).begin();
while( it != (*fullList).end() )
{
obj = *it;
it++;
removeFromContain( obj, true );
obj->kill();
}
}
//-------------------------------------------------------------------------------------------------
/** Remove all contained objects from the contained list */
//-------------------------------------------------------------------------------------------------
void TunnelContain::removeAllContained( Bool exposeStealthUnits )
{
Player *owningPlayer = getObject()->getControllingPlayer();
const ContainedItemsList *fullList = owningPlayer->getTunnelSystem()->getContainedItemsList();
Object *obj;
ContainedItemsList::const_iterator it;
it = (*fullList).begin();
while( it != (*fullList).end() )
{
obj = *it;
it++;
removeFromContain( obj, exposeStealthUnits );
}
}
//-------------------------------------------------------------------------------------------------
/** Iterate the contained list and call the callback on each of the objects */
//-------------------------------------------------------------------------------------------------
void TunnelContain::iterateContained( ContainIterateFunc func, void *userData, Bool reverse )
{
Player *owningPlayer = getObject()->getControllingPlayer();
owningPlayer->getTunnelSystem()->iterateContained( func, userData, reverse );
}
//-------------------------------------------------------------------------------------------------
void TunnelContain::onContaining( Object *obj, Bool wasSelected )
{
OpenContain::onContaining( obj, wasSelected );
// objects inside a building are held
obj->setDisabled( DISABLED_HELD );
obj->getControllingPlayer()->getAcademyStats()->recordUnitEnteredTunnelNetwork();
obj->handlePartitionCellMaintenance();
}
//-------------------------------------------------------------------------------------------------
void TunnelContain::onRemoving( Object *obj )
{
OpenContain::onRemoving(obj);
// object is no longer held inside a garrisoned building
obj->clearDisabled( DISABLED_HELD );
/// place the object in the world at position of the container m_object
ThePartitionManager->registerObject( obj );
obj->setPosition( getObject()->getPosition() );
if( obj->getDrawable() )
{
obj->setSafeOcclusionFrame(TheGameLogic->getFrame()+obj->getTemplate()->getOcclusionDelay());
obj->getDrawable()->setDrawableHidden( false );
}
doUnloadSound();
}
//-------------------------------------------------------------------------------------------------
void TunnelContain::onSelling()
{
// A TunnelContain tells everyone to leave if this is the last tunnel
Player *owningPlayer = getObject()->getControllingPlayer();
if( owningPlayer == NULL )
return;
TunnelTracker *tunnelTracker = owningPlayer->getTunnelSystem();
if( tunnelTracker == NULL )
return;
// We are the last tunnel, so kick everyone out. This makes tunnels act like Palace and Bunker
// rather than killing the occupants as if the last tunnel died.
if( tunnelTracker->friend_getTunnelCount() == 1 )
removeAllContained(FALSE);// Can't be order to exit, as I have no time to organize their exits.
// If they don't go right now, I will delete them in a moment
// Unregister after the kick out, or else the unregistering will activate a cavein-kill.
// We need to do this in case someone sells their last two tunnels at the same time.
if( m_isCurrentlyRegistered )
{
tunnelTracker->onTunnelDestroyed( getObject() );
m_isCurrentlyRegistered = FALSE;
}
}
//-------------------------------------------------------------------------------------------------
Bool TunnelContain::isValidContainerFor(const Object* obj, Bool checkCapacity) const
{
Player *owningPlayer = getObject()->getControllingPlayer();
return owningPlayer->getTunnelSystem()->isValidContainerFor( obj, checkCapacity );
}
UnsignedInt TunnelContain::getContainCount() const
{
Player *owningPlayer = getObject()->getControllingPlayer();
if( owningPlayer && owningPlayer->getTunnelSystem() )
{
return owningPlayer->getTunnelSystem()->getContainCount();
}
return 0;
}
Int TunnelContain::getContainMax( void ) const
{
Player *owningPlayer = getObject()->getControllingPlayer();
return owningPlayer->getTunnelSystem()->getContainMax();
}
const ContainedItemsList* TunnelContain::getContainedItemsList() const
{
Player *owningPlayer = getObject()->getControllingPlayer();
return owningPlayer->getTunnelSystem()->getContainedItemsList();
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// PRIVATE FUNCTIONS //////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
void TunnelContain::scatterToNearbyPosition(Object* obj)
{
Object *theContainer = getObject();
//
// for now we will just set the position of the object that is being removed from us
// at a random angle away from our center out some distance
//
//
// pick an angle that is in the view of the current camera position so that
// the thing will come out "toward" the player and they can see it
// NOPE, can't do that ... all players screen angles will be different, unless
// we maintain the angle of each players screen in the player structure or something
//
Real angle = GameLogicRandomValueReal( 0.0f, 2.0f * PI );
// angle = TheTacticalView->getAngle();
// angle -= GameLogicRandomValueReal( PI / 3.0f, 2.0f * (PI / 3.0F) );
Real minRadius = theContainer->getGeometryInfo().getBoundingCircleRadius();
Real maxRadius = minRadius + minRadius / 2.0f;
const Coord3D *containerPos = theContainer->getPosition();
Real dist = GameLogicRandomValueReal( minRadius, maxRadius );
Coord3D pos;
pos.x = dist * Cos( angle ) + containerPos->x;
pos.y = dist * Sin( angle ) + containerPos->y;
pos.z = TheTerrainLogic->getGroundHeight( pos.x, pos.y );
// set orientation
obj->setOrientation( angle );
AIUpdateInterface *ai = obj->getAIUpdateInterface();
if( ai )
{
// set position of the object at center of building and move them toward pos
obj->setPosition( theContainer->getPosition() );
ai->ignoreObstacle(theContainer);
ai->aiMoveToPosition( &pos, CMD_FROM_AI );
} // end if
else
{
// no ai, just set position at the target pos
obj->setPosition( &pos );
} // end else
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void TunnelContain::onDie( const DamageInfo * damageInfo )
{
// override the onDie we inherit from OpenContain. no super call.
if (!getTunnelContainModuleData()->m_dieMuxData.isDieApplicable(getObject(), damageInfo))
return;
if( !m_isCurrentlyRegistered )
return;//it isn't registered as a tunnel
Player *owningPlayer = getObject()->getControllingPlayer();
if( owningPlayer == NULL )
return;
TunnelTracker *tunnelTracker = owningPlayer->getTunnelSystem();
if( tunnelTracker == NULL )
return;
tunnelTracker->onTunnelDestroyed( getObject() );
m_isCurrentlyRegistered = FALSE;
}
//-------------------------------------------------------------------------------------------------
void TunnelContain::onDelete( void )
{
// Being sold is a straight up delete. no death
if( !m_isCurrentlyRegistered )
return;//it isn't registered as a tunnel
Player *owningPlayer = getObject()->getControllingPlayer();
if( owningPlayer == NULL )
return;
TunnelTracker *tunnelTracker = owningPlayer->getTunnelSystem();
if( tunnelTracker == NULL )
return;
tunnelTracker->onTunnelDestroyed( getObject() );
m_isCurrentlyRegistered = FALSE;
}
//-------------------------------------------------------------------------------------------------
void TunnelContain::onCreate( void )
{
}
//-------------------------------------------------------------------------------------------------
void TunnelContain::onObjectCreated()
{
//Kris: July 29, 2003
//Added this function to support the sneak attack (which doesn't call onBuildComplete).
if( ! shouldDoOnBuildComplete() )
return;
m_needToRunOnBuildComplete = false;
Player *owningPlayer = getObject()->getControllingPlayer();
if( owningPlayer == NULL )
return;
TunnelTracker *tunnelTracker = owningPlayer->getTunnelSystem();
if( tunnelTracker == NULL )
return;
tunnelTracker->onTunnelCreated( getObject() );
m_isCurrentlyRegistered = TRUE;
}
//-------------------------------------------------------------------------------------------------
void TunnelContain::onBuildComplete( void )
{
//Kris: July 29, 2003
//Obsolete -- onObjectCreated handles it before this function gets called.
/*
if( ! shouldDoOnBuildComplete() )
return;
m_needToRunOnBuildComplete = false;
Player *owningPlayer = getObject()->getControllingPlayer();
if( owningPlayer == NULL )
return;
TunnelTracker *tunnelTracker = owningPlayer->getTunnelSystem();
if( tunnelTracker == NULL )
return;
tunnelTracker->onTunnelCreated( getObject() );
m_isCurrentlyRegistered = TRUE;
*/
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void TunnelContain::onCapture( Player *oldOwner, Player *newOwner )
{
if( m_isCurrentlyRegistered )
{
TunnelTracker *oldTunnelTracker = oldOwner->getTunnelSystem();
if( oldTunnelTracker )
{
DEBUG_ASSERTCRASH( oldTunnelTracker->getContainCount() == 0, ("You shouldn't force a capture of a Tunnel with people in it. Future ExitFromContainer scripts will fail."));
oldTunnelTracker->onTunnelDestroyed(getObject());
}
TunnelTracker *newTunnelTracker = newOwner->getTunnelSystem();
if( newTunnelTracker )
{
newTunnelTracker->onTunnelCreated(getObject());
}
}
// extend base class
OpenContain::onCapture( oldOwner, newOwner );
}
// ------------------------------------------------------------------------------------------------
/** Per frame update */
// ------------------------------------------------------------------------------------------------
UpdateSleepTime TunnelContain::update( void )
{
// extending functionality to heal the units within the tunnel system
OpenContain::update();
const TunnelContainModuleData *modData = getTunnelContainModuleData();
Object *obj = getObject();
Player *controllingPlayer = NULL;
if (obj)
{
controllingPlayer = obj->getControllingPlayer();
}
if (controllingPlayer)
{
TunnelTracker *tunnelSystem = controllingPlayer->getTunnelSystem();
if (tunnelSystem)
{
tunnelSystem->healObjects(modData->m_framesForFullHeal);
}
// check for attacked.
BodyModuleInterface *body = obj->getBodyModule();
if (body) {
const DamageInfo *info = body->getLastDamageInfo();
if (info) {
if (body->getLastDamageTimestamp() + LOGICFRAMES_PER_SECOND > TheGameLogic->getFrame()) {
// winner.
ObjectID attackerID = info->in.m_sourceID;
Object *attacker = TheGameLogic->findObjectByID(attackerID);
if( attacker )
{
if (obj->getRelationship(attacker) == ENEMIES) {
tunnelSystem->updateNemesis(attacker);
}
}
}
}
}
}
return UPDATE_SLEEP_NONE;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void TunnelContain::crc( Xfer *xfer )
{
// extend base class
OpenContain::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void TunnelContain::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
OpenContain::xfer( xfer );
// need to run on build complete
xfer->xferBool( &m_needToRunOnBuildComplete );
// Currently registered with owning player
xfer->xferBool( &m_isCurrentlyRegistered );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void TunnelContain::loadPostProcess( void )
{
// extend base class
OpenContain::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,94 @@
/*
** 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: CreateModule.cpp /////////////////////////////////////////////////////////////////////////
// Author: Colin Day, October 2002
// Desc: Create module base class
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/CreateModule.h"
// ------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CreateModule::CreateModule( Thing *thing, const ModuleData* moduleData )
: BehaviorModule( thing, moduleData ),
m_needToRunOnBuildComplete(TRUE)
{
} // end createModule
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
CreateModule::~CreateModule()
{
} // end ~CreateModule
//-------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void CreateModule::crc( Xfer *xfer )
{
// extend base class
BehaviorModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void CreateModule::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
BehaviorModule::xfer( xfer );
// need to run on build complete
xfer->xferBool( &m_needToRunOnBuildComplete );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void CreateModule::loadPostProcess( void )
{
// extend base class
BehaviorModule::loadPostProcess();
} // ene loadPostProcess

View File

@@ -0,0 +1,182 @@
/*
** 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: GrantUpgradeCreate.cpp ////////////////////////////////////////////////////////////////////////
// Author: Kris Morness, April 2002
// Desc: GrantUpgrade create module
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_OBJECT_STATUS_NAMES
#include "Common/Player.h"
#include "Common/Upgrade.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/GrantUpgradeCreate.h"
#include "GameLogic/Object.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
GrantUpgradeCreateModuleData::GrantUpgradeCreateModuleData()
{
m_upgradeName = "";
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void GrantUpgradeCreateModuleData::buildFieldParse(MultiIniFieldParse& p)
{
CreateModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "UpgradeToGrant", INI::parseAsciiString, NULL, offsetof( GrantUpgradeCreateModuleData, m_upgradeName ) },
{ "ExemptStatus", ObjectStatusMaskType::parseFromINI, NULL, offsetof( GrantUpgradeCreateModuleData, m_exemptStatus ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
GrantUpgradeCreate::GrantUpgradeCreate( Thing *thing, const ModuleData* moduleData ) : CreateModule( thing, moduleData )
{
} // end GrantUpgradeCreate
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
GrantUpgradeCreate::~GrantUpgradeCreate( void )
{
} // end ~GrantUpgradeCreate
//-------------------------------------------------------------------------------------------------
/** The create callback. */
//-------------------------------------------------------------------------------------------------
void GrantUpgradeCreate::onCreate( void )
{
ObjectStatusMaskType exemptStatus = getGrantUpgradeCreateModuleData()->m_exemptStatus;
ObjectStatusMaskType currentStatus = getObject()->getStatusBits();
if( exemptStatus.test( OBJECT_STATUS_UNDER_CONSTRUCTION ) )
{
if( !currentStatus.test( OBJECT_STATUS_UNDER_CONSTRUCTION ) )
{
const UpgradeTemplate *upgradeTemplate = TheUpgradeCenter->findUpgrade( getGrantUpgradeCreateModuleData()->m_upgradeName );
if( !upgradeTemplate )
{
DEBUG_ASSERTCRASH( 0, ("GrantUpdateCreate for %s can't find upgrade template %s.", getObject()->getName(), getGrantUpgradeCreateModuleData()->m_upgradeName ) );
return;
}
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 );
}
}
} // end onCreate
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void GrantUpgradeCreate::onBuildComplete( void )
{
if( ! shouldDoOnBuildComplete() )
return;
CreateModule::onBuildComplete(); // extend
const UpgradeTemplate *upgradeTemplate = TheUpgradeCenter->findUpgrade( getGrantUpgradeCreateModuleData()->m_upgradeName );
if( !upgradeTemplate )
{
DEBUG_ASSERTCRASH( 0, ("GrantUpdateCreate for %s can't find upgrade template %s.", getObject()->getName(), getGrantUpgradeCreateModuleData()->m_upgradeName ) );
return;
}
if( upgradeTemplate->getUpgradeType() == UPGRADE_TYPE_PLAYER )
{
// get the player
Player *player = getObject()->getControllingPlayer();
player->addUpgrade( upgradeTemplate, UPGRADE_STATUS_COMPLETE );
}
else
{
getObject()->giveUpgrade( upgradeTemplate );
}
} // end onBuildComplete
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void GrantUpgradeCreate::crc( Xfer *xfer )
{
// extend base class
CreateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void GrantUpgradeCreate::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CreateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void GrantUpgradeCreate::loadPostProcess( void )
{
// extend base class
CreateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,134 @@
/*
** 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: LockWeaponCreate.cpp //////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, March 2003
// Desc: Locks the weapon choice to the slot specified on creation
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_WEAPONSLOTTYPE_NAMES
#include "Common/Xfer.h"
#include "GameLogic/Module/LockWeaponCreate.h"
#include "GameLogic/Object.h"
#include "GameLogic/WeaponSet.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
LockWeaponCreateModuleData::LockWeaponCreateModuleData()
{
m_slotToLock = PRIMARY_WEAPON;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void LockWeaponCreateModuleData::buildFieldParse(MultiIniFieldParse& p)
{
CreateModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "SlotToLock", INI::parseLookupList, TheWeaponSlotTypeNamesLookupList, offsetof( LockWeaponCreateModuleData, m_slotToLock ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
LockWeaponCreate::LockWeaponCreate( Thing *thing, const ModuleData* moduleData ) : CreateModule( thing, moduleData )
{
} // end GrantUpgradeCreate
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
LockWeaponCreate::~LockWeaponCreate( void )
{
} // end ~GrantUpgradeCreate
//-------------------------------------------------------------------------------------------------
/** The create callback. */
//-------------------------------------------------------------------------------------------------
void LockWeaponCreate::onCreate( void )
{
} // end onCreate
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void LockWeaponCreate::onBuildComplete( void )
{
CreateModule::onBuildComplete(); // extend
Object *me = getObject();
WeaponSlotType slot = getLockWeaponCreateModuleData()->m_slotToLock;
me->setWeaponLock( slot, LOCKED_PERMANENTLY );
} // end onBuildComplete
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void LockWeaponCreate::crc( Xfer *xfer )
{
// extend base class
CreateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void LockWeaponCreate::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CreateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void LockWeaponCreate::loadPostProcess( void )
{
// extend base class
CreateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,114 @@
/*
** 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: PreorderCreate.cpp ///////////////////////////////////////////////////////////////////////
// Author: Matthew D. Campbell, December 2002
// Desc: When a building is created, set the preorder status if necessary
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/PreorderCreate.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
PreorderCreate::PreorderCreate( Thing *thing, const ModuleData* moduleData ) : CreateModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
PreorderCreate::~PreorderCreate( void )
{
}
//-------------------------------------------------------------------------------------------------
void PreorderCreate::onCreate( void )
{
}
//-------------------------------------------------------------------------------------------------
void PreorderCreate::onBuildComplete( void )
{
if (getObject()->getControllingPlayer()->didPlayerPreorder())
{
getObject()->setModelConditionState( MODELCONDITION_PREORDER );
}
else
{
getObject()->clearModelConditionState( MODELCONDITION_PREORDER );
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void PreorderCreate::crc( Xfer *xfer )
{
// extend base class
CreateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void PreorderCreate::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CreateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void PreorderCreate::loadPostProcess( void )
{
// extend base class
CreateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,113 @@
/*
** 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: SpecialPowerCreate.h /////////////////////////////////////////////////////////////////////////////
// Author: Matthew D. Campbell, May 2002
// Desc: When a building is created, tell special powers to start counting down
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/SpecialPowerCreate.h"
#include "GameLogic/Module/SpecialPowerModule.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SpecialPowerCreate::SpecialPowerCreate( Thing *thing, const ModuleData* moduleData ) : CreateModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SpecialPowerCreate::~SpecialPowerCreate( void )
{
}
//-------------------------------------------------------------------------------------------------
void SpecialPowerCreate::onCreate( void )
{
}
//-------------------------------------------------------------------------------------------------
void SpecialPowerCreate::onBuildComplete( void )
{
if( ! shouldDoOnBuildComplete() )
return;
CreateModule::onBuildComplete(); // extend
for (BehaviorModule** m = getObject()->getBehaviorModules(); *m; ++m)
{
SpecialPowerModuleInterface* sp = (*m)->getSpecialPower();
if (!sp)
continue;
sp->onSpecialPowerCreation();
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SpecialPowerCreate::crc( Xfer *xfer )
{
// extend base class
CreateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SpecialPowerCreate::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CreateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SpecialPowerCreate::loadPostProcess( void )
{
// extend base class
CreateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,119 @@
/*
** 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: SupplyCenterCreate.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood Feb 2002
// Desc: When a Supply Center is created, it needs to update all the Resource brains in all players
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/ResourceGatheringManager.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/SupplyCenterCreate.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SupplyCenterCreate::SupplyCenterCreate( Thing *thing, const ModuleData* moduleData ) : CreateModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SupplyCenterCreate::~SupplyCenterCreate( void )
{
}
//-------------------------------------------------------------------------------------------------
void SupplyCenterCreate::onCreate( void )
{
}
//-------------------------------------------------------------------------------------------------
void SupplyCenterCreate::onBuildComplete( void )
{
if( ! shouldDoOnBuildComplete() )
return;
CreateModule::onBuildComplete(); // extend
if( ThePlayerList == NULL )
return;
for( Int playerIndex = ThePlayerList->getPlayerCount() - 1; playerIndex >= 0; playerIndex-- )
{
Player *currentPlayer = ThePlayerList->getNthPlayer( playerIndex );
if( currentPlayer == NULL )
continue;
ResourceGatheringManager *manager = currentPlayer->getResourceGatheringManager();
if( manager == NULL )
continue;
manager->addSupplyCenter( getObject() );
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SupplyCenterCreate::crc( Xfer *xfer )
{
// extend base class
CreateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SupplyCenterCreate::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CreateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SupplyCenterCreate::loadPostProcess( void )
{
// extend base class
CreateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,110 @@
/*
** 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: SupplyWarehouseCreate.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood Feb 2002
// Desc: When a Supply Center is created, it needs to update all the Resource brains in all players
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/ResourceGatheringManager.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/SupplyWarehouseCreate.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SupplyWarehouseCreate::SupplyWarehouseCreate( Thing *thing, const ModuleData* moduleData ) : CreateModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SupplyWarehouseCreate::~SupplyWarehouseCreate( void )
{
}
//-------------------------------------------------------------------------------------------------
void SupplyWarehouseCreate::onCreate( void )
{
// Warehouses are never Built.
if( ThePlayerList == NULL )
return;
for( Int playerIndex = ThePlayerList->getPlayerCount() - 1; playerIndex >= 0; playerIndex-- )
{
Player *currentPlayer = ThePlayerList->getNthPlayer( playerIndex );
if( currentPlayer == NULL )
continue;
ResourceGatheringManager *manager = currentPlayer->getResourceGatheringManager();
if( manager == NULL )
continue;
manager->addSupplyWarehouse( getObject() );
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SupplyWarehouseCreate::crc( Xfer *xfer )
{
// extend base class
CreateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SupplyWarehouseCreate::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CreateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SupplyWarehouseCreate::loadPostProcess( void )
{
// extend base class
CreateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,139 @@
/*
** 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: VeterancyGainCreate.cpp //////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, August 2002
// Desc: On creation, will set Object's veterancy level if required Science is present.
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_VETERANCY_NAMES // for TheVeterancyNames[]
#include "Common/Player.h"
#include "Common/Xfer.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/VeterancyGainCreate.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
VeterancyGainCreateModuleData::VeterancyGainCreateModuleData()
{
m_startingLevel = LEVEL_REGULAR;
m_scienceRequired = SCIENCE_INVALID;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void VeterancyGainCreateModuleData::buildFieldParse(MultiIniFieldParse& p)
{
CreateModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "StartingLevel", INI::parseIndexList, TheVeterancyNames, offsetof( VeterancyGainCreateModuleData, m_startingLevel ) },
{ "ScienceRequired", INI::parseScience, NULL, offsetof( VeterancyGainCreateModuleData, m_scienceRequired ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
VeterancyGainCreate::VeterancyGainCreate( Thing *thing, const ModuleData* moduleData ) : CreateModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
VeterancyGainCreate::~VeterancyGainCreate( void )
{
}
//-------------------------------------------------------------------------------------------------
/** The create callback. */
//-------------------------------------------------------------------------------------------------
void VeterancyGainCreate::onCreate( void )
{
// When produced normally, this Object will ask the Player if the correct Science is known for it
// to set its level to the given level
const VeterancyGainCreateModuleData *md = getVeterancyGainCreateModuleData();
Player *myPlayer = getObject()->getControllingPlayer();
if( myPlayer && (md->m_scienceRequired == SCIENCE_INVALID ||
myPlayer->hasScience( md->m_scienceRequired )) )
{
ExperienceTracker* myExp = getObject()->getExperienceTracker();
if( myExp && myExp->isTrainable() )
{
// srj sez: use "setMin" here so that we never lose levels
myExp->setMinVeterancyLevel( md->m_startingLevel );// sVL can override isTrainable, but this module should not.
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void VeterancyGainCreate::crc( Xfer *xfer )
{
// extend base class
CreateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void VeterancyGainCreate::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
CreateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void VeterancyGainCreate::loadPostProcess( void )
{
// extend base class
CreateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,118 @@
/*
** 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: BoneFXDamage.cpp ///////////////////////////////////////////////////////////////////
// Author: Bryan Cleveland, March 2002
// Desc: Damage module that goes with BoneFX update
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/BoneFXDamage.h"
#include "GameLogic/Module/BoneFXUpdate.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
BoneFXDamage::BoneFXDamage( Thing *thing, const ModuleData* moduleData )
: DamageModule( thing, moduleData )
{
} // end BoneFXDamage
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
BoneFXDamage::~BoneFXDamage( void )
{
} // end ~BoneFXDamage
//-------------------------------------------------------------------------------------------------
void BoneFXDamage::onObjectCreated()
{
static NameKeyType key_BoneFXUpdate = NAMEKEY("BoneFXUpdate");
BoneFXUpdate* bfxu = (BoneFXUpdate*)getObject()->findUpdateModule(key_BoneFXUpdate);
if (bfxu == NULL)
{
DEBUG_ASSERTCRASH(bfxu != NULL, ("BoneFXDamage requires BoneFXUpdate"));
throw INI_INVALID_DATA;
}
}
//-------------------------------------------------------------------------------------------------
/** Switching damage states */
//-------------------------------------------------------------------------------------------------
void BoneFXDamage::onBodyDamageStateChange( const DamageInfo *damageInfo,
BodyDamageType oldState,
BodyDamageType newState )
{
static NameKeyType key_BoneFXUpdate = NAMEKEY("BoneFXUpdate");
BoneFXUpdate* bfxu = (BoneFXUpdate*)getObject()->findUpdateModule(key_BoneFXUpdate);
if (bfxu)
bfxu->changeBodyDamageState(oldState, newState);
} // end onBodyDamageStateChange
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void BoneFXDamage::crc( Xfer *xfer )
{
// extend base class
DamageModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void BoneFXDamage::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
DamageModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void BoneFXDamage::loadPostProcess( void )
{
// extend base class
DamageModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,71 @@
/*
** 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: DamageModule.cpp /////////////////////////////////////////////////////////////////////////
// Author: Colin Day, September 2002
// Desc: Damage Module base class
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/DamageModule.h"
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void DamageModule::crc( Xfer *xfer )
{
// extend base class
BehaviorModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void DamageModule::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// call base class
BehaviorModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void DamageModule::loadPostProcess( void )
{
// call base class
BehaviorModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,469 @@
/*
** 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: TransitionDamageFX.cpp ///////////////////////////////////////////////////////////////////
// Author: Colin Day, March 2002
// Desc: Damage module capable of launching various effects on damage transitions
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameClient/FXList.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectCreationList.h"
#include "GameLogic/Module/TransitionDamageFX.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
TransitionDamageFXModuleData::TransitionDamageFXModuleData( void )
{
Int i, j;
for( i = 0; i < BODYDAMAGETYPE_COUNT; i++ )
{
for( j = 0; j < DAMAGE_MODULE_MAX_FX; j++ )
{
m_fxList[ i ][ j ].fx = NULL;
m_fxList[ i ][ j ].locInfo.loc.x = 0.0f;
m_fxList[ i ][ j ].locInfo.loc.y = 0.0f;
m_fxList[ i ][ j ].locInfo.loc.z = 0.0f;
m_fxList[ i ][ j ].locInfo.locType = FX_DAMAGE_LOC_TYPE_COORD;
m_fxList[ i ][ j ].locInfo.randomBone = FALSE;
m_OCL[ i ][ j ].ocl = NULL;
m_OCL[ i ][ j ].locInfo.loc.x = 0.0f;
m_OCL[ i ][ j ].locInfo.loc.y = 0.0f;
m_OCL[ i ][ j ].locInfo.loc.z = 0.0f;
m_OCL[ i ][ j ].locInfo.locType = FX_DAMAGE_LOC_TYPE_COORD;
m_OCL[ i ][ j ].locInfo.randomBone = FALSE;
m_particleSystem[ i ][ j ].particleSysTemplate = NULL;
m_particleSystem[ i ][ j ].locInfo.loc.x = 0.0f;
m_particleSystem[ i ][ j ].locInfo.loc.y = 0.0f;
m_particleSystem[ i ][ j ].locInfo.loc.z = 0.0f;
m_particleSystem[ i ][ j ].locInfo.locType = FX_DAMAGE_LOC_TYPE_COORD;
m_particleSystem[ i ][ j ].locInfo.randomBone = FALSE;
} // end for j
} // end for i
m_damageFXTypes = DAMAGE_TYPE_FLAGS_NONE;
m_damageFXTypes.flip();
m_damageOCLTypes = DAMAGE_TYPE_FLAGS_NONE;
m_damageOCLTypes.flip();
m_damageParticleTypes = DAMAGE_TYPE_FLAGS_NONE;
m_damageParticleTypes.flip();
} // end TransitionDamageFXModuleData
//-------------------------------------------------------------------------------------------------
/** Parse fx location info ... that is a named bone or a coord3d position */
//-------------------------------------------------------------------------------------------------
static void parseFXLocInfo( INI *ini, void *instance, FXLocInfo *locInfo )
{
const char *token = ini->getNextToken( ini->getSepsColon() );
if( stricmp( token, "bone" ) == 0 )
{
// save bone name and location type
locInfo->boneName = AsciiString( ini->getNextToken() );
locInfo->locType = FX_DAMAGE_LOC_TYPE_BONE;
//
// bones are followed by RandomBone:<Yes|No>, if random bone is yes, the bone name is
// assumed to be a "base bone name" and we will find all the bones with that prefix
// when picking an effect position. If it's no, the bone name is assumed to be explicit
//
token = ini->getNextToken( ini->getSepsColon() );
if( stricmp( token, "randombone" ) != 0 )
{
DEBUG_CRASH(( "parseFXLocInfo: Bone name not followed by RandomBone specifier\nPress IGNORE to see which INI file and line # is incorrect." ));
throw INI_INVALID_DATA;
} // end if
// parse the Bool definition
ini->parseBool( ini, instance, &locInfo->randomBone, NULL );
} // end if
else if( stricmp( token, "loc" ) == 0 )
{
// save location and location type
locInfo->loc.x = ini->scanReal( ini->getNextSubToken("X") );
locInfo->loc.y = ini->scanReal( ini->getNextSubToken("Y") );
locInfo->loc.z = ini->scanReal( ini->getNextSubToken("Z") );
locInfo->locType = FX_DAMAGE_LOC_TYPE_COORD;
} // end else
else
{
// error
throw INI_INVALID_DATA;
} // end else
} // end parseFXLocInfo
//-------------------------------------------------------------------------------------------------
/** In the form of:
* FXListSlot = <<Bone:BoneName BoneRandom:<Yes|No>> | <Loc: X:x Y:y Z:z>> FXList:FXListName */
//-------------------------------------------------------------------------------------------------
void TransitionDamageFXModuleData::parseFXList( INI *ini, void *instance,
void *store, const void *userData )
{
const char *token;
FXDamageFXListInfo *info = (FXDamageFXListInfo *)store;
// parse the location bone or location
parseFXLocInfo( ini, instance, &info->locInfo );
// make sure we have an "FXList:" token
token = ini->getNextToken( ini->getSepsColon() );
if( stricmp( token, "fxlist" ) != 0 )
{
// error
throw INI_INVALID_DATA;
} // end if
// parse the fx list name
ini->parseFXList( ini, instance, &info->fx, NULL );
} // end parseFXList
//-------------------------------------------------------------------------------------------------
/** In the form of:
* OCLSlot = <<Bone:BoneName BoneRandom:<Yes|No>> | <Loc: X:x Y:y Z:z>> OCL:OCLName */
//-------------------------------------------------------------------------------------------------
void TransitionDamageFXModuleData::parseObjectCreationList( INI *ini, void *instance,
void *store, const void *userData )
{
const char *token;
FXDamageOCLInfo *info = (FXDamageOCLInfo *)store;
// parse the location bone or location
parseFXLocInfo( ini, instance, &info->locInfo );
// make sure we have an "OCL:" token
token = ini->getNextToken( ini->getSepsColon() );
if( stricmp( token, "ocl" ) != 0 )
{
// error
throw INI_INVALID_DATA;
} // end if
// parse the ocl name
ini->parseObjectCreationList( ini, instance, store, &info->ocl );
} // end parseObjectCreationList
//-------------------------------------------------------------------------------------------------
/** In the form of:
* ParticleSlot = <<Bone:BoneName BoneRandom:<Yes|No>> | <Loc: X:x Y:y Z:z>> PSys:PSysName */
//-------------------------------------------------------------------------------------------------
void TransitionDamageFXModuleData::parseParticleSystem( INI *ini, void *instance,
void *store, const void *userData )
{
const char *token;
FXDamageParticleSystemInfo *info = (FXDamageParticleSystemInfo *)store;
// parse the location bone or location
parseFXLocInfo( ini, instance, &info->locInfo );
// make sure we have an "PSys:" token
token = ini->getNextToken( ini->getSepsColon() );
if( stricmp( token, "psys" ) != 0 )
{
// error
throw INI_INVALID_DATA;
} // end if
// parse the particle system name
ini->parseParticleSystemTemplate( ini, instance, store, &info->particleSysTemplate );
} // end parseParticleSystem
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
TransitionDamageFX::TransitionDamageFX( Thing *thing, const ModuleData* moduleData )
: DamageModule( thing, moduleData )
{
Int i, j;
for( i = 0; i < BODYDAMAGETYPE_COUNT; i++ )
for( j = 0; j < DAMAGE_MODULE_MAX_FX; j++ )
m_particleSystemID[ i ][ j ] = INVALID_PARTICLE_SYSTEM_ID;
} // end TransitionDamageFX
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
TransitionDamageFX::~TransitionDamageFX( void )
{
} // end ~TransitionDamageFX
/*
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void TransitionDamageFX::onDelete( void )
{
//
// we would in theory delete any particle systems we have created and attached, but the
// particle system will automatically delete itself when the object is destroyed
//
} // end onDelete
*/
//-------------------------------------------------------------------------------------------------
/** Given an FXLoc info struct, return the effect position that we are supposed to use.
* The position is local to to the object */
//-------------------------------------------------------------------------------------------------
static Coord3D getLocalEffectPos( const FXLocInfo *locInfo, Drawable *draw )
{
DEBUG_ASSERTCRASH( locInfo, ("getLocalEffectPos: locInfo is NULL\n") );
if( locInfo->locType == FX_DAMAGE_LOC_TYPE_BONE && draw )
{
if( locInfo->randomBone == FALSE )
{
Coord3D pos;
// get the bone position
Int count = draw->getPristineBonePositions( locInfo->boneName.str(), 0, &pos, NULL, 1 );
// sanity, if bone not found revert back to location defined in struct (which is 0,0,0)
if( count == 0 )
return locInfo->loc;
// return the position retrieved
return pos;
} // end if
else
{
const Int MAX_BONES = 32;
Coord3D positions[ MAX_BONES ];
// get the bone positions
Int boneCount;
boneCount = draw->getPristineBonePositions( locInfo->boneName.str(), 1, positions, NULL, MAX_BONES );
// sanity, if bone not found revert back to location defined in struct (which is 0,0,0)
if( boneCount == 0 )
return locInfo->loc;
// pick one of the bone positions
Int pick = GameLogicRandomValue( 0, boneCount - 1 );
return positions[ pick ];
} // end else
} // end if
else
return locInfo->loc;
} // end getLocalEffectPos
//-------------------------------------------------------------------------------------------------
/** Switching damage states */
//-------------------------------------------------------------------------------------------------
void TransitionDamageFX::onBodyDamageStateChange( const DamageInfo* damageInfo,
BodyDamageType oldState,
BodyDamageType newState )
{
Object *damageSource = NULL;
Int i;
Drawable *draw = getObject()->getDrawable();
const TransitionDamageFXModuleData *modData = getTransitionDamageFXModuleData();
// get the source of the damage if present
if( damageInfo )
damageSource = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID );
// remove any particle systems that might be emitting from our old state
for( i = 0; i < DAMAGE_MODULE_MAX_FX; i++ )
{
if( m_particleSystemID[ oldState ][ i ] != INVALID_PARTICLE_SYSTEM_ID )
{
TheParticleSystemManager->destroyParticleSystemByID( m_particleSystemID[ oldState ][ i ] );
m_particleSystemID[ oldState ][ i ] = INVALID_PARTICLE_SYSTEM_ID;
} // end if
} // end for i
//
// when we are transitioning to a "worse" state we will play a set of effects for that
// new state to make the transition
//
if( IS_CONDITION_WORSE( newState, oldState ) )
{
const ParticleSystemTemplate *pSystemT;
Coord3D pos;
// if we are restricted by the damage type executing effect, bail out of here
const DamageInfo *lastDamageInfo = getObject()->getBodyModule()->getLastDamageInfo();
for( i = 0; i < DAMAGE_MODULE_MAX_FX; i++ )
{
// play fx list for our new state
if( modData->m_fxList[ newState ][ i ].fx )
{
if( lastDamageInfo == NULL ||
getDamageTypeFlag( modData->m_damageFXTypes, lastDamageInfo->in.m_damageType ) )
{
pos = getLocalEffectPos( &modData->m_fxList[ newState ][ i ].locInfo, draw );
getObject()->convertBonePosToWorldPos( &pos, NULL, &pos, NULL );
FXList::doFXPos( modData->m_fxList[ newState ][ i ].fx, &pos );
} // end if
} // end if
// do any object creation list for our new state
if( modData->m_OCL[ newState ][ i ].ocl )
{
if( lastDamageInfo == NULL ||
getDamageTypeFlag( modData->m_damageOCLTypes, lastDamageInfo->in.m_damageType ) )
{
pos = getLocalEffectPos( &modData->m_OCL[ newState ][ i ].locInfo, draw );
getObject()->convertBonePosToWorldPos( &pos, NULL, &pos, NULL );
ObjectCreationList::create( modData->m_OCL[ newState ][ i ].ocl,
getObject(), &pos, damageSource->getPosition(), INVALID_ANGLE );
} // end if
} // end if
// get the template of the system to create
pSystemT = modData->m_particleSystem[ newState ][ i ].particleSysTemplate;
if( pSystemT )
{
if( lastDamageInfo == NULL ||
getDamageTypeFlag( modData->m_damageParticleTypes, lastDamageInfo->in.m_damageType ) )
{
// create a new particle system based on the template provided
ParticleSystem* pSystem = TheParticleSystemManager->createParticleSystem( pSystemT );
if( pSystem )
{
// get the what is the position we're going to playe the effect at
pos = getLocalEffectPos( &modData->m_particleSystem[ newState ][ i ].locInfo, draw );
//
// set position on system given any bone position provided, the bone position is
// local to the object and that's what we want for the particle system ... the
// transormation into world space using the object position is taken care of in
// the particle system attachToObject method
//
pSystem->setPosition( &pos );
// attach to object
pSystem->attachToObject( getObject() );
// save the id of this particle system so we can remove it later if it still exists
m_particleSystemID[ newState ][ i ] = pSystem->getSystemID();
} // end if
} // end if
} // end if
} // end for i
} // end if
} // end onBodyDamageStateChange
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void TransitionDamageFX::crc( Xfer *xfer )
{
// extend base class
DamageModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void TransitionDamageFX::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
DamageModule::xfer( xfer );
// particle systems ids
xfer->xferUser( m_particleSystemID, sizeof( ParticleSystemID ) * BODYDAMAGETYPE_COUNT * DAMAGE_MODULE_MAX_FX );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void TransitionDamageFX::loadPostProcess( void )
{
// extend base class
DamageModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,89 @@
/*
** 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: DestroyModule.cpp ////////////////////////////////////////////////////////////////////////
// Author: Colin Day, October 2002
// Desc: Destroy module base class
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/DestroyModule.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
DestroyModule::DestroyModule( Thing *thing, const ModuleData* moduleData )
: BehaviorModule( thing, moduleData )
{
} // end DestroyModule
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
DestroyModule::~DestroyModule( void )
{
} // end ~DestroyModule
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void DestroyModule::crc( Xfer *xfer )
{
// extend base class
BehaviorModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void DestroyModule::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
BehaviorModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void DestroyModule::loadPostProcess( void )
{
// extend base class
BehaviorModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,293 @@
/*
** 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: CreateCrateDie.cpp /////////////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, February 2002
// Desc: A chance to create a crate on death according to certain condition checks
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/PlayerList.h"
#include "Common/Player.h"
#include "Common/RandomValue.h"
#include "Common/ThingFactory.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameLogic/CrateSystem.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
#include "GameLogic/Module/CreateCrateDie.h"
#include "GameLogic/Module/AIUpdate.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CreateCrateDie::CreateCrateDie( Thing *thing, const ModuleData* moduleData ) : DieModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CreateCrateDie::~CreateCrateDie( void )
{
}
void CreateCrateDieModuleData::parseCrateData( INI* ini, void *instance, void * /*store*/, const void* /*userData*/ )
{
CreateCrateDieModuleData* self = (CreateCrateDieModuleData*)instance;
AsciiString crateName = ini->getNextToken();
self->m_crateNameList.push_back( crateName );
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void CreateCrateDie::onDie( const DamageInfo * damageInfo )
{
if (!isDieApplicable(damageInfo))
return;
CrateTemplate const *currentCrateData = NULL;
Object *killer = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID );
Object *me = getObject();
if( killer && killer->getRelationship( me ) == ALLIES )
return; //Nope, no crate for killing ally at all.
for( AsciiStringListConstIterator iter = getCreateCrateDieModuleData()->m_crateNameList.begin();
iter != getCreateCrateDieModuleData()->m_crateNameList.end();
iter++
)
{
currentCrateData = TheCrateSystem->findCrateTemplate( *iter );
if( currentCrateData )
{
if( ! testCreationChance( currentCrateData ) )
continue;// always test this
if( (currentCrateData->m_veterancyLevel != LEVEL_INVALID) && ! testVeterancyLevel( currentCrateData ) )
continue; //If this is set up to test and it fails
if( KINDOFMASK_ANY_SET(currentCrateData->m_killedByTypeKindof) && !testKillerType( currentCrateData, killer ) )
continue; //If this is set up to test and it fails
if( (currentCrateData->m_killerScience != SCIENCE_INVALID) && !testKillerScience( currentCrateData, killer ) )
continue; //If this is set up to test and it fails
Object *crate = createCrate( currentCrateData );//Make the crate
if( crate )
{
// Design needs to set ownership of crates sometimes
if( currentCrateData->m_isOwnedByMaker )
{
crate->setTeam( me->getControllingPlayer()->getDefaultTeam() );
}
if (killer)
{
// If the killer is a computer controlled player, notify that the crate exists.
if (killer->getControllingPlayer() &&
killer->getControllingPlayer()->getPlayerType()==PLAYER_COMPUTER)
{
AIUpdateInterface *ai = killer->getAIUpdateInterface();
if (ai)
{
ai->notifyCrate( crate->getID() );
}
}
}
}
}
}
}
Bool CreateCrateDie::testCreationChance( CrateTemplate const *currentCrateData )
{
Real testAgainst = currentCrateData->m_creationChance;
Real testWith = GameLogicRandomValueReal( 0, 1 );
return testWith < testAgainst;
}
Bool CreateCrateDie::testVeterancyLevel( CrateTemplate const *currentCrateData )
{
VeterancyLevel testAgainst = currentCrateData->m_veterancyLevel;
VeterancyLevel testWith = getObject()->getVeterancyLevel();
return testAgainst == testWith;
}
Bool CreateCrateDie::testKillerType( CrateTemplate const *currentCrateData, Object *killer )
{
if( killer == NULL )
return FALSE;
// Must match the whole group of bits set in the KilledBy description (most likely One).
if( ! killer->getTemplate()->isKindOfMulti( currentCrateData->m_killedByTypeKindof, KINDOFMASK_NONE ) )
return FALSE;
return TRUE;
}
Bool CreateCrateDie::testKillerScience( CrateTemplate const *currentCrateData, Object *killer )
{
if( killer == NULL )
return FALSE;
// killer's player must have the listed science
Player *killerPlayer = killer->getControllingPlayer();
if( killerPlayer == NULL )
return FALSE;
if( ! killerPlayer->hasScience( currentCrateData->m_killerScience ) )
return FALSE;
return TRUE;
}
Object *CreateCrateDie::createCrate( CrateTemplate const *currentCrateData )
{
Coord3D centerPoint = *getObject()->getPosition();
PathfindLayerEnum layer = getObject()->getLayer();
// CreationChance is used for the success of this block, but this block can have any number of potential actual crates
Real multipleCratePick = GameLogicRandomValueReal( 0, 1 );
Real multipleCrateRunningTotal = 0;
AsciiString crateName = "";
for( crateCreationEntryConstIterator iter = currentCrateData->m_possibleCrates.begin();
iter != currentCrateData->m_possibleCrates.end();
iter++
)
{
multipleCrateRunningTotal += (*iter).crateChance;
if( multipleCrateRunningTotal > multipleCratePick )
{
// Run through the list of possibles, and if the sum of the chances is greater than my random pick,
// then this is the correct one. (This simulates contiguous %, or weighted distribution)
crateName = (*iter).crateName;
break;
}
}
// At this point, I could very well have a "" for the type, if the Designer didn't make the sum of chances 1
ThingTemplate const *crateType = TheThingFactory->findTemplate( crateName );
if( crateType == NULL )
return NULL;
Bool spotFound = FALSE;
Coord3D creationPoint;
FindPositionOptions fpOptions;
fpOptions.minRadius = 0.0f;
fpOptions.maxRadius = 5.0f;
fpOptions.relationshipObject = getObject();
fpOptions.flags = FPF_IGNORE_ALLY_OR_NEUTRAL_UNITS; // So the dead guy won't block, nor will his dead hulk.
if (layer != LAYER_GROUND) {
creationPoint = centerPoint;
spotFound = true;
} else if( ThePartitionManager->findPositionAround( &centerPoint, &fpOptions, &creationPoint ) )
{
spotFound = TRUE;
}
else
{
// If the tight ignore units scan fails, then try a great big scan so we appear on the edge
// of the large dead thing (building rubble)
fpOptions.minRadius = 0.0f;
fpOptions.maxRadius = 125.0f;
fpOptions.relationshipObject = NULL;
fpOptions.flags = FPF_NONE;
if( ThePartitionManager->findPositionAround( &centerPoint, &fpOptions, &creationPoint ) )
{
spotFound = TRUE;
}
}
if( spotFound )
{
Object *newCrate = TheThingFactory->newObject( crateType, NULL );
newCrate->setPosition( &creationPoint );
newCrate->setOrientation( GameLogicRandomValueReal( 0, 2*PI ) );
newCrate->setLayer(layer);
Drawable *crateDrawable = newCrate->getDrawable();
if( crateDrawable )
{
crateDrawable->setTerrainDecal(TERRAIN_DECAL_CRATE);
crateDrawable->setTerrainDecalSize(2.5f * newCrate->getGeometryInfo().getMajorRadius(),
2.5f * newCrate->getGeometryInfo().getMajorRadius() ) ;
crateDrawable->setTerrainDecalFadeTarget(1.0f, 0.03f);
}
return newCrate;
}
return NULL;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void CreateCrateDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void CreateCrateDie::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
DieModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void CreateCrateDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

Some files were not shown because too many files have changed in this diff Show More