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(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,833 @@
/*
** Command & Conquer Generals(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 "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.
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_INNER, newInstance(AIGuardInnerState)( this ), AI_GUARD_OUTER, AI_GUARD_OUTER );
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_RETURN, AI_GUARD_RETURN );
}
//--------------------------------------------------------------------------------------
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;
PartitionFilter *filters[16];
Int count = 0;
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 )
{
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==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 AIGuardInnerState::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.
}
}
//-- 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;
}
m_exitConditions.m_attackGiveUpFrame = TheGameLogic->getFrame() + TheAI->getAiData()->m_guardChaseUnitFrames;
m_exitConditions.m_conditionsToConsider = (ExitConditions::ATTACK_ExitIfExpiredDuration |
ExitConditions::ATTACK_ExitIfNoUnitFound);
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();
}

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,866 @@
/*
** Command & Conquer Generals(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);
// 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(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,543 @@
/*
** Command & Conquer Generals(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),
//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;
// 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();
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);
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 (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_3);
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.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,152 @@
/*
** Command & Conquer Generals(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
#define DEFINE_DAMAGE_NAMES // for DamageNames[]
#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;
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)INI::scanIndexList(damageName, TheDamageNames);
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,360 @@
/*
** Command & Conquer Generals(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"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
struct AutoHealPlayerScanHelper
{
KindOfMaskType m_kindOfToTest;
Object *m_theHealer;
ObjectPointerList *m_objectList;
};
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( !testObj->isAnyKindOf(helper->m_kindOfToTest) )
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 = getAutoHealBehaviorModuleData()->m_kindOf;
helper.m_objectList = &objectsToHeal;
helper.m_theHealer = getObject();
// 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() && obj->isAnyKindOf( d->m_kindOf ) )
{
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,73 @@
/*
** Command & Conquer Generals(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(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(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,772 @@
/*
** Command & Conquer Generals(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/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;
}
//-------------------------------------------------------------------------------------------------
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);
// 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(OBJECT_STATUS_NO_COLLISIONS);
return true;
}
//-------------------------------------------------------------------------------------------------
void DumbProjectileBehavior::detonate()
{
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);
}
//-------------------------------------------------------------------------------------------------
/**
* 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) && 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
{
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(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,161 @@
/*
** Command & Conquer Generals(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 "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"
#include "Common/Player.h"
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();
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( BitTest( getObject()->getStatusBits(), OBJECT_STATUS_UNDER_CONSTRUCTION ) == TRUE )
return;
Int64 activation, conflicting;
getUpgradeActivationMasks( activation, conflicting );
if( getObject()->getObjectCompletedUpgradeMask() & conflicting )
{
return;
}
if( getObject()->getControllingPlayer()->getCompletedUpgradeMask() & conflicting )
{
return;
}
if (d->m_deathWeapon)
{
// fire the default weapon
TheWeaponStore->createAndFireTempWeapon(d->m_deathWeapon, getObject(), getObject()->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,489 @@
/*
** Command & Conquer Generals(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_genFX = NULL;
m_distanceAroundObject = TheGlobalData->m_standardMinefieldDistance;
m_minesPerSquareFoot = TheGlobalData->m_standardMinefieldDensity;
m_onDeath = false;
m_borderOnly = true;
m_alwaysCircular = 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 ) },
{ "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) },
{ "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 ) : BehaviorModule( thing, moduleData )
{
m_target.zero();
m_generated = false;
m_hasTarget = false;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
GenerateMinefieldBehavior::~GenerateMinefieldBehavior( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
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;
}
}
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 = 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);
}
// ------------------------------------------------------------------------------------------------
/** 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->xferCoord3D( &m_target );
} // 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,215 @@
/*
** Command & Conquer Generals(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,391 @@
/*
** Command & Conquer Generals(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 )
{
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
} // 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,699 @@
/*
** Command & Conquer Generals(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;
}
//-------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*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 ) },
{ 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(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(OBJECT_STATUS_MASKED);
}
else
{
getObject()->clearModelConditionState(MODELCONDITION_RUBBLE);
getObject()->clearStatus(OBJECT_STATUS_MASKED);
}
}
//-----------------------------------------------------------------------------
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(OBJECT_STATUS_MASKED);
}
else
{
getObject()->clearModelConditionState(MODELCONDITION_RUBBLE);
getObject()->clearStatus(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(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,328 @@
/*
** Command & Conquer Generals(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(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,248 @@
/*
** Command & Conquer Generals(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_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(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 )
{
// extend functionality
OpenContain::onContaining( obj );
// 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(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,596 @@
/*
** Command & Conquer Generals(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"
// 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;
} // 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 ) },
{ 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( BitTest( self->getStatusBits(), 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
// 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, as a tower we're not interesting anyway
if( obj == us )
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,488 @@
/*
** Command & Conquer Generals(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
newWorkerRespawnProcess( NULL );
} /// 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();
// we want to prevent the player from selecting and doing things with this worker
worker->setStatus( 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 &&
BitTest( reconstructing->getStatusBits(), OBJECT_STATUS_UNDER_CONSTRUCTION ) == FALSE )
{
// 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,582 @@
/*
** Command & Conquer Generals(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 )
{
if (!isDieApplicable(damageInfo))
return;
AIUpdateInterface *ai = getObject()->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(getObject(), PLAYERMASK_ALL, TRUE);
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 (/* UpdateModuleInterface** */ 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;
}
}
}
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(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,180 @@
/*
** Command & Conquer Generals(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->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(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(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,141 @@
/*
** Command & Conquer Generals(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"
// 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
{
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(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(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(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,71 @@
/*
** Command & Conquer Generals(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,191 @@
/*
** Command & Conquer Generals(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() & 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;
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( 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,290 @@
/*
** Command & Conquer Generals(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_AIRCRAFT ) || other->isKindOf( KINDOF_BOAT ) )
{
//Can't hijack planes and boats!
return FALSE;
}
if ( other->getStatusBits() & 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( 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 );
targetExp->setVeterancyLevel( highestLevel );
}
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( OBJECT_STATUS_NO_COLLISIONS );
obj->setStatus( OBJECT_STATUS_MASKED );
obj->setStatus( 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,220 @@
/*
** Command & Conquer Generals(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 "GameClient/Anim2D.h"
#include "GameClient/FXList.h"
#include "GameClient/InGameUI.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/CrateCollide.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
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
return TRUE;
}
// ------------------------------------------------------------------------------------------------
/** 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(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,107 @@
/*
** Command & Conquer Generals(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;
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;
}
// ------------------------------------------------------------------------------------------------
/** 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,253 @@
/*
** Command & Conquer Generals(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( 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);
}
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::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::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(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(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(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,173 @@
/*
** Command & Conquer Generals(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", INI::parseBitString32, TheObjectStatusBitNames, offsetof( FireWeaponCollideModuleData, m_requiredStatus ) },
{ "ForbiddenStatus", INI::parseBitString32, TheObjectStatusBitNames, 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();
UnsignedInt status = getObject()->getStatusBits();
if( (status & d->m_requiredStatus) != d->m_requiredStatus )
return FALSE;
if( (status & d->m_forbiddenStatus) != 0 )
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(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(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 )
{
OpenContain::onContaining(obj);
// 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( BitTest( getObject()->getStatusBits(), 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(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,498 @@
/*
** Command & Conquer Generals(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 )
{
OpenContain::onContaining(rider);
// 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 )
{
static NameKeyType key_StealthUpdate = NAMEKEY( "StealthUpdate" );
StealthUpdate* stealth = (StealthUpdate*)obj->findUpdateModule( key_StealthUpdate );
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,498 @@
/*
** Command & Conquer Generals(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 "GameClient/ControlBar.h"
#include "GameClient/Drawable.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/OverlordContain.h"
#include "GameLogic/Object.h"
#include "GameLogic/PartitionManager.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
OverlordContainModuleData::OverlordContainModuleData()
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void OverlordContainModuleData::buildFieldParse(MultiIniFieldParse& p)
{
TransportContainModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
OverlordContain::OverlordContain( Thing *thing, const ModuleData *moduleData ) :
TransportContain( thing, moduleData )
{
m_redirectionActivated = FALSE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
OverlordContain::~OverlordContain( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
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 )
{
// Do you mean me the Overlord, or my behavior of passing stuff on to my passengers?
if( getRedirectedContain() == NULL )
{
TransportContain::onContaining( obj );
activateRedirectedContain();//Am now carrying something
return;
}
OpenContain::onContaining(obj);
getRedirectedContain()->onContaining( obj );
}
//-------------------------------------------------------------------------------------------------
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;
}
}
}
// ------------------------------------------------------------------------------------------------
/** 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,737 @@
/*
** Command & Conquer Generals(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/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(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 );
}
}
}
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(OBJECT_STATUS_NO_COLLISIONS);
if (rider)
rider->setStatus(OBJECT_STATUS_NO_COLLISIONS);
}
else
{
// opened/nonempty chutes DO collide...
parachute->clearStatus(OBJECT_STATUS_NO_COLLISIONS);
if (rider)
rider->clearStatus(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 )
{
OpenContain::onContaining(rider);
// objects inside a transport are held
rider->setDisabled( DISABLED_HELD );
rider->setStatus(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(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(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
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 );
}
}
//-------------------------------------------------------------------------------------------------
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(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,563 @@
/*
** Command & Conquer Generals(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"
#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_resetMoodCheckTimeOnExit = true;
m_destroyRidersWhoAreNotFreeToExit = false;
m_exitPitchRate = 0.0f;
m_initialPayload.count = 0;
m_healthRegen = 0.0f;
m_exitDelay = 0;
//
// 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 ) },
{ 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)
{
return (m_extraSlotsInUse + getContainCount() + transportSlotCount <= getContainMax());
}
else
{
return true;
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void TransportContain::onContaining( Object *rider )
{
OpenContain::onContaining(rider);
// 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
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
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;
DEBUG_ASSERTCRASH(m_extraSlotsInUse >= 0 && m_extraSlotsInUse + getContainCount() <= getContainMax(), ("Hmm, bad slot count"));
// 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;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
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;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
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.
{
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 );
}
}
}
// ------------------------------------------------------------------------------------------------
/** 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,441 @@
/*
** Command & Conquer Generals(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 );
}
//-------------------------------------------------------------------------------------------------
/** 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 )
{
OpenContain::onContaining(obj);
// objects inside a building are held
obj->setDisabled( DISABLED_HELD );
}
//-------------------------------------------------------------------------------------------------
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::onBuildComplete( void )
{
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;
}
// ------------------------------------------------------------------------------------------------
/** 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(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,181 @@
/*
** Command & Conquer Generals(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 = "";
m_exemptStatus = OBJECT_STATUS_NONE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void GrantUpgradeCreateModuleData::buildFieldParse(MultiIniFieldParse& p)
{
CreateModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "UpgradeToGrant", INI::parseAsciiString, NULL, offsetof( GrantUpgradeCreateModuleData, m_upgradeName ) },
{ "ExemptStatus", INI::parseBitString32, TheObjectStatusBitNames, 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 )
{
ObjectStatusBits exemptStatus = (ObjectStatusBits)getGrantUpgradeCreateModuleData()->m_exemptStatus;
ObjectStatusBits currentStatus = (ObjectStatusBits)getObject()->getStatusBits();
if( BitTest( exemptStatus, OBJECT_STATUS_UNDER_CONSTRUCTION ) == TRUE )
{
if( BitTest( currentStatus, OBJECT_STATUS_UNDER_CONSTRUCTION ) == FALSE )
{
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 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,114 @@
/*
** Command & Conquer Generals(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(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(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(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(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(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(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,466 @@
/*
** Command & Conquer Generals(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_ALL;
m_damageOCLTypes = DAMAGE_TYPE_FLAGS_ALL;
m_damageParticleTypes = DAMAGE_TYPE_FLAGS_ALL;
} // 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() );
} // 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(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(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

View File

@@ -0,0 +1,132 @@
/*
** Command & Conquer Generals(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: CreateObjectDie.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Michael S. Booth, January 2002
// Desc: Create an object upon this object's death
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_OBJECT_STATUS_NAMES
#include "Common/ThingFactory.h"
#include "Common/Xfer.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Module/CreateObjectDie.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectCreationList.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
CreateObjectDieModuleData::CreateObjectDieModuleData()
{
m_ocl = NULL;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void CreateObjectDieModuleData::buildFieldParse(MultiIniFieldParse& p)
{
DieModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "CreationList", INI::parseObjectCreationList, NULL, offsetof( CreateObjectDieModuleData, m_ocl ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CreateObjectDie::CreateObjectDie( Thing *thing, const ModuleData* moduleData ) : DieModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CreateObjectDie::~CreateObjectDie( void )
{
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void CreateObjectDie::onDie( const DamageInfo * damageInfo )
{
if (!isDieApplicable(damageInfo))
return;
Object *damageDealer = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID );
ObjectCreationList::create(getCreateObjectDieModuleData()->m_ocl, getObject(), damageDealer);
} // end onDie
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void CreateObjectDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void CreateObjectDie::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 CreateObjectDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,248 @@
/*
** Command & Conquer Generals(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: CrushDie.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Colin Day, November 2001
// Desc: Crush Die module
///////////////////////////////////////////////////////////////////////////////////////////////////
// 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/RandomValue.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/Module/BodyModule.h"
#include "GameLogic/Module/CrushDie.h"
//-------------------------------------------------------------------------------------------------
// Figure out which crush point was hit so the correct crushed object can be swapped in
// Figure out which crush point was hit so the correct crushed object can be swapped in
static CrushEnum crushLocationCheck( Object* crusherObject, Object* victimObject )
{
if( (crusherObject == NULL) || (victimObject == NULL) )
return NO_CRUSH;
Bool frontCrushed = victimObject->getBodyModule()->getFrontCrushed();
Bool backCrushed = victimObject->getBodyModule()->getBackCrushed();
// const Coord3D *dir = crusherObject->getUnitDirectionVector2D();
const Coord3D *otherDir = victimObject->getUnitDirectionVector2D();
const Coord3D *pos = crusherObject->getPosition();
const Coord3D *otherPos = victimObject->getPosition();
Real crushPointOffsetDistance = victimObject->getGeometryInfo().getMajorRadius() * 0.5;
Coord3D crushPointOffset;
crushPointOffset.x = otherDir->x * crushPointOffsetDistance;
crushPointOffset.y = otherDir->y * crushPointOffsetDistance;
crushPointOffset.z = 0;
Coord3D comparisonCoord;
Real dx, dy;
CrushEnum retval = NO_CRUSH;
Real bestDist = 99999;
// PhysicsCollide has already done the logic of which point to smoosh and waited until we crossed that point
// so at this point we just need to know which crush point is closest.
if( !frontCrushed && !backCrushed )
{
// Check the middle crush point
comparisonCoord = *otherPos;//copy so can move to each crush point
dx = comparisonCoord.x - pos->x;
dy = comparisonCoord.y - pos->y;
Real dist = (Real)( dx*dx + dy*dy );
//otherwise we want to make sure we get the closest valid crush point
retval = TOTAL_CRUSH;
bestDist = dist;
}
if( !frontCrushed )
{
// Check the front point.
comparisonCoord = *otherPos;
comparisonCoord.x += crushPointOffset.x;
comparisonCoord.y += crushPointOffset.y;
dx = comparisonCoord.x - pos->x;
dy = comparisonCoord.y - pos->y;
Real dist = (Real)( dx*dx + dy*dy );
if( dist < bestDist )//closer
{
if( backCrushed )
{
retval = TOTAL_CRUSH;
bestDist = dist;
}
else
{
retval = FRONT_END_CRUSH;
bestDist = dist;
}
}
}
if( !backCrushed )
{
// Check back point
comparisonCoord = *otherPos;
comparisonCoord.x -= crushPointOffset.x;
comparisonCoord.y -= crushPointOffset.y;
dx = comparisonCoord.x - pos->x;
dy = comparisonCoord.y - pos->y;
Real dist = (Real)( dx*dx + dy*dy );
if( dist < bestDist )//closer
{
if( frontCrushed )
{
retval = TOTAL_CRUSH;
bestDist = dist;
}
else
{
retval = BACK_END_CRUSH;
bestDist = dist;
}
}
}
return retval;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CrushDie::CrushDie( Thing *thing, const ModuleData* moduleData ) : DieModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
CrushDie::~CrushDie( void )
{
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void CrushDie::onDie( const DamageInfo * damageInfo )
{
if (!isDieApplicable(damageInfo))
return;
DEBUG_ASSERTCRASH(damageInfo->in.m_damageType == DAMAGE_CRUSH, ("this should only be used for crush damage\n"));
if (damageInfo->in.m_damageType != DAMAGE_CRUSH)
return;
Object *damageDealer = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID );
DEBUG_ASSERTCRASH(damageDealer,("You must have a damageDealer source for this effect"));
CrushEnum crushType = damageDealer ? crushLocationCheck(damageDealer, getObject()) : TOTAL_CRUSH;
if (crushType != NO_CRUSH)
{
if (getCrushDieModuleData()->m_crushSounds[crushType].getEventName().isEmpty() == false)
{
// be sure that 0==never, 100==always
// MDC: moving to GameLogicRandomValue. This does not need to be synced, but having it so makes searches *so* much nicer.
if (GameLogicRandomValue(0, 99) < getCrushDieModuleData()->m_crushSoundPercent[crushType])
{
AudioEventRTS crushSound(getCrushDieModuleData()->m_crushSounds[crushType]);
crushSound.setObjectID(getObject()->getID());
TheAudio->addAudioEvent(&crushSound);
}
}
{
Object *me = getObject();
if (me)
{
me->getBodyModule()->setFrontCrushed(crushType == TOTAL_CRUSH || crushType == FRONT_END_CRUSH);
me->getBodyModule()->setBackCrushed(crushType == TOTAL_CRUSH || crushType == BACK_END_CRUSH);
ModelConditionFlags newCrushed;
newCrushed.set(MODELCONDITION_FRONTCRUSHED, (crushType == TOTAL_CRUSH || crushType == FRONT_END_CRUSH));
newCrushed.set(MODELCONDITION_BACKCRUSHED, crushType == TOTAL_CRUSH || crushType == BACK_END_CRUSH);
me->getDrawable()->clearAndSetModelConditionFlags(
MAKE_MODELCONDITION_MASK2(MODELCONDITION_BACKCRUSHED, MODELCONDITION_FRONTCRUSHED),
newCrushed);
}
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void CrushDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void CrushDie::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 CrushDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,149 @@
/*
** Command & Conquer Generals(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: DamDie.cpp ///////////////////////////////////////////////////////////////////////////////
// Author: Colin Day, April 2002
// Desc: The big water dam dying
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/RandomValue.h"
#include "Common/Xfer.h"
#include "GameClient/ParticleSys.h"
#include "GameClient/TerrainVisual.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/DamDie.h"
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
DamDieModuleData::DamDieModuleData( void )
{
} // end DamDieModuleData
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void DamDieModuleData::buildFieldParse(MultiIniFieldParse& p)
{
DieModuleData::buildFieldParse( p );
// static const FieldParse dataFieldParse[] =
// {
// { 0, 0, 0, 0 }
// };
//
// p.add(dataFieldParse);
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
DamDie::DamDie( Thing *thing, const ModuleData *moduleData )
:DieModule( thing, moduleData )
{
} // end DamDie
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
DamDie::~DamDie( void )
{
} // end ~DamDie
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void DamDie::onDie( const DamageInfo *damageInfo )
{
if (!isDieApplicable(damageInfo))
return;
// enable all the water wave objects on the map
Object *obj;
for( obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() )
{
// only care aboue water waves
if( obj->isKindOf( KINDOF_WAVEGUIDE ) == FALSE )
continue;
// clear any disabled status of the water wave
obj->clearDisabled( DISABLED_DEFAULT );
} // end for, obj
} // end onDie
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void DamDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void DamDie::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 DamDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,98 @@
/*
** Command & Conquer Generals(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: DestroyDie.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Colin Day, November 2001
// Desc: Default Die module
///////////////////////////////////////////////////////////////////////////////////////////////////
// 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/Module/DestroyDie.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
DestroyDie::DestroyDie( Thing *thing, const ModuleData* moduleData ) : DieModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
DestroyDie::~DestroyDie( void )
{
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void DestroyDie::onDie( const DamageInfo *damageInfo )
{
if (!isDieApplicable(damageInfo))
return;
TheGameLogic->destroyObject(getObject());
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void DestroyDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void DestroyDie::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 DestroyDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,130 @@
/*
** Command & Conquer Generals(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: DieModule.cpp ////////////////////////////////////////////////////////////////////////////
// Author:
// Desc:
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_OBJECT_STATUS_NAMES
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Module/DieModule.h"
#include "GameLogic/Object.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
//-------------------------------------------------------------------------------------------------
DieMuxData::DieMuxData() :
m_deathTypes(DEATH_TYPE_FLAGS_ALL),
m_veterancyLevels(VETERANCY_LEVEL_FLAGS_ALL),
m_exemptStatus(OBJECT_STATUS_NONE),
m_requiredStatus(OBJECT_STATUS_NONE)
{
}
//-------------------------------------------------------------------------------------------------
const FieldParse* DieMuxData::getFieldParse()
{
static const FieldParse dataFieldParse[] =
{
{ "DeathTypes", INI::parseDeathTypeFlags, NULL, offsetof( DieMuxData, m_deathTypes ) },
{ "VeterancyLevels", INI::parseVeterancyLevelFlags, NULL, offsetof( DieMuxData, m_veterancyLevels ) },
{ "ExemptStatus", INI::parseBitString32, TheObjectStatusBitNames, offsetof( DieMuxData, m_exemptStatus ) },
{ "RequiredStatus", INI::parseBitString32, TheObjectStatusBitNames, offsetof( DieMuxData, m_requiredStatus ) },
{ 0, 0, 0, 0 }
};
return dataFieldParse;
}
//-------------------------------------------------------------------------------------------------
Bool DieMuxData::isDieApplicable(const Object* obj, const DamageInfo *damageInfo) const
{
// wrong death type? punt
if (!getDeathTypeFlag(m_deathTypes, damageInfo->in.m_deathType))
return false;
// wrong vet level? punt
if (!getVeterancyLevelFlag(m_veterancyLevels, obj->getVeterancyLevel()))
return false;
// all 'exempt' bits must be clear for us to run.
if ((obj->getStatusBits() & m_exemptStatus) != 0)
return false;
// all 'required' bits must be set for us to run.
if ((obj->getStatusBits() & m_requiredStatus) != m_requiredStatus)
return false;
return true;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void DieModule::crc( Xfer *xfer )
{
// extend base class
BehaviorModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method */
// ------------------------------------------------------------------------------------------------
void DieModule::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 DieModule::loadPostProcess( void )
{
// call base class
BehaviorModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,152 @@
/*
** Command & Conquer Generals(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: EjectPilotDie.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Steven Johnson, April 2002
// Desc: Create an object upon this object's death
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/GameAudio.h"
#include "Common/Player.h"
#include "Common/ThingFactory.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Module/EjectPilotDie.h"
#include "GameLogic/ExperienceTracker.h"
#include "GameLogic/Object.h"
#include "GameLogic/ObjectCreationList.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
EjectPilotDieModuleData::EjectPilotDieModuleData() :
m_oclInAir(NULL),
m_oclOnGround(NULL),
m_invulnerableTime(0)
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void EjectPilotDieModuleData::buildFieldParse(MultiIniFieldParse& p)
{
DieModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "AirCreationList", INI::parseObjectCreationList, NULL, offsetof( EjectPilotDieModuleData, m_oclInAir ) },
{ "GroundCreationList", INI::parseObjectCreationList, NULL, offsetof( EjectPilotDieModuleData, m_oclOnGround ) },
{ "InvulnerableTime", INI::parseDurationUnsignedInt, NULL, offsetof(EjectPilotDieModuleData, m_invulnerableTime ) },
{ 0, 0, 0, 0 }
};
p.add(dataFieldParse);
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
EjectPilotDie::EjectPilotDie( Thing *thing, const ModuleData* moduleData ) : DieModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
EjectPilotDie::~EjectPilotDie( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
/*static*/ void EjectPilotDie::ejectPilot(const ObjectCreationList* ocl, const Object* dyingObject, const Object* damageDealer)
{
if (!ocl || !dyingObject)
return; // it's OK for damageDealer to be null
ObjectCreationList::create(ocl, dyingObject, damageDealer);
AudioEventRTS voiceEject = *(dyingObject->getTemplate()->getPerUnitSound("VoiceEject"));
voiceEject.setPosition( dyingObject->getPosition() );
voiceEject.setPlayerIndex( dyingObject->getControllingPlayer()->getPlayerIndex() );
TheAudio->addAudioEvent(&voiceEject);
AudioEventRTS soundEject = *(dyingObject->getTemplate()->getPerUnitSound("SoundEject"));
soundEject.setPosition( dyingObject->getPosition() );
TheAudio->addAudioEvent(&soundEject);
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void EjectPilotDie::onDie( const DamageInfo * damageInfo )
{
if (!isDieApplicable(damageInfo))
return;
Object* damageDealer = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID );
const EjectPilotDieModuleData* d = getEjectPilotDieModuleData();
const ObjectCreationList* ocl = getObject()->isSignificantlyAboveTerrain() ? d->m_oclInAir : d->m_oclOnGround;
ejectPilot(ocl, getObject(), damageDealer);
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void EjectPilotDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void EjectPilotDie::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 EjectPilotDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,121 @@
/*
** Command & Conquer Generals(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: FXListDie.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Steven Johnson, Jan 2002
// Desc: Simple Die module
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#define DEFINE_DAMAGE_NAMES
#include "Common/INI.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameClient/FXList.h"
#include "GameLogic/Damage.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/FXListDie.h"
#include "GameLogic/Module/AIUpdate.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
FXListDie::FXListDie( Thing *thing, const ModuleData* moduleData ) : DieModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
FXListDie::~FXListDie( void )
{
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void FXListDie::onDie( const DamageInfo *damageInfo )
{
if (!isDieApplicable(damageInfo))
return;
const FXListDieModuleData* d = getFXListDieModuleData();
if (d->m_defaultDeathFX)
{
// if the object has any ambient sound(s), kill 'em now.
TheAudio->stopAllAmbientsBy(getObject());
if (d->m_orientToObject)
{
Object *damageDealer = TheGameLogic->findObjectByID( damageInfo->in.m_sourceID );
FXList::doFXObj(getFXListDieModuleData()->m_defaultDeathFX, getObject(), damageDealer);
}
else
{
FXList::doFXPos(getFXListDieModuleData()->m_defaultDeathFX, getObject()->getPosition());
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void FXListDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void FXListDie::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 FXListDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,102 @@
/*
** Command & Conquer Generals(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: KeepObjectDie.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Kris Morness, November 2002
// Desc: Die module for things that want to leave rubble in the world and don't have other die
// modules. This fixes civilian buildings that don't have garrison contains. Garrison
// contains have a die module built in, so these buildings need something. Without it
// they default to the destroydie module which outright removes the object.
///////////////////////////////////////////////////////////////////////////////////////////////////
// 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/Module/KeepObjectDie.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
KeepObjectDie::KeepObjectDie( Thing *thing, const ModuleData* moduleData ) : DieModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
KeepObjectDie::~KeepObjectDie( void )
{
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void KeepObjectDie::onDie( const DamageInfo *damageInfo )
{
if( !isDieApplicable(damageInfo) )
{
return;
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void KeepObjectDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void KeepObjectDie::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 KeepObjectDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,206 @@
/*
** Command & Conquer Generals(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: RebuildHoleExposeDie.cpp /////////////////////////////////////////////////////////////////
// Author: Colin Day, June 2002
// Desc: When a structure dies with this module, a rebuild hole will be created in place
// of the structure that will automatically rebuild the structure
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDE FILES //////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/PlayerList.h"
#include "Common/ThingFactory.h"
#include "Common/Xfer.h"
#include "GameLogic/AI.h"
#include "GameLogic/AIPathfind.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Module/AIUpdate.h"
#include "GameLogic/Module/BodyModule.h"
#include "GameLogic/Module/RebuildHoleBehavior.h"
#include "GameLogic/Module/RebuildHoleExposeDie.h"
#include "GameLogic/Object.h"
#include "GameLogic/ScriptEngine.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
RebuildHoleExposeDieModuleData::RebuildHoleExposeDieModuleData()
{
m_holeMaxHealth = 0.0f;
m_transferAttackers = true;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
/*static*/ void RebuildHoleExposeDieModuleData::buildFieldParse( MultiIniFieldParse &p )
{
DieModuleData::buildFieldParse(p);
static const FieldParse dataFieldParse[] =
{
{ "HoleName", INI::parseAsciiString, NULL, offsetof( RebuildHoleExposeDieModuleData, m_holeName ) },
{ "HoleMaxHealth", INI::parseReal, NULL, offsetof( RebuildHoleExposeDieModuleData, m_holeMaxHealth ) },
{ "TransferAttackers", INI::parseBool, NULL, offsetof( RebuildHoleExposeDieModuleData, m_transferAttackers ) },
{ 0, 0, 0, 0 }
};
p.add( dataFieldParse );
} // end buildFieldParse
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
RebuildHoleExposeDie::RebuildHoleExposeDie( Thing *thing, const ModuleData* moduleData )
: DieModule( thing, moduleData )
{
} // end RebuildHoleExposeDie
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
RebuildHoleExposeDie::~RebuildHoleExposeDie( void )
{
} // end ~RebuildHoleExposeDie
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void RebuildHoleExposeDie::onDie( const DamageInfo *damageInfo )
{
if (!isDieApplicable(damageInfo))
return;
const RebuildHoleExposeDieModuleData *modData = getRebuildHoleExposeDieModuleData();
Object *us = getObject();
//
// if we are being constructed from either the first time or from a hole reconstruction
// we do not "spawn" a hole object
//
if( us->getControllingPlayer() != ThePlayerList->getNeutralPlayer() && (us->getControllingPlayer()->isPlayerActive()) &&
(BitTest( us->getStatusBits(), OBJECT_STATUS_UNDER_CONSTRUCTION ) == FALSE ))
{
Object *hole;
// create the hole
hole = TheThingFactory->newObject( TheThingFactory->findTemplate( modData->m_holeName ),
getObject()->getTeam() );
// put the hole at our position and angle
hole->setPosition( us->getPosition() );
hole->setOrientation( us->getOrientation() );
//
// modify the hole extents to be the same as ours because we need to preserve the
// same amount of space for the rebuilding process
//
hole->setGeometryInfo( us->getGeometryInfo() );
//
// Transfer the building's name to the hole
//
TheScriptEngine->transferObjectName( us->getName(), hole );
//
// add to pathfind map, this really should be wrapped up somewhere in the creation
// pipeline of the object automagically!
//
TheAI->pathfinder()->addObjectToPathfindMap( hole );
// set the health of the hole to that defined by our data
BodyModuleInterface *body = hole->getBodyModule();
body->setMaxHealth( modData->m_holeMaxHealth );
// set the information in the hole about what to build
RebuildHoleBehaviorInterface *rhbi = RebuildHoleBehavior::getRebuildHoleBehaviorInterfaceFromObject( hole );
// sanity
DEBUG_ASSERTCRASH( rhbi, ("RebuildHoleExposeDie: No Rebuild Hole Behavior interface on hole\n") );
// start the rebuild process
if( rhbi )
rhbi->startRebuildProcess( us->getTemplate(), us->getID() );
if (modData->m_transferAttackers)
{
for ( Object *obj = TheGameLogic->getFirstObject(); obj; obj = obj->getNextObject() )
{
AIUpdateInterface* ai = obj->getAI();
if (!ai)
continue;
ai->transferAttack(us->getID(), hole->getID());
}
}
} // end if
} // end onDie
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void RebuildHoleExposeDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void RebuildHoleExposeDie::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 RebuildHoleExposeDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,131 @@
/*
** Command & Conquer Generals(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: SpecialPowerCompletionDie.cpp ////////////////////////////////////////////////////////////
// Author: Matthew D. Campbell, May 2002
// Desc: Die method responsible for telling TheScriptEngine that a special power has been completed
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Player.h"
#include "Common/SpecialPower.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/SpecialPowerCompletionDie.h"
#include "GameLogic/Object.h"
#include "GameLogic/ScriptEngine.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SpecialPowerCompletionDie::SpecialPowerCompletionDie( Thing *thing, const ModuleData* moduleData ) : DieModule( thing, moduleData )
{
m_creatorID = INVALID_ID;
m_creatorSet = FALSE;
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
SpecialPowerCompletionDie::~SpecialPowerCompletionDie( void )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void SpecialPowerCompletionDie::onDie( const DamageInfo *damageInfo )
{
if (!isDieApplicable(damageInfo))
return;
notifyScriptEngine();
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void SpecialPowerCompletionDie::notifyScriptEngine( void )
{
if (m_creatorID != INVALID_ID)
{
TheScriptEngine->notifyOfCompletedSpecialPower(
getObject()->getControllingPlayer()->getPlayerIndex(),
getSpecialPowerCompletionDieModuleData()->m_specialPowerTemplate->getName(),
m_creatorID);
}
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
void SpecialPowerCompletionDie::setCreator( ObjectID creatorID )
{
if (!m_creatorSet)
{
m_creatorSet = TRUE;
m_creatorID = creatorID;
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void SpecialPowerCompletionDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void SpecialPowerCompletionDie::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// extend base class
DieModule::xfer( xfer );
// creator id
xfer->xferObjectID( &m_creatorID );
// creator set
xfer->xferBool( &m_creatorSet );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void SpecialPowerCompletionDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,127 @@
/*
** Command & Conquer Generals(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: UpgradeDie.cpp ///////////////////////////////////////////////////////////////////////////
// Author: Kris Morness, August 2002
// Desc: When object dies, the parent object is freed of the specified object upgrade field.
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/ThingTemplate.h"
#include "Common/Upgrade.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Module/UpgradeDie.h"
#include "GameLogic/Object.h"
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UpgradeDie::UpgradeDie( Thing *thing, const ModuleData* moduleData ) : DieModule( thing, moduleData )
{
}
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
UpgradeDie::~UpgradeDie( void )
{
}
//-------------------------------------------------------------------------------------------------
/** The die callback. */
//-------------------------------------------------------------------------------------------------
void UpgradeDie::onDie( const DamageInfo *damageInfo )
{
if (!isDieApplicable(damageInfo))
return;
//Look for the object that created me.
Object *producer = TheGameLogic->findObjectByID( getObject()->getProducerID() );
if( producer )
{
//Okay, we found our parent... now look for the upgrade.
const UpgradeTemplate *upgrade = TheUpgradeCenter->findUpgrade( getUpgradeDieModuleData()->m_upgradeName );
if( upgrade )
{
//We found the upgrade, now see if the parent object has it set...
if( producer->hasUpgrade( upgrade ) )
{
//Cool, now remove it.
producer->removeUpgrade( upgrade );
}
else
{
DEBUG_ASSERTCRASH( 0, ("Object %s just died, but is trying to free upgrade %s in it's producer %s%s",
getObject()->getTemplate()->getName().str(),
getUpgradeDieModuleData()->m_upgradeName.str(),
producer->getTemplate()->getName().str(),
", which the producer doesn't have. This is used in cases where the producer builds an upgrade that can die... like ranger building scout drones.") );
}
}
}
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void UpgradeDie::crc( Xfer *xfer )
{
// extend base class
DieModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void UpgradeDie::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 UpgradeDie::loadPostProcess( void )
{
// extend base class
DieModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,276 @@
/*
** Command & Conquer Generals(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: ExperienceTracker.cpp //////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, February 2002
// Desc: Keeps track of experience points so Veterance levels can be gained
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "Common/ThingTemplate.h"
#include "GameLogic/ExperienceTracker.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
//-------------------------------------------------------------------------------------------------
ExperienceTracker::ExperienceTracker(Object *parent) :
m_parent(parent),
m_currentLevel(LEVEL_REGULAR),
m_experienceSink(INVALID_ID),
m_experienceScalar( 1.0f ),
m_currentExperience(0) // Added By Sadullah Nader
{
}
//-------------------------------------------------------------------------------------------------
ExperienceTracker::~ExperienceTracker()
{
}
//-------------------------------------------------------------------------------------------------
Int ExperienceTracker::getExperienceValue( const Object* killer ) const
{
// No experience for killing an ally, cheater.
if( killer->getRelationship( m_parent ) == ALLIES )
return 0;
return m_parent->getTemplate()->getExperienceValue(m_currentLevel);
}
//-------------------------------------------------------------------------------------------------
Bool ExperienceTracker::isTrainable() const
{
return m_parent->getTemplate()->isTrainable();
}
//-------------------------------------------------------------------------------------------------
Bool ExperienceTracker::isAcceptingExperiencePoints() const
{
return isTrainable() || (m_experienceSink != INVALID_ID);
}
//-------------------------------------------------------------------------------------------------
void ExperienceTracker::setExperienceSink( ObjectID sink )
{
m_experienceSink = sink;
}
//-------------------------------------------------------------------------------------------------
// Set Level to AT LEAST this... if we are already >= this level, do nothing.
void ExperienceTracker::setMinVeterancyLevel( VeterancyLevel newLevel )
{
// This does not check for IsTrainable, because this function is for explicit setting,
// so the setter is assumed to know what they are doing. The game function
// of addExperiencePoints cares about Trainability.
if (m_currentLevel < newLevel)
{
VeterancyLevel oldLevel = m_currentLevel;
m_currentLevel = newLevel;
m_currentExperience = m_parent->getTemplate()->getExperienceRequired(m_currentLevel); //Minimum for this level
if (m_parent)
m_parent->onVeterancyLevelChanged( oldLevel, newLevel );
}
}
//-------------------------------------------------------------------------------------------------
void ExperienceTracker::setVeterancyLevel( VeterancyLevel newLevel )
{
// This does not check for IsTrainable, because this function is for explicit setting,
// so the setter is assumed to know what they are doing. The game function
// of addExperiencePoints cares about Trainability, if flagged thus.
if (m_currentLevel != newLevel)
{
VeterancyLevel oldLevel = m_currentLevel;
m_currentLevel = newLevel;
m_currentExperience = m_parent->getTemplate()->getExperienceRequired(m_currentLevel); //Minimum for this level
if (m_parent)
m_parent->onVeterancyLevelChanged( oldLevel, newLevel );
}
}
//-------------------------------------------------------------------------------------------------
Bool ExperienceTracker::gainExpForLevel(Int levelsToGain, Bool canScaleForBonus)
{
Int newLevel = (Int)m_currentLevel + levelsToGain;
if (newLevel > LEVEL_LAST)
newLevel = LEVEL_LAST;
// gain what levels we can, even if we can't use 'em all
if (newLevel > m_currentLevel)
{
Int experienceNeeded = m_parent->getTemplate()->getExperienceRequired(newLevel) - m_currentExperience;
addExperiencePoints( experienceNeeded, canScaleForBonus );
return true;
}
return false;
}
//-------------------------------------------------------------------------------------------------
Bool ExperienceTracker::canGainExpForLevel(Int levelsToGain) const
{
Int newLevel = (Int)m_currentLevel + levelsToGain;
// return true if we can gain levels, even if we can't gain ALL the levels requested
if (newLevel > LEVEL_LAST)
newLevel = LEVEL_LAST;
return (newLevel > m_currentLevel);
}
//-------------------------------------------------------------------------------------------------
void ExperienceTracker::addExperiencePoints( Int experienceGain, Bool canScaleForBonus)
{
if( m_experienceSink != INVALID_ID )
{
// I have been set up to give my experience to someone else
Object *sinkPointer = TheGameLogic->findObjectByID( m_experienceSink );
if( sinkPointer )
{
// Not a fatal failure if not valid, he died when I was in the air.
sinkPointer->getExperienceTracker()->addExperiencePoints( experienceGain * m_experienceScalar, canScaleForBonus );
return;
}
}
if( !isTrainable() )
return; //safety
VeterancyLevel oldLevel = m_currentLevel;
Int amountToGain = experienceGain;
if ( canScaleForBonus )
amountToGain *= m_experienceScalar;
m_currentExperience += amountToGain;
Int levelIndex = 0;
while( ( (levelIndex + 1) < LEVEL_COUNT)
&& m_currentExperience >= m_parent->getTemplate()->getExperienceRequired(levelIndex + 1)
)
{
// If there is a higher level to qualify for, and I qualify for it, advance the index
levelIndex++;
}
m_currentLevel = (VeterancyLevel)levelIndex;
if( oldLevel != m_currentLevel )
{
// Edge trigger special level gain effects.
m_parent->onVeterancyLevelChanged( oldLevel, m_currentLevel );
}
}
//-------------------------------------------------------------------------------------------------
void ExperienceTracker::setExperienceAndLevel( Int experienceIn )
{
if( m_experienceSink != INVALID_ID )
{
// I have been set up to give my experience to someone else
Object *sinkPointer = TheGameLogic->findObjectByID( m_experienceSink );
if( sinkPointer )
{
// Not a fatal failure if not valid, he died when I was in the air.
sinkPointer->getExperienceTracker()->setExperienceAndLevel( experienceIn );
return;
}
}
if( !isTrainable() )
return; //safety
VeterancyLevel oldLevel = m_currentLevel;
m_currentExperience = experienceIn;
Int levelIndex = 0;
while( ( (levelIndex + 1) < LEVEL_COUNT)
&& m_currentExperience >= m_parent->getTemplate()->getExperienceRequired(levelIndex + 1)
)
{
// If there is a level to qualify for, and I qualify for it, advance the index
levelIndex++;
}
m_currentLevel = (VeterancyLevel)levelIndex;
if( oldLevel != m_currentLevel )
{
// Edge trigger special level gain effects.
m_parent->onVeterancyLevelChanged( oldLevel, m_currentLevel ); //<<== paradox! this may be a level lost!
}
}
//-----------------------------------------------------------------------------
void ExperienceTracker::crc( Xfer *xfer )
{
xfer->xferInt( &m_currentExperience );
xfer->xferUser( &m_currentLevel, sizeof( VeterancyLevel ) );
} // end crc
//-----------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version
*/
// ----------------------------------------------------------------------------
void ExperienceTracker::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// no need to save the m_parent pointer, it is connected on allocation time
// m_parent
// current level
xfer->xferUser( &m_currentLevel, sizeof( VeterancyLevel ) );
// current experience
xfer->xferInt( &m_currentExperience );
// experience sink
xfer->xferObjectID( &m_experienceSink );
// experience scalar
xfer->xferReal( &m_experienceScalar );
} // end xfer
//-----------------------------------------------------------------------------
void ExperienceTracker::loadPostProcess( void )
{
} // end loadPostProcess

View File

@@ -0,0 +1,376 @@
/*
** Command & Conquer Generals(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: FiringTracker.cpp //////////////////////////////////////////////////////////////////////
// Author: Graham Smallwood, February 2002
// Desc: Keeps track of shots fired and people targeted for weapons that want a history of such a thing
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/AudioHandleSpecialValues.h"
#include "Common/GameType.h"
#include "Common/GameAudio.h"
#include "Common/PerfTimer.h"
#include "Common/ThingTemplate.h"
#include "Common/Xfer.h"
#include "GameLogic/FiringTracker.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Module/ObjectHelper.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
//-------------------------------------------------------------------------------------------------
FiringTracker::FiringTracker(Thing* thing, const ModuleData *modData) : UpdateModule( thing, modData )
{
m_consecutiveShots = 0;
m_victimID = INVALID_ID;
m_frameToStartCooldown = 0;
m_frameToForceReload = 0;
m_frameToStopLoopingSound = 0;
m_audioHandle = AHSV_NoSound;
setWakeFrame(getObject(), UPDATE_SLEEP_FOREVER);
}
//-------------------------------------------------------------------------------------------------
FiringTracker::~FiringTracker()
{
// no need to protect this.
TheAudio->removeAudioEvent( m_audioHandle );
m_audioHandle = AHSV_NoSound;
}
//-------------------------------------------------------------------------------------------------
Int FiringTracker::getNumConsecutiveShotsAtVictim( const Object *victim ) const
{
if( victim == NULL )
return 0;// safety, this function is for asking about shots at a victim
if( victim->getID() != m_victimID )
return 0;// nope, not shooting at him
return m_consecutiveShots;// this is how any times I have shot at this hoser right now
}
//-------------------------------------------------------------------------------------------------
void FiringTracker::shotFired(const Weapon* weaponFired, ObjectID victimID)
{
UnsignedInt now = TheGameLogic->getFrame();
if( victimID == m_victimID )
{
// Shooting at the same guy
++m_consecutiveShots;
}
else if( now < m_frameToStartCooldown )
{
// Switching targets within the coast time is valid, and we will not spin down
++m_consecutiveShots;
m_victimID = victimID;
}
else
{
// Start the count over for the new guy
m_consecutiveShots = 1;
m_victimID = victimID;
}
// Push back the time that we should force a reload with each shot
UnsignedInt autoReloadDelay = weaponFired->getAutoReloadWhenIdleFrames();
if( autoReloadDelay > 0 )
m_frameToForceReload = now + autoReloadDelay;
UnsignedInt coast = weaponFired->getContinuousFireCoastFrames();
if (coast)
m_frameToStartCooldown = weaponFired->getPossibleNextShotFrame() + coast;
else
m_frameToStartCooldown = 0;
Int shotsNeededOne = weaponFired->getContinuousFireOneShotsNeeded();
Int shotsNeededTwo = weaponFired->getContinuousFireTwoShotsNeeded();
if( getObject()->testWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN ) )
{
// Can either go up or down from here.
if( m_consecutiveShots < shotsNeededOne )
coolDown();
else if( m_consecutiveShots > shotsNeededTwo )
speedUp();
}
else if( getObject()->testWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST ) )
{
// Only place I can go here from here is all the way down
if( m_consecutiveShots < shotsNeededTwo )
{
coolDown();
}
}
else
{
// Check to go up
if( m_consecutiveShots > shotsNeededOne )
speedUp();
}
UnsignedInt fireSoundLoopTime = weaponFired->getFireSoundLoopTime();
if (fireSoundLoopTime != 0)
{
// If the sound has stopped playing, then we need to re-add it.
if (m_frameToStopLoopingSound == 0 || !TheAudio->isCurrentlyPlaying(m_audioHandle))
{
AudioEventRTS audio = weaponFired->getFireSound();
audio.setObjectID(getObject()->getID());
m_audioHandle = TheAudio->addAudioEvent( &audio );
}
m_frameToStopLoopingSound = now + fireSoundLoopTime;
}
else
{
AudioEventRTS fireAndForgetSound = weaponFired->getFireSound();
fireAndForgetSound.setObjectID(getObject()->getID());
TheAudio->addAudioEvent(&fireAndForgetSound);
m_frameToStopLoopingSound = 0;
}
setWakeFrame(getObject(), calcTimeToSleep());
}
//-------------------------------------------------------------------------------------------------
UpdateSleepTime FiringTracker::update()
{
//DEBUG_ASSERTCRASH(m_frameToStartCooldown != 0 || m_frameToStopLoopingSound != 0, ("hmm, should be asleep"));
UnsignedInt now = TheGameLogic->getFrame();
// I have been idle long enough that I should reload, so I do not hang around with a near empty clip forever.
if( m_frameToForceReload != 0 && now >= m_frameToForceReload )
{
getObject()->reloadAllAmmo(TRUE);
m_frameToForceReload = 0;
}
// If it has been too long since I fired. I have to start over.
// (don't call if we don't need to cool down... it's expensive!)
if (m_frameToStopLoopingSound != 0)
{
if (now >= m_frameToStopLoopingSound)
{
TheAudio->removeAudioEvent( m_audioHandle );
m_audioHandle = AHSV_NoSound;
m_frameToStopLoopingSound = 0;
}
}
if( m_frameToStartCooldown != 0 && now > m_frameToStartCooldown )
{
m_frameToStartCooldown = now + LOGICFRAMES_PER_SECOND;
coolDown();// if this is the coolest call to cooldown, it will set m_frameToStartCooldown to zero
return UPDATE_SLEEP(LOGICFRAMES_PER_SECOND);
}
UpdateSleepTime sleepTime = calcTimeToSleep();
return sleepTime;
}
//-------------------------------------------------------------------------------------------------
UpdateSleepTime FiringTracker::calcTimeToSleep()
{
// Figure out the longest amount of time we can sleep as unneeded
// If all the timers are off, then we aren't needed at all
if (m_frameToStopLoopingSound == 0 && m_frameToStartCooldown == 0 && m_frameToForceReload == 0)
return UPDATE_SLEEP_FOREVER;
// Otherwise, we need to wake up to service the shortest timer
UnsignedInt now = TheGameLogic->getFrame();
UnsignedInt sleepTime = UPDATE_SLEEP_FOREVER;
if( m_frameToStopLoopingSound != 0 )
{
if( m_frameToStopLoopingSound <= now )
sleepTime = UPDATE_SLEEP_NONE;
else if( (m_frameToStopLoopingSound - now) < sleepTime )
sleepTime = m_frameToStopLoopingSound - now;
}
if( m_frameToStartCooldown != 0 )
{
if( m_frameToStartCooldown <= now )
sleepTime = UPDATE_SLEEP_NONE;
else if( (m_frameToStartCooldown - now) < sleepTime )
sleepTime = m_frameToStartCooldown - now;
}
if( m_frameToForceReload != 0 )
{
if( m_frameToForceReload <= now )
sleepTime = UPDATE_SLEEP_NONE;
else if( (m_frameToForceReload - now) < sleepTime )
sleepTime = m_frameToForceReload - now;
}
return UPDATE_SLEEP(sleepTime);
}
//-------------------------------------------------------------------------------------------------
void FiringTracker::speedUp()
{
ModelConditionFlags clr, set;
Object *self = getObject();
if( self->testWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST ) )
{
//self->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN );
//self->clearModelConditionState( MODELCONDITION_CONTINUOUS_FIRE_MEAN );
//self->clearModelConditionState( MODELCONDITION_CONTINUOUS_FIRE_SLOW );
}
else if(self->testWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN ) )
{
const AudioEventRTS *soundToPlayPtr = self->getTemplate()->getPerUnitSound( "VoiceRapidFire" );
AudioEventRTS soundToPlay = *soundToPlayPtr;
soundToPlay.setObjectID( self->getID() );
TheAudio->addAudioEvent( &soundToPlay );
// These flags are exclusive, not cumulative
self->setWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST );
set.set(MODELCONDITION_CONTINUOUS_FIRE_FAST);
// These flags are exclusive, not cumulative
self->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN );
clr.set(MODELCONDITION_CONTINUOUS_FIRE_MEAN);
clr.set(MODELCONDITION_CONTINUOUS_FIRE_SLOW);
}
else
{
self->setWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN );
set.set(MODELCONDITION_CONTINUOUS_FIRE_MEAN);
// These flags are exclusive, not cumulative
self->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST );
clr.set(MODELCONDITION_CONTINUOUS_FIRE_FAST);
clr.set(MODELCONDITION_CONTINUOUS_FIRE_SLOW);
}
self->clearAndSetModelConditionFlags(clr, set);
}
//-------------------------------------------------------------------------------------------------
void FiringTracker::coolDown()
{
ModelConditionFlags clr, set;
if( getObject()->testWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST )
|| getObject()->testWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN ))
{
// Straight to zero from wherever it is
set.set(MODELCONDITION_CONTINUOUS_FIRE_SLOW);
getObject()->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST );
getObject()->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN );
clr.set(MODELCONDITION_CONTINUOUS_FIRE_FAST);
clr.set(MODELCONDITION_CONTINUOUS_FIRE_MEAN);
}
else // we stop, now
{
getObject()->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_FAST );
getObject()->clearWeaponBonusCondition( WEAPONBONUSCONDITION_CONTINUOUS_FIRE_MEAN );
// Just chillin, nothing to change
clr.set(MODELCONDITION_CONTINUOUS_FIRE_FAST);
clr.set(MODELCONDITION_CONTINUOUS_FIRE_MEAN);
clr.set(MODELCONDITION_CONTINUOUS_FIRE_SLOW);
m_frameToStartCooldown = 0;
}
getObject()->clearAndSetModelConditionFlags(clr, set);
// Start everything over
m_consecutiveShots = 0;
m_victimID = INVALID_ID;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void FiringTracker::crc( Xfer *xfer )
{
// object helper base class
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void FiringTracker::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// object helper base class
UpdateModule::xfer( xfer );
// consecutive shots
xfer->xferInt( &m_consecutiveShots );
// victim id
xfer->xferObjectID( &m_victimID );
// frame to start cooldown
xfer->xferUnsignedInt( &m_frameToStartCooldown );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void FiringTracker::loadPostProcess( void )
{
// object helper back class
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,224 @@
/*
** Command & Conquer Generals(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 GhostObject.cpp ///////////////////////////////////////////////////////////////////////////
// Simple base object
// Author: Michael S. Booth, October 2000
///////////////////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h" // This must go first in EVERY cpp file int the GameEngine
#include "Common/Xfer.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/GhostObject.h"
#include "GameLogic/Object.h"
GhostObjectManager *TheGhostObjectManager=NULL;
//-------------------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------------------
GhostObject::GhostObject(void):
//Added By Sadullah Nader
//Initializations missing and needed
m_parentAngle(0.0f),
m_parentGeometryIsSmall(0.0f),
m_parentGeometryMajorRadius(0.0f),
m_parentGeometryminorRadius(0.0f),
m_parentObject(NULL),
m_partitionData(NULL)
{
m_parentPosition.zero();
// End Initializations
} // end Object
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
GhostObject::~GhostObject()
{
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void GhostObject::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void GhostObject::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// parent object
ObjectID parentObjectID = INVALID_ID;
if( m_parentObject )
parentObjectID = m_parentObject->getID();
xfer->xferObjectID( &parentObjectID );
if( xfer->getXferMode() == XFER_LOAD )
{
// tie up parent object pointer
m_parentObject = TheGameLogic->findObjectByID( parentObjectID );
// sanity
if( parentObjectID != INVALID_ID && m_parentObject == NULL )
{
DEBUG_CRASH(( "GhostObject::xfer - Unable to connect m_parentObject\n" ));
throw INI_INVALID_DATA;
} // end if
} // end if
// parent geometry type
xfer->xferUser( &m_parentGeometryType, sizeof( GeometryType ) );
// parent geometry is small
xfer->xferBool( &m_parentGeometryIsSmall );
// parent geometry major radius
xfer->xferReal( &m_parentGeometryMajorRadius );
// parent geometry minor radius
xfer->xferReal( &m_parentGeometryminorRadius );
// parent angle
xfer->xferReal( &m_parentAngle );
// parent position
xfer->xferCoord3D( &m_parentPosition );
// partition data
///@todo write me ---> !!!!!
// PartitionData *m_partitionData; ///< our PartitionData
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void GhostObject::loadPostProcess( void )
{
} // end loadPostProcess
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
GhostObjectManager::GhostObjectManager(void)
{
m_lockGhostObjects = FALSE;
m_saveLockGhostObjects = FALSE;
m_localPlayer = 0;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
GhostObjectManager::~GhostObjectManager()
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void GhostObjectManager::reset(void)
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
GhostObject *GhostObjectManager::addGhostObject(Object *object, PartitionData *pd)
{
return 0;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void GhostObjectManager::removeGhostObject(GhostObject *mod)
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void GhostObjectManager::updateOrphanedObjects(int *playerIndexList, int numNonLocalPlayers)
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void GhostObjectManager::releasePartitionData(void)
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void GhostObjectManager::restorePartitionData(void)
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void GhostObjectManager::crc( Xfer *xfer )
{
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer Method:
* Version Info:
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void GhostObjectManager::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// local player
xfer->xferInt( &m_localPlayer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void GhostObjectManager::loadPostProcess( void )
{
} // end loadPostProcess

View File

@@ -0,0 +1,186 @@
/*
** Command & Conquer Generals(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: ObjectDefectionHelper.cpp ////////////////////////////////////////////////////////////////
// Author: Colin Day, Steven Johnson - September 2002
// Desc: Object helper module
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/MiscAudio.h"
#include "Common/Xfer.h"
#include "GameClient/Drawable.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/ObjectDefectionHelper.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
ObjectDefectionHelper::~ObjectDefectionHelper( void )
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime ObjectDefectionHelper::update()
{
Object* obj = getObject();
Drawable* draw = obj->getDrawable();
// if we are here, we should be an undetected defector, but
// just in case someone has changed us behind our backs...
if (!obj->getIsUndetectedDefector())
return UPDATE_SLEEP_FOREVER;
UnsignedInt now = TheGameLogic->getFrame();
if (now >= m_defectionDetectionEnd)
{
obj->friend_setUndetectedDefector(FALSE);
// timer has reached zero, so we flash white once!!!! -- lorenzen
if (draw && m_doDefectorFX)
{
RGBColor white = {1,1,1};
if (draw)
draw->flashAsSelected( &white ); //Whew! that's easier, now, isn't it!
AudioEventRTS defectorVulnerableSound = TheAudio->getMiscAudio()->m_defectorTimerDingSound;
defectorVulnerableSound.setObjectID( obj->getID() );
TheAudio->addAudioEvent(&defectorVulnerableSound);
}
return UPDATE_SLEEP_FOREVER; // hey, we're done.
}
// dead or attacking... our cover is blown.
if (obj->isEffectivelyDead() ||
(obj->getStatusBits() & OBJECT_STATUS_IS_FIRING_WEAPON)!= 0)
{
// PLEASE NOTE:
// checking the is_attacking statusbit above, only handles weapon related attacks...
// I also set m_undetectedDefector = FALSE in the groupDoSpecialPower[...]( ) functions;
obj->friend_setUndetectedDefector(FALSE);
return UPDATE_SLEEP_FOREVER; // hey, we're done.
}
if (draw && m_doDefectorFX) // skip fx if merely 'invulnerable'
{
Bool lastPhase = ( ((Int)m_defectionDetectionFlashPhase) & 1 );// were we in a flashy phase last frame?
UnsignedInt timeLeft = m_defectionDetectionEnd - now;
m_defectionDetectionFlashPhase += 0.5f * ( 1.0f - ((Real)timeLeft / DEFECTION_DETECTION_TIME_MAX ) );
Bool thisPhase = ( ((Int)m_defectionDetectionFlashPhase) & 1 );// are we in a flashy phase this frame?
if ( lastPhase && ( ! thisPhase ) )
{
draw->flashAsSelected(); //Whew! that's easier, now, isn't it!
AudioEventRTS defectorTimerSound = TheAudio->getMiscAudio()->m_defectorTimerTickSound;
defectorTimerSound.setObjectID( obj->getID() );
TheAudio->addAudioEvent(&defectorTimerSound);
}
}
return UPDATE_SLEEP_NONE;
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void ObjectDefectionHelper::startDefectionTimer(UnsignedInt numFrames, Bool withDefectorFX)
{
Object* obj = getObject();
if (!obj->getIsUndetectedDefector())
{
setWakeFrame(obj, UPDATE_SLEEP_FOREVER);
return;
}
UnsignedInt now = TheGameLogic->getFrame();
m_defectionDetectionStart = now;
m_defectionDetectionEnd = now + numFrames;
m_defectionDetectionFlashPhase = 0.0f;
m_doDefectorFX = withDefectorFX;
setWakeFrame(obj, UPDATE_SLEEP_NONE);
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void ObjectDefectionHelper::crc( Xfer *xfer )
{
// object helper crc
ObjectHelper::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info;
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void ObjectDefectionHelper::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// object helper base class
ObjectHelper::xfer( xfer );
// detection start
xfer->xferUnsignedInt( &m_defectionDetectionStart );
// detection end
xfer->xferUnsignedInt( &m_defectionDetectionEnd );
// flash phase
xfer->xferReal( &m_defectionDetectionFlashPhase );
// do defector fx
xfer->xferBool( &m_doDefectorFX );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void ObjectDefectionHelper::loadPostProcess( void )
{
// object helper base class
ObjectHelper::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,104 @@
/*
** Command & Conquer Generals(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: ObjectHelper.cpp /////////////////////////////////////////////////////////////////////////
// Author: Colin Day, Steven Johnson, September 2002
// Desc: Object helper module base class
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/Xfer.h"
#include "GameLogic/GameLogic.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/ObjectHelper.h"
#ifdef _INTERNAL
// for occasional debugging...
//#pragma optimize("", off)
//#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
#endif
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
ObjectHelper::~ObjectHelper( void )
{
} // end ~ObjectHelper
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
void ObjectHelper::sleepUntil(UnsignedInt when)
{
if (getObject()->getStatusBits() & OBJECT_STATUS_DESTROYED)
return;
// note the setWakeFrame(NEVER) actually awakens immediately, since NEVER==0.
// when we get NEVER in this case, we really want to sleep forever.
// so just special case it.
UpdateSleepTime wakeDelay = (when == NEVER || when == FOREVER) ?
UPDATE_SLEEP_FOREVER :
UPDATE_SLEEP(when - TheGameLogic->getFrame());
setWakeFrame(getObject(), wakeDelay);
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void ObjectHelper::crc( Xfer *xfer )
{
// update module crc
UpdateModule::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/* Xfer method
* Version Info:
* 1: Initial Version */
// ------------------------------------------------------------------------------------------------
void ObjectHelper::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// update module xfer
UpdateModule::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void ObjectHelper::loadPostProcess( void )
{
// update module post process
UpdateModule::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,93 @@
/*
** Command & Conquer Generals(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: ObjectRepulsorHelper.cpp //////////////////////////////////////////////////////////////////////
// Author: Steven Johnson, December 2002
// Desc: Repulsor helper
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/Xfer.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/ObjectRepulsorHelper.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
ObjectRepulsorHelper::~ObjectRepulsorHelper( void )
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime ObjectRepulsorHelper::update()
{
// if we ever get here, clear this.
getObject()->setStatus(OBJECT_STATUS_REPULSOR, FALSE);
// then go back to sleep until we are forcibly awakened.
return UPDATE_SLEEP_FOREVER;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void ObjectRepulsorHelper::crc( Xfer *xfer )
{
// object helper crc
ObjectHelper::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info;
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void ObjectRepulsorHelper::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// object helper base class
ObjectHelper::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void ObjectRepulsorHelper::loadPostProcess( void )
{
// object helper base class
ObjectHelper::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,93 @@
/*
** Command & Conquer Generals(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: ObjectSMCHelper.cpp //////////////////////////////////////////////////////////////////////
// Author: Colin Day, Steven Johnson, September 2002
// Desc: SMC helper
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/Xfer.h"
#include "GameLogic/Object.h"
#include "GameLogic/Module/ObjectSMCHelper.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
ObjectSMCHelper::~ObjectSMCHelper( void )
{
}
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
UpdateSleepTime ObjectSMCHelper::update()
{
// if we ever get here, stop this.
getObject()->clearSpecialModelConditionStates();
// then go back to sleep until we are forcibly awakened.
return UPDATE_SLEEP_FOREVER;
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void ObjectSMCHelper::crc( Xfer *xfer )
{
// object helper crc
ObjectHelper::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info;
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void ObjectSMCHelper::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// object helper base class
ObjectHelper::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void ObjectSMCHelper::loadPostProcess( void )
{
// object helper base class
ObjectHelper::loadPostProcess();
} // end loadPostProcess

View File

@@ -0,0 +1,80 @@
/*
** Command & Conquer Generals(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: ObjectSMCHelper.cpp //////////////////////////////////////////////////////////////////////
// Author: Colin Day, Steven Johnson, September 2002
// Desc: Weapon status helper
///////////////////////////////////////////////////////////////////////////////////////////////////
// INCLUDES ///////////////////////////////////////////////////////////////////////////////////////
#include "PreRTS.h"
#include "Common/Xfer.h"
#include "GameLogic/Module/ObjectWeaponStatusHelper.h"
// ------------------------------------------------------------------------------------------------
// ------------------------------------------------------------------------------------------------
ObjectWeaponStatusHelper::~ObjectWeaponStatusHelper( void )
{
}
// ------------------------------------------------------------------------------------------------
/** CRC */
// ------------------------------------------------------------------------------------------------
void ObjectWeaponStatusHelper::crc( Xfer *xfer )
{
// object helper crc
ObjectHelper::crc( xfer );
} // end crc
// ------------------------------------------------------------------------------------------------
/** Xfer method
* Version Info;
* 1: Initial version */
// ------------------------------------------------------------------------------------------------
void ObjectWeaponStatusHelper::xfer( Xfer *xfer )
{
// version
XferVersion currentVersion = 1;
XferVersion version = currentVersion;
xfer->xferVersion( &version, currentVersion );
// object helper base class
ObjectHelper::xfer( xfer );
} // end xfer
// ------------------------------------------------------------------------------------------------
/** Load post process */
// ------------------------------------------------------------------------------------------------
void ObjectWeaponStatusHelper::loadPostProcess( void )
{
// object helper base class
ObjectHelper::loadPostProcess();
} // end loadPostProcess

File diff suppressed because it is too large Load Diff

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