mirror of
https://github.com/electronicarts/CnC_Generals_Zero_Hour.git
synced 2025-12-16 23:51:41 -05:00
Initial commit of Command & Conquer Generals and Command & Conquer Generals Zero Hour source code.
This commit is contained in:
1038
Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp
Normal file
1038
Generals/Code/GameEngine/Source/GameLogic/AI/AI.cpp
Normal file
File diff suppressed because it is too large
Load Diff
819
Generals/Code/GameEngine/Source/GameLogic/AI/AIDock.cpp
Normal file
819
Generals/Code/GameEngine/Source/GameLogic/AI/AIDock.cpp
Normal 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 );
|
||||
}
|
||||
|
||||
|
||||
3131
Generals/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp
Normal file
3131
Generals/Code/GameEngine/Source/GameLogic/AI/AIGroup.cpp
Normal file
File diff suppressed because it is too large
Load Diff
833
Generals/Code/GameEngine/Source/GameLogic/AI/AIGuard.cpp
Normal file
833
Generals/Code/GameEngine/Source/GameLogic/AI/AIGuard.cpp
Normal 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();
|
||||
}
|
||||
|
||||
10423
Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp
Normal file
10423
Generals/Code/GameEngine/Source/GameLogic/AI/AIPathfind.cpp
Normal file
File diff suppressed because it is too large
Load Diff
3487
Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp
Normal file
3487
Generals/Code/GameEngine/Source/GameLogic/AI/AIPlayer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
1229
Generals/Code/GameEngine/Source/GameLogic/AI/AISkirmishPlayer.cpp
Normal file
1229
Generals/Code/GameEngine/Source/GameLogic/AI/AISkirmishPlayer.cpp
Normal file
File diff suppressed because it is too large
Load Diff
6931
Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp
Normal file
6931
Generals/Code/GameEngine/Source/GameLogic/AI/AIStates.cpp
Normal file
File diff suppressed because it is too large
Load Diff
866
Generals/Code/GameEngine/Source/GameLogic/AI/AITNGuard.cpp
Normal file
866
Generals/Code/GameEngine/Source/GameLogic/AI/AITNGuard.cpp
Normal 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();
|
||||
}
|
||||
|
||||
271
Generals/Code/GameEngine/Source/GameLogic/AI/Squad.cpp
Normal file
271
Generals/Code/GameEngine/Source/GameLogic/AI/Squad.cpp
Normal 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
|
||||
1441
Generals/Code/GameEngine/Source/GameLogic/AI/TurretAI.cpp
Normal file
1441
Generals/Code/GameEngine/Source/GameLogic/AI/TurretAI.cpp
Normal file
File diff suppressed because it is too large
Load Diff
543
Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp
Normal file
543
Generals/Code/GameEngine/Source/GameLogic/Map/PolygonTrigger.cpp
Normal 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
|
||||
1158
Generals/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp
Normal file
1158
Generals/Code/GameEngine/Source/GameLogic/Map/SidesList.cpp
Normal file
File diff suppressed because it is too large
Load Diff
2970
Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp
Normal file
2970
Generals/Code/GameEngine/Source/GameLogic/Map/TerrainLogic.cpp
Normal file
File diff suppressed because it is too large
Load Diff
152
Generals/Code/GameEngine/Source/GameLogic/Object/Armor.cpp
Normal file
152
Generals/Code/GameEngine/Source/GameLogic/Object/Armor.cpp
Normal 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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
|
||||
1312
Generals/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp
Normal file
1312
Generals/Code/GameEngine/Source/GameLogic/Object/Body/ActiveBody.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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( ¢erPoint, &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( ¢erPoint, &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
|
||||
@@ -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
|
||||
@@ -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
|
||||
149
Generals/Code/GameEngine/Source/GameLogic/Object/Die/DamDie.cpp
Normal file
149
Generals/Code/GameEngine/Source/GameLogic/Object/Die/DamDie.cpp
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
224
Generals/Code/GameEngine/Source/GameLogic/Object/GhostObject.cpp
Normal file
224
Generals/Code/GameEngine/Source/GameLogic/Object/GhostObject.cpp
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
2768
Generals/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp
Normal file
2768
Generals/Code/GameEngine/Source/GameLogic/Object/Locomotor.cpp
Normal file
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
Reference in New Issue
Block a user