/* ** 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 #include ///////////////////////////////////////////////////////////////////////////// // 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 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); if (pship->OkToLaunch(now)) 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; }