/*
** Copyright (C) 1996, 1997 Microsoft Corporation. All Rights Reserved.
**
** File: clusterIGC.cpp
**
** Author:
**
** Description:
** Implementation of the CclusterIGC class. This file was initially created by
** the ATL wizard for the core object.
**
** Clusters are distinct places in the trek universe: nothing happening in one
** cluster can directly affect things in another cluster. Clusters (in addition to
** the ships, missiles, etc.) have a collection of static objects that represent things
** like planets.
**
** History:
*/
// clusterIGC.cpp : Implementation of CclusterIGC
#include "pch.h"
#include "clusterIGC.h"
#include "modelIGC.h"
#include <stdlib.h>
#include <math.h>
/////////////////////////////////////////////////////////////////////////////
// CclusterIGC
HRESULT CclusterIGC::Initialize(ImissionIGC* pMission, Time now, const void* data, int dataSize)
{
assert (pMission);
m_pMission = pMission;
ZRetailAssert (data && (dataSize == sizeof(m_data)));
m_data = *((DataClusterIGC*)data);
m_nPass = m_data.clusterID;
m_lastUpdate = now;
pMission->AddCluster(this);
m_pClusterSite = pMission->GetIgcSite()->CreateClusterSite(this);
if (m_data.posterName[0] != '\0')
m_pClusterSite->SetEnvironmentGeo(m_data.posterName);
if (m_data.planetName[0] != '\0')
{
Vector position;
double sinLatitude = double(m_data.planetSinLatitude - 0.5);
double cosLatitude = sqrt(1.0 - sinLatitude * sinLatitude);
double longitude = double(m_data.planetLongitude * (2.0f * pi));
position.x = float(cos(longitude) * cosLatitude);
position.y = float(sin(longitude) * cosLatitude);
position.z = float(sinLatitude);
m_pClusterSite->AddPoster(m_data.planetName,
position,
float(m_data.planetRadius));
}
return S_OK;
}
void CclusterIGC::Terminate(void)
{
{
//Models remove themselves from the list when terminated
ModelLinkIGC* l;
while (l = m_models.first()) //Not ==
{
l->data()->Terminate();
}
}
m_kdrStatic.flush();
m_kdrMoving.flush();
assert (m_modelsPickable.n() == 0);
assert (m_modelsCastRay.n() == 0);
assert (m_stations.n() == 0);
assert (m_models.n() == 0);
assert (m_probes.n() == 0);
assert (m_warps.n() == 0);
assert (m_treasures.n() == 0);
assert (m_asteroids.n() == 0);
assert (m_mines.n() == 0);
assert (m_pClusterSite);
m_pClusterSite->Terminate();
m_pClusterSite = NULL;
m_pMission->DeleteCluster(this);
}
void CclusterIGC::Update(Time now)
{
if (now > m_lastUpdate)
{
float dt = now - m_lastUpdate;
bool bStarted = m_pMission->GetMissionStage() == STAGE_STARTED;
if (bStarted)
{
{
//Have any stations launch docked drones
for (StationLinkIGC* l = m_stations.first();
(l != NULL);
l = l->next())
{
IstationIGC* pstation = l->data();
ShipLinkIGC* pslNext;
for (ShipLinkIGC* psl = pstation->GetShips()->first();
(psl != NULL);
psl = pslNext)
{
IshipIGC* pship = psl->data();
pslNext = psl->next(); //Get the next link now since the ship may launch
// const MissionParams* pmp = m_pMission->GetMissionParams(); // mmf 10/07 added so we can get at bExperimental game type
if (pship->GetAutopilot() && (pship->GetPilotType() < c_ptPlayer))
{
//Docked non-players on autopilot never are observers/parents
assert (pship->GetParentShip() == NULL);
assert (pship->GetChildShips()->n() == 0);
// mmf/yp 10/07 added this so drones launch when ordered to even if OkToLaunch might be false
// intentionally left c_cidMine out of the list otherwise miners would launch with their AI
// 'order' to mine
if (pship->OkToLaunch(now) || (pship->GetCommandID(c_cmdAccepted) == c_cidGoto) ||
(pship->GetCommandID(c_cmdAccepted) == c_cidBuild) )
pship->SetStation(NULL);
// if (pship->OkToLaunch(now)) // mmf orig code
// pship->SetStation(NULL);
}
}
}
}
{
m_fCost = m_pMission->GetFloatConstant(c_fcidBaseClusterCost);
float costLifepod = m_pMission->GetFloatConstant(c_fcidLifepodCost);
float costTurret = m_pMission->GetFloatConstant(c_fcidTurretCost);
float costPlayer = m_pMission->GetFloatConstant(c_fcidPlayerCost);
float costDrone = m_pMission->GetFloatConstant(c_fcidDroneCost);
//Have miners and builders do any pre-plotted moves. Allow ships to suicide.
ShipLinkIGC* lNext;
for (ShipLinkIGC* l = m_ships.first();
(l != NULL);
l = lNext)
{
IshipIGC* s = l->data();
lNext = l->next();
if (s->GetPilotType() < c_ptPlayer)
m_fCost += costDrone;
else if (s->GetParentShip() != NULL)
m_fCost += costTurret;
else
{
IhullTypeIGC* pht = s->GetBaseHullType();
assert (pht);
m_fCost += pht->HasCapability(c_habmLifepod)
? costLifepod
: costPlayer;
}
s->PreplotShipMove(now);
}
if (m_fCost > 0.0f)
{
m_fCost *= dt / m_pMission->GetFloatConstant(c_fcidClusterDivisor);
}
{
//Have all ships on autopilot plot their moves. Allow ships to suicide.
ShipLinkIGC* lNext;
for (ShipLinkIGC* l = m_ships.first();
(l != NULL);
l = lNext)
{
IshipIGC* s = l->data();
lNext = l->next();
s->PlotShipMove(now);
}
}
}
{
//Have all ships execute their moves
for (ShipLinkIGC* l = m_ships.first();
(l != NULL);
l = l->next())
{
IshipIGC* s = l->data();
if (s->GetParentShip() == NULL)
{
s->ExecuteShipMove(now);
}
}
}
}
else
m_fCost = 0.0f;
{
//Call the update method on all the contained models
//models might self-terminate in the update and nuke earlier models in the update loop
//NYI debugging variables
//ObjectType oldObjectType = NA;
//ObjectType newObjectType = NA;
ModelLinkIGC* lNext;
for (ModelLinkIGC* l = m_models.first();
(l != NULL);
l = lNext)
{
//oldObjectType = newObjectType;
//newObjectType = l->data()->GetObjectType();
lNext = l->next();
l->data()->Update(now);
}
}
if (m_data.activeF && bStarted)
{
{
//Update the bounding boxes for all moving objects & projectiles
for (ModelLinkIGC* l = m_models.first();
(l != NULL);
l = l->next())
{
l->data()->SetBB(m_lastUpdate, now, dt);
}
m_tMax = dt;
}
m_kdrStatic.update();
m_kdrMoving.update();
{
//Cast rays through the KD tree for each object
for (ModelLinkIGC* l = m_modelsCastRay.first();
(l != NULL);
l = l->next())
{
ImodelIGC* m = l->data();
HitTest* ht = m->GetHitTest();
if (!ht->GetDeadF())
{
m_kdrStatic.test(ht, &m_collisions);
m_kdrMoving.test(ht, &m_collisions);
}
}
}
//Sort the collisions by the time they occur
m_collisions.sort(0);
//Process each collision (in order)
{
m_tOffset = 0.0f;
for (m_collisionID = 0; (m_collisionID < m_collisions.n()); m_collisionID++)
{
const CollisionEntry& entry = m_collisions[m_collisionID];
if (!(entry.m_pHitTest1->GetDeadF() || entry.m_pHitTest2->GetDeadF()))
{
Time timeCollision = m_lastUpdate + (m_tOffset + entry.m_tCollision);
ImodelIGC* pModelHitTest1 = (ImodelIGC*)(entry.m_pHitTest1->GetData());
assert (pModelHitTest1);
ImodelIGC* pModelHitTest2 = (ImodelIGC*)(entry.m_pHitTest2->GetData());
assert (pModelHitTest2);
//Give each participant in the collision a chance to handle the collision
//but give the "1st" model first dibs.
if ((pModelHitTest1->GetCluster() == this) &&
(pModelHitTest2->GetCluster() == this))
{
pModelHitTest1->HandleCollision(timeCollision, entry.m_tCollision, entry, pModelHitTest2);
}
}
}
m_collisions.purge();
}
{
//Apply any damage from mines
//Kids always follow parents in the ship list, so go from back to front
//so that the killing a parent doesn't mean hitting dead elements in the list
ShipLinkIGC* lTxen;
for (ShipLinkIGC* l = m_ships.last();
(l != NULL);
l = lTxen)
{
IshipIGC* s = l->data();
lTxen = l->txen();
s->ApplyMineDamage();
}
}
//Move each object & projectile
{
for (ModelLinkIGC* l = m_models.first();
(l != NULL);
l = l->next())
{
l->data()->Move();
}
}
if ((m_nPass++) % c_nPassesPerUpdate == 0)
{
for (ModelLinkIGC* l = m_models.first();
(l != NULL);
l = l->next())
{
l->data()->UpdateSeenBySide();
}
m_pMission->GetIgcSite()->ClusterUpdateEvent(this);
}
}
//Draw and resolve any explosions
if (m_nExplosions != 0)
{
const int c_maxDmgs = 500;
IdamageIGC* pdmgs[c_maxDmgs];
int nDmgs = 0;
//Copy the list of models in the sector that can be damaged into
for (ModelLinkIGC* l = m_modelsPickable.first();
(l != NULL);
l = l->next())
{
ImodelIGC* pmodel = l->data();
ObjectType type = pmodel->GetObjectType();
//Not everything that can take damage can be affected by an explosion.
if ((type == OT_ship) || (type == OT_asteroid) ||
(type == OT_station) || (type == OT_missile) || (type == OT_probe))
{
pmodel->AddRef();
pdmgs[nDmgs++] = (IdamageIGC*)pmodel;
if (nDmgs == c_maxDmgs)
break;
}
}
ImineIGC* pmines[c_maxDmgs];
int nMines = 0;
{
for (MineLinkIGC* l = m_mines.first(); (l != NULL); l = l->next())
{
ImineIGC* pm = l->data();
pm->AddRef();
pmines[nMines++] = pm;
if (nMines == c_maxDmgs)
break;
}
}
int i = 0;
do
{
ExplosionData& e = m_explosions[i];
m_pClusterSite->AddExplosion(e.position, e.radius, e.explosionType);
float dt = (e.time - m_lastUpdate) - m_tOffset;
//Now, the painful part: applying damage to everything in the sector that could be hit
{
for (int j = 0; (j < nDmgs); j++)
{
IdamageIGC* pTarget = pdmgs[j];
if (pTarget->GetCluster() == this) //Make sure it wasn't already destroyed
{
//The target is still around
Vector p = pTarget->GetPosition() + dt * pTarget->GetVelocity();
float d = (e.position - p).Length() - pTarget->GetRadius();
if (d < e.radius)
{
float amount = e.amount;
if (d > 0.0f)
{
float f = 1.0f - (d / e.radius);
amount *= f * f;
}
pTarget->ReceiveDamage(e.damageType | c_dmgidNoWarn | c_dmgidNoDebris,
amount,
e.time,
p, e.position,
e.launcher);
}
}
}
}
{
for (int j = 0; (j < nMines); j++)
{
ImineIGC* pTarget = pmines[j];
if (pTarget->GetCluster() == this) //Make sure it wasn't already destroyed
{
//The target is still around
const Vector& p = pTarget->GetPosition();
float d = (e.position - p).Length() - pTarget->GetRadius();
if (d < e.radius)
{
float amount = e.amount;
if (d > 0.0f)
{
float f = 1.0f - (d / e.radius);
amount *= f * f;
}
pTarget->ReduceStrength(amount);
}
}
}
}
if (e.launcher)
e.launcher->Release();
}
while (++i < m_nExplosions);
//Release all the cached pointers
{
while (--nDmgs >= 0)
pdmgs[nDmgs]->Release();
}
{
while (--nMines >= 0)
pmines[nMines]->Release();
}
m_nExplosions = 0;
}
m_lastUpdate = now;
}
}
int CclusterIGC::Export(void* data) const
{
if (data)
{
*((DataClusterIGC*)data) = m_data;
//Selectively lie here: exported clusters are always inactive by default.
((DataClusterIGC*)data)->activeF = false;
}
return sizeof(m_data);
}
void CclusterIGC::AddModel(ImodelIGC* modelNew)
{
assert (modelNew);
ZVerify(m_models.first(modelNew));
modelNew->AddRef();
m_pClusterSite->AddThingSite(modelNew->GetThingSite());
{
//Add the model to the collision set
HitTest* ht = modelNew->GetHitTest();
ModelAttributes mt = modelNew->GetAttributes();
if ((mt & c_mtNotPickable) == 0)
{
ZVerify(m_modelsPickable.last(modelNew));
modelNew->AddRef();
}
if (mt & c_mtCastRay)
{
ZVerify(m_modelsCastRay.last(modelNew));
modelNew->AddRef();
}
if (mt & c_mtHitable)
{
((mt & c_mtStatic)
? m_kdrStatic
: m_kdrMoving).addHitTest(ht);
}
else
ht->SetDeadF(false);
ZRetailAssert(!ht->GetDeadF());
}
}
void CclusterIGC::DeleteModel(ImodelIGC* modelOld)
{
assert (modelOld);
{
//Add the model to the collision set
HitTest* ht = modelOld->GetHitTest();
ModelAttributes mt = modelOld->GetAttributes();
if ((mt & c_mtNotPickable) == 0)
{
DeleteIbaseIGC((BaseListIGC*)&m_modelsPickable, modelOld);
}
if (mt & c_mtCastRay)
{
DeleteIbaseIGC((BaseListIGC*)&m_modelsCastRay, modelOld);
}
if (mt & c_mtHitable)
{
((mt & c_mtStatic)
? m_kdrStatic
: m_kdrMoving).deleteHitTest(ht);
}
else
ht->SetDeadF(true);
ZRetailAssert(ht->GetDeadF());
}
m_pClusterSite->DeleteThingSite(modelOld->GetThingSite());
DeleteIbaseIGC((BaseListIGC*)&m_models, modelOld);
}
ImodelIGC* CclusterIGC::GetModel(const char* name) const
{
assert (name);
for (ModelLinkIGC* l = m_models.first();
(l != NULL);
l = l->next())
{
ImodelIGC* m = l->data();
if (_stricmp(m->GetName(), name) == 0)
{
return m;
}
}
return NULL;
}
void CclusterIGC::RecalculateCollisions(float tOffset,
ImodelIGC* pModel1,
ImodelIGC* pModel2)
{
//Update the stop positions for the hit tests (& update their bounding boxes)
assert ((pModel1->GetAttributes() & c_mtStatic) == 0);
HitTest* pHitTest1 = pModel1->GetHitTest();
if (pHitTest1)
{
if (pHitTest1->GetDeadF())
pHitTest1 = NULL;
else
pHitTest1->SetStopPosition();
}
HitTest* pHitTest2;
if ((pModel2 == NULL) || (pModel2->GetAttributes() & c_mtStatic))
pHitTest2 = NULL;
else
{
pHitTest2 = pModel2->GetHitTest();
if (pHitTest2)
{
if (pHitTest2->GetDeadF())
pHitTest2 = NULL;
else
pHitTest2->SetStopPosition();
}
}
if (pHitTest1 || pHitTest2)
{
m_tOffset += tOffset;
//Don't bother recalculating collisions if we are already outside the window in which collisions can occur.
if (m_tOffset <= m_tMax)
{
{
//Move all objects to their positions at the time of the collision
for (ModelLinkIGC* l = m_models.first();
(l != NULL);
l = l->next())
{
ImodelIGC* m = l->data();
HitTest* pht = m->GetHitTest();
//Move the models not involved in the collision
if ((m != pModel1) && (m != pModel2))
m->Move(tOffset);
else
pht->AdjustTimes(tOffset);
//Update the bounding boxes for everything
pht->UpdateBB();
}
}
//Remove any collisions that involved either object involved in this collision
m_collisions.flush(m_collisionID + 1, pHitTest1, pHitTest2);
{
//Check for any collisions between either ship and the rest of the stuff in the cluster
for (ModelLinkIGC* l = m_models.first();
(l != NULL);
l = l->next())
{
ImodelIGC* m = l->data();
if ((m != pModel1) && (m != pModel2) && (m->GetAttributes() & (c_mtCastRay | c_mtHitable)))
{
HitTest* ht = m->GetHitTest();
if (!ht->GetDeadF())
{
if (ht->GetID() != c_htidStaticObject)
{
//ht is not a static object, so it can always go first (which also handles
//the problem that projectiles (which ht might be) go before ships).
//prevent collisions between projectiles and their launcher.
if (pHitTest1 &&
(ht->GetNoHit() != pHitTest1) &&
(pHitTest1->GetNoHit() != ht))
ht->Collide(pHitTest1, &m_collisions);
if (pHitTest2 &&
(ht->GetNoHit() != pHitTest2) &&
(pHitTest2->GetNoHit() != ht))
ht->Collide(pHitTest2, &m_collisions);
}
else
{
//in collisions between static objects and non-static objects, the
//non-static object is always first
if (pHitTest1 &&
(ht->GetNoHit() != pHitTest1) &&
(pHitTest1->GetNoHit() != ht))
pHitTest1->Collide(ht, &m_collisions);
if (pHitTest2 &&
(ht->GetNoHit() != pHitTest2) &&
(pHitTest2->GetNoHit() != ht))
pHitTest2->Collide(ht, &m_collisions);
}
}
}
}
}
//Resort the collisions of the yet to be handled collisions
m_collisions.sort(m_collisionID + 1);
}
}
}
IbuildingEffectIGC* CclusterIGC::CreateBuildingEffect(Time now,
IasteroidIGC* pasteroid,
IstationIGC* pstation,
IshipIGC* pshipBuilder,
float radiusAsteroid,
float radiusStation,
const Vector& positionStart,
const Vector& positionStop)
{
DataBuildingEffectIGC dbe;
dbe.timeStart = now;
dbe.pasteroid = pasteroid;
dbe.pstation = pstation;
dbe.pcluster = this;
dbe.pshipBuilder = pshipBuilder;
dbe.radiusAsteroid = radiusAsteroid;
dbe.radiusStation = radiusStation;
dbe.positionStart = positionStart;
dbe.positionStop = positionStop;
IbuildingEffectIGC* pbe = (IbuildingEffectIGC*)(m_pMission->CreateObject(now, OT_buildingEffect, &dbe, sizeof(dbe)));
assert (pbe);
pbe->Release();
return pbe;
}