Files
Cyd 2b8ca921cb Initial full mirror of c:\VWE (source + assets + toolchain + outputs) via Git LFS
Complete disaster-recovery snapshot: engine/game source, game data assets,
VC6 toolchain + DX SDKs, build outputs, deployed game, and _UNUSED archive.
Large binaries in Git LFS; text preserved byte-for-byte (core.autocrlf=false,
no eol attributes). See RECOVERY.md for the one-clone rebuild procedure.
2026-06-24 21:28:16 -05:00

670 lines
18 KiB
C++

//===========================================================================//
// File: Tank.cpp
//---------------------------------------------------------------------------//
// Date Who Modification //
// -------- --- ---------------------------------------------------------- //
// 09/09/98 JSE Inital base class based off of Shadowrun/Adept //
// 09/23/98 BDB State engine update //
//---------------------------------------------------------------------------//
// Copyright (C) 1998, Fasa Interactive //
// All Rights reserved worldwide //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL //
//===========================================================================//
#include "MW4Headers.hpp"
#include "Tank.hpp"
#include "gameinfo.hpp"
#include "AI.hpp"
#include "AI_UserConstants.hpp"
#include "Mech.hpp"
#include "MWDamageObject.hpp"
#include <adept\effect.hpp>
#include "obstacle.hpp"
// MSL 5.04 Tank Scorable
#include "MWMission.hpp"
#include <Adept\EntityManager.hpp>
#include <Adept\CollisionVolume.hpp>
#include <Adept\DamageObject.hpp>
//#############################################################################
//############################### Tank ##############################
//#############################################################################
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Tank::ClassData*
Tank::DefaultData = NULL;
DWORD MechWarrior4::Executed_Tank_Count = 0;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Tank::InitializeClass()
{
Check_Object(MWMover::ExecutionStateEngine::DefaultData);
Verify(!DefaultData);
DefaultData =
new ClassData(
TankClassID,
"MechWarrior4::Tank",
BaseClass::DefaultData,
0, NULL,
(Entity::Factory)Make,
(Entity::CreateMessage::Factory)CreateMessage::ConstructCreateMessage,
ExecutionStateEngine::DefaultData,
(Entity::GameModel::Factory)GameModel::ConstructGameModel,
(Entity::GameModel::Factory)GameModel::ConstructOBBStream,
(Entity::GameModel::ReadAndVerifier)GameModel::ReadAndVerify,
(Entity::GameModel::ModelWrite)GameModel::WriteToText,
(Entity::GameModel::ModelSave)GameModel::SaveGameModel,
AnimationStateEngine::Make
);
Register_Object(DefaultData);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Tank::TerminateClass()
{
Unregister_Object(DefaultData);
delete DefaultData;
DefaultData = NULL;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Tank*
Tank::Make(
CreateMessage *message,
ReplicatorID *base_id
)
{
Check_Object(message);
gos_PushCurrentHeap(Heap);
Tank *new_entity = new
Tank(DefaultData, message, base_id, NULL);
gos_PopCurrentHeap();
Check_Object(new_entity);
Check_Object(EntityManager::GetInstance());
EntityManager::GetInstance()->AddMover(new_entity);
return new_entity;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Tank::Tank(
ClassData *class_data,
CreateMessage *message,
ReplicatorID *base_id,
ElementRenderer::Element *element
):
Vehicle(class_data, message, base_id, element)
{
Check_Pointer(this);
Check_Object(message);
HookUpSubsystems();
CommonCreation(message);
// MSL 5.04 Tank Scorable
//should this tank be scored?
m_scoreThisTank = false;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Tank::~Tank()
{
Check_Object(EntityManager::GetInstance());
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Tank::Reuse(
const CreateMessage *message,
ReplicatorID *base_id
)
{
Check_Object(this);
Check_Object(message);
STOP(("Not implemented"));
BaseClass::Reuse(message, base_id);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Tank::CommonCreation(CreateMessage *message)
{
Check_Object(this);
Check_Object(message);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Tank::Respawn(Entity::CreateMessage *message)
{
Check_Object(this);
Check_Object(message);
BaseClass::Respawn(message);
CreateMessage *vehicle_message = Cast_Pointer(CreateMessage *, message);
CommonCreation(vehicle_message);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Tank::PreCollisionExecute(Time till)
{
Check_Object(this);
PRECOLLISION_LOGIC("Tank");
#if defined(LAB_ONLY)
if (!instanceName)
MWGameInfo::g_LastVehicle[0] = '\0';
else
{
strncpy(MWGameInfo::g_LastVehicle, instanceName, sizeof(MWGameInfo::g_LastVehicle)-1);
MWGameInfo::g_LastVehicle[sizeof(MWGameInfo::g_LastVehicle)-1] = '\0';
}
MWGameInfo::g_LastVehiclePos = GetLocalToWorld ();
#endif
//
//-------------------------------------------------------------
// Let the base class deal with setting up the motion variables
//-------------------------------------------------------------
//
BaseClass::PreCollisionExecute(till);
Set_Statistic(Executed_Tank_Count, Executed_Tank_Count+1);
//
//----------------------------------------------------------------------
// Any executing vehicle should not be in never execute state and should
// always use post collision
//----------------------------------------------------------------------
//
Check_Object(executionState);
int pre_state = executionState->GetState();
Verify(pre_state != ExecutionStateEngine::NeverExecuteState);
//
//-------------------------------------------------------------------------
// Do the appropriate type of simulation. Animated motion state is for BRB
//-------------------------------------------------------------------------
//
Scalar time_slice = GetTimeSlice(till);
switch (pre_state)
{
//
//----------------------------------------------------
// Always execute state is for the normal driving code
//----------------------------------------------------
//
case ExecutionStateEngine::DyingState:
ComputeFrictionSpeed (time_slice);
UpdateVehiclePosition (time_slice);
break;
case ExecutionStateEngine::DrivingMotionState:
ComputeForwardSpeed(time_slice);
UpdateVehiclePosition(time_slice);
break;
//
//----------------------------------------------------
// Always execute state is for the normal driving code
//----------------------------------------------------
//
case ExecutionStateEngine::FlyingMotionState:
ComputeForwardSpeed(time_slice);
UpdateVehiclePosition(time_slice);
break;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void Tank::PostCollisionExecute (Stuff::Time time)
{
Check_Object(this);
POSTCOLLISION_LOGIC("Tank");
lastParameterization = time;
initialLocalToParent = GetLocalToParent();
initialWorldSpaceVelocity = worldSpaceVelocity,
initialWorldSpaceAcceleration = worldSpaceAcceleration;
int pre_state = executionState->GetState();
Verify(pre_state != ExecutionStateEngine::NeverExecuteState);
switch (pre_state)
{
case ExecutionStateEngine::DyingState:
// check for unit stopped falling and moving
if ((localSpaceVelocity.linearMotion.z == 0.0f) && ((localSpaceVelocity.linearMotion.y == 0.0f)))
{
Verify (m_DeathDamageMode != -1);
Verify (m_DeathDamageType != -1);
const GameModel *model = GetGameModel();
Check_Object(model);
DealSplashDamage();
SetDead ();
Entity::ReactToDestruction(m_DeathDamageMode,m_DeathDamageType);
// CreateEffect(model->secondaryDestroyedEffectResource, this);
if(!m_deathEntity.GetCurrent())
{
Entity *death_entity = CreateStaticHermitEntity(model->deathEntityResource);
if(death_entity)
{
m_deathEntity.Remove();
m_deathEntity.Add(death_entity);
}
}
executionState->RequestState(ExecutionStateEngine::NeverExecuteState);
entityElement->SetAlwaysCullMode();
RemoveCollision();
lastParameterization = GetTimeSlice (time);
initialLocalToParent = GetLocalToParent();
SyncMatrices(true);
}
break;
}
BaseClass::PostCollisionExecute (time);
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool
Tank::CollisionHandler(
Stuff::LinearMatrix4D *new_position,
Stuff::DynamicArrayOf<CollisionData> *collisions
)
{
Check_Object(this);
Check_Object(collisions);
Verify(GetInterestLevel() != DormantInterestLevel);
bool shoulddamage=true;
//
//----------------------------------------------------------
// If we are destroyed, delete the collision list and return
//----------------------------------------------------------
//
if (IsDestroyed())
{
Verify(!newCollisions);
delete collisions;
return false;
}
//
//--------------------------------
// Ask the AI about the collision
//--------------------------------
//
if (m_AI)
{
if (!m_AI->ReactToCollision(collisions,shoulddamage)) // ai will return false if collision should not occur
{
Verify(!newCollisions);
delete collisions;
return false;
}
}
//
//----------------------------------------
// Turn off collision if we are going fast
//----------------------------------------
//
#ifdef LAB_ONLY
if (m_DebugFast)
{
Verify(!newCollisions);
delete collisions;
return false;
}
#endif
//
//-----------------------
// Iterate the collisions
//-----------------------
//
bool just_bump = true;
for (int i=0; i<collisions->GetLength(); ++i)
{
CollisionData *data = &(*collisions)[i];
Check_Pointer(data);
//
//---------------------------------------------------------------------
// Look to see if we have run into a collider, and if so, calculate the
// relative velocity
//---------------------------------------------------------------------
//
Entity *other = data->m_otherEntity;
if (other->IsACollider())
{
just_bump = false;
Adept::Mover *adeptmover = Cast_Object(Adept::Mover*, other);
Vector3D rel_vel;
rel_vel.Subtract(adeptmover->worldSpaceVelocity.linearMotion, worldSpaceVelocity.linearMotion);
Scalar speed = rel_vel.GetLengthSquared();
MWObject *mover = Cast_Object(MWObject*, other);
//
//-------------------------------
// If its a mech, get its ratings
//-------------------------------
//
if (other->GetClassID() == MechClassID)
{
Mech *mech = Cast_Object(Mech*, mover);
int mech_class;
Scalar damage_received;
Scalar mech_speed;
if (mech->GetTonage() >= MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_mech_heavy_tonage))
{
damage_received = MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_mech_heavy_damage);
mech_speed = MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_mech_heavy_building_speed);
mech_class = HeavyCanWalkThruType;
}
else if (mech->GetTonage() >= MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_mech_medium_tonage))
{
damage_received = MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_mech_medium_damage);
mech_speed = MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_mech_medium_building_speed);
mech_class = MediumCanWalkThruType;
}
else
{
damage_received = MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_mech_light_damage);
mech_speed = MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_mech_light_building_speed);
mech_class = AnythingCanWalkThruType;
}
//
//----------------------
// See if it squishes us
//----------------------
//
if (GetWalkThruType() >= mech_class)
{
if (shoulddamage)
{
TakeDamageMessage damage_message(
GetReplicatorID(),
other->GetReplicatorID(),
100000.0f,
// MSL 5.05 Advance Mode
0,
RammingDamageFromAboveType,
data->m_normal,
data->m_worldIntersectionPoint,
0.0f,
false,
TakeDamageMessage::DefaultWeaponType
);
Receive(&damage_message);
}
}
//
//-----------------------------------------------
// See if we are going fast enough to get damaged
//-----------------------------------------------
//
else if (speed >= mech_speed)
{
speed = Sqrt(speed)*mover->GetTonage();
//
//------------------------
// Evaluate the DFA states
//------------------------
//
Scalar min_dfa_vel = MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_dfa_speed);
Entity::ExecutionStateEngine *engine = mech->executionState;
bool their_dfa = mech->worldSpaceVelocity.linearMotion.y <= -min_dfa_vel && engine->GetState() == ExecutionStateEngine::FlyingMotionState;
//
//---------------------------------------
// Use the appropriate collision modifier
//---------------------------------------
//
char damage_type;
if (their_dfa)
{
damage_type = RammingDamageFromAboveType;
speed *= MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_dfa_damage_given);
}
else
{
damage_type = RammingDamageFromAheadType;
speed *= damage_received;
}
//
//----------------------------------
// Set the damage message to ourself
//----------------------------------
//
// MSL 5.04 Tank Collision Damage
// MSL 5.06 Armor Mode
MWApplication *m_App;
m_App = MWApplication::GetInstance ();
m_ArmorMode = m_App->GetLocalNetParams()->m_armormodeOn;
if (shoulddamage)
{
if (m_ArmorMode)
{
TakeDamageMessage damage_message(
GetReplicatorID(),
other->GetReplicatorID(),
0.1f,
m_ArmorMode,
damage_type,
data->m_normal,
data->m_worldIntersectionPoint,
0.0f,
false,
TakeDamageMessage::DefaultWeaponType
);
Receive(&damage_message);
}
else
{
TakeDamageMessage damage_message(
GetReplicatorID(),
other->GetReplicatorID(),
speed,
m_ArmorMode,
damage_type,
data->m_normal,
data->m_worldIntersectionPoint,
0.0f,
false,
TakeDamageMessage::DefaultWeaponType
);
Receive(&damage_message);
}
}
}
}
}
//
//--------------------------------------------------------------
// If we've run into a static object, see if we can just kill it
//--------------------------------------------------------------
//
else
{
if (GetWalkThruType() <= other->GetWalkThruType())
{
if (shoulddamage)
{
TakeDamageMessage damage_message(
other->GetReplicatorID(),
GetReplicatorID(),
100000.0f,
// MSL 5.05 Advance Mode
0,
RammingDamageFromAboveType,
data->m_otherNormal,
data->m_worldIntersectionPoint,
0.0f,
false,
TakeDamageMessage::DefaultWeaponType
);
other->Receive(&damage_message);
}
}
//
//------------------------------
// Otherwise, just stop the tank
//------------------------------
//
else
just_bump = false;
}
}
//
//-------------------------------
// Slow down the tank as required
//-------------------------------
//
Scalar slowdown = MW4AI::UserConstants::m_Instance->Get(MW4AI::UserConstants::collision_tank_slowdown);
bool result = false;
if (just_bump)
{
if (currentSpeedMPS > 0.0f)
{
currentSpeedMPS -= slowdown;
Min_Clamp(currentSpeedMPS, 0.0f);
}
if (currentSpeedMPS < 0.0f)
{
currentSpeedMPS += slowdown;
Max_Clamp(currentSpeedMPS, 0.0f);
}
}
//
//-------------------------
// Otherwise, stop the tank
//-------------------------
//
else
{
result = true;
currentSpeedMPS = 0.0f;
*new_position = GetLocalToParent();
}
currentSpeedKPH = currentSpeedMPS * 3.6f;
//
//--------------------------------------------------------------------
// If the tank isn't executing, it can't move or retain its collisions
//--------------------------------------------------------------------
//
Entity::ExecutionStateEngine *engine = executionState;
if (engine->GetState() == ExecutionStateEngine::NeverExecuteState || !IsUsingPostCollision())
{
Verify(!newCollisions);
delete collisions;
return false;
}
newCollisions = collisions;
return result;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
Tank::TestInstance() const
{
Verify(IsDerivedFrom(DefaultData));
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void Tank::ReactToDestruction(int damage_mode, int damage_type)
{
if (damage_mode != InternalDamageObject::DestructionDamageMode)
{
SPEW(("jerryeds", "DYING WITHOUT DEATH!!!"));
SPEW(("daberger", "DYING WITHOUT DEATH!!!"));
damage_mode = InternalDamageObject::DestructionDamageMode;
}
if ((localSpaceVelocity.linearMotion.z == 0.0f) && ((localSpaceVelocity.linearMotion.y == 0.0f)))
{
BaseClass::ReactToDestruction (damage_mode,damage_type);
return;
}
switch(damage_mode)
{
case InternalDamageObject::DestructionDamageMode:
{
// MSL 5.04 Tank Scorable
if (m_scoreThisTank)
{
Check_Object (Mission::GetInstance());
Mission::GetInstance()->ScoringReactToBuildingDeath (m_whoShotMeLast, GetReplicatorID());
}
if(m_AI)
{
Check_Object(m_AI);
m_AI->Die();
}
else
{
MW4AI::g_Rect4DHash->RemovePermRect (this);
}
if((!IsDestroyed()) && (!m_SecondaryExplosion.GetCurrent ()))
{
m_DeathDamageMode = damage_mode;
m_DeathDamageType = damage_type;
const GameModel *model = GetGameModel();
SetDying ();
Check_Object(model);
Effect *eff;
eff = CreateEffect(model->secondaryDestroyedEffectResource, this,true);
if (eff != NULL)
{
Check_Object (eff);
m_SecondaryExplosion.Add (eff);
executionState->RequestState(ExecutionStateEngine::DyingState);
}
}
}
}
}