//*************************************************************************** // // carnage.cpp -- File contains the Carnage class code // // MechCommander 2 // //---------------------------------------------------------------------------// // Copyright (C) Microsoft Corporation. All rights reserved. // //===========================================================================// #ifndef MCLIB_H #include "mclib.h" #endif #ifndef CARNAGE_H #include "carnage.h" #endif #ifndef GAMESOUND_H #include "gamesound.h" #endif #ifndef BLDNG_H #include "bldng.h" #endif #ifndef OBJMGR_H #include "objmgr.h" #endif #ifndef MULTPLYR_H #include "multplyr.h" #endif #ifndef WEAPONFX_H #include "weaponfx.h" #endif #ifndef TURRET_H #include "turret.h" #endif #ifndef GATE_H #include "gate.h" #endif #ifndef MISSION_H #include "mission.h" #endif #include //--------------------------------------------------------------------------- #define BLIP_FRAME_RATE 0.067 #define EXPLOSION_TIME 0.5 #define EXPLOSION_DEPTH_FIXUP -150 extern unsigned long NextIdNumber; extern TeamPtr homeTeam; extern bool useSound; extern bool somethingOnFire; float Carnage::maxFireBurnTime = 5.0; extern MidLevelRenderer::MLRClipper * theClipper; extern bool MLRVertexLimitReached; //*************************************************************************** // FIRE TYPE class //*************************************************************************** GameObjectPtr FireType::createInstance (void) { CarnagePtr newFire = new Carnage; if (!newFire) return(NULL); newFire->init(true, this); return(newFire); } //--------------------------------------------------------------------------- void FireType::destroy (void) { if (fireOffsetX) { systemHeap->Free(fireOffsetX); fireOffsetX = NULL; } if (fireOffsetY) { systemHeap->Free(fireOffsetY); fireOffsetY = NULL; } if (fireDelay) { systemHeap->Free(fireDelay); fireDelay = NULL; } if (fireRandomOffsetX) { systemHeap->Free(fireRandomOffsetX); fireRandomOffsetX = NULL; } if (fireRandomOffsetY) { systemHeap->Free(fireRandomOffsetY); fireRandomOffsetY = NULL; } if (fireRandomDelay) { systemHeap->Free(fireRandomDelay); fireRandomDelay = NULL; } ObjectType::destroy(); } //--------------------------------------------------------------------------- long FireType::init (FilePtr objFile, unsigned long fileSize) { long result = 0; FitIniFile explFile; result = explFile.open(objFile,fileSize); if (result != NO_ERR) return(result); result = explFile.seekBlock("FireData"); if (result != NO_ERR) return(result); unsigned long dmgLevel; result = explFile.readIdULong("DmgLevel",dmgLevel); if (result != NO_ERR) return(result); damageLevel = dmgLevel; result = explFile.readIdULong("SoundEffectId",soundEffectId); if (result != NO_ERR) return(result); result = explFile.readIdULong("LightObjectId",lightObjectId); if (result != NO_ERR) lightObjectId = -1; result = explFile.readIdULong("startLoopFrame",startLoopFrame); if (result != NO_ERR) return(result); result = explFile.readIdULong("numLoops",numLoops); if (result != NO_ERR) return(result); result = explFile.readIdULong("endLoopFrame",endLoopFrame); if (result != NO_ERR) return(result); result = explFile.readIdFloat("maxExtentRadius",maxExtent); if (result != NO_ERR) maxExtent = 0.0; result = explFile.readIdFloat("TimeToMaxExtent",timeToMax); if (result != NO_ERR) timeToMax = 0.0; result = explFile.readIdLong("TotalFireShapes",totalFires); if (result != NO_ERR) totalFires = 1; //------------------------------------------------------------------ // Initialize the base object Type from the current file. result = ObjectType::init(&explFile); objectClass = FIRE; return(result); } //--------------------------------------------------------------------------- bool FireType::handleCollision (GameObjectPtr collidee, GameObjectPtr collider) { if (MPlayer && !MPlayer->isServer()) return(false); if (!collider->isDestroyed()) { switch (collider->getObjectClass()) { case BATTLEMECH: //------------------------------------------------------------- // We should induce heat in a battlemech if it is in the fire. break; case BUILDING: //---------------------------------------------------------------------- // We should light a building on fire if we have expanded to engulf it. // Not anymore. Fires are undefined at present. break; case TERRAINOBJECT: if (((TerrainObjectPtr)collider)->getSubType() == TERROBJ_TREE) { if (RollDice(10)) { ((TerrainObjectPtr)collider)->lightOnFire(15.0); if (MPlayer) MPlayer->addLightOnFireChunk(collider, 15); } } break; } } return(false); } //--------------------------------------------------------------------------- bool FireType::handleDestruction (GameObjectPtr collidee, GameObjectPtr collider) { return(FALSE); } //*************************************************************************** // EXPLOSION TYPE class //*************************************************************************** GameObjectPtr ExplosionType::createInstance (void) { CarnagePtr newExplosion = new Carnage; if (!newExplosion) return(NULL); newExplosion->init(true, this); return(newExplosion); } //--------------------------------------------------------------------------- void ExplosionType::destroy (void) { ObjectType::destroy(); } //--------------------------------------------------------------------------- long ExplosionType::init (FilePtr objFile, unsigned long fileSize) { long result = 0; FitIniFile explFile; result = explFile.open(objFile,fileSize); if (result != NO_ERR) return(result); //------------------------------------------------------------------ // Read in the data needed to fire the laser result = explFile.seekBlock("ExplosionData"); if (result != NO_ERR) return(result); unsigned long dmgLevel; result = explFile.readIdULong("DmgLevel",dmgLevel); if (result != NO_ERR) return(result); damageLevel = dmgLevel; result = explFile.readIdULong("SoundEffectId",soundEffectId); if (result != NO_ERR) return(result); result = explFile.readIdLong("ExplosionRadius",explRadius); if (result != NO_ERR) explRadius = 0; result = explFile.readIdULong("LightObjectId",lightObjectId); if (result != NO_ERR) lightObjectId = -1; result = explFile.readIdFloat("DamageChunkSize",chunkSize); if (result != NO_ERR) chunkSize = 5.0; result = explFile.readIdFloat("DelayUntilCollidable",delayUntilCollidable); if (result != NO_ERR) delayUntilCollidable = EXPLOSION_TIME; result = explFile.seekBlock("LightData"); if (result == NO_ERR) { result = explFile.readIdFloat("LightMinMaxRadius",lightMinMaxRadius); if (result != NO_ERR) STOP(("Light data for explosion BAD")); result = explFile.readIdFloat("LightMaxMaxRadius",lightMaxMaxRadius); if (result != NO_ERR) STOP(("Light data for explosion BAD")); result = explFile.readIdFloat("LightOutMinRadius",lightOutMinRadius); if (result != NO_ERR) STOP(("Light data for explosion BAD")); result = explFile.readIdFloat("LightOutMaxRadius",lightOutMaxRadius); if (result != NO_ERR) STOP(("Light data for explosion BAD")); result = explFile.readIdULong("LightRGB",lightRGB); if (result != NO_ERR) STOP(("Light data for explosion BAD")); result = explFile.readIdFloat("MaxIntensity",maxIntensity); if (result != NO_ERR) STOP(("Light data for explosion BAD")); result = explFile.readIdFloat("MinIntensity",minIntensity); if (result != NO_ERR) STOP(("Light data for explosion BAD")); result = explFile.readIdFloat("Duration",duration); if (result != NO_ERR) STOP(("Light data for explosion BAD")); } //------------------------------------------------------------------ // Initialize the base object Type from the current file. result = ObjectType::init(&explFile); objectClass = EXPLOSION; return(result); } //--------------------------------------------------------------------------- bool ExplosionType::handleCollision (GameObjectPtr collidee, GameObjectPtr collider) { if (MPlayer && !MPlayer->isServer()) return(false); float damageLeft = collidee->getExplDmg(); //------------------------------------------------------- // Explosions CAN damage objects around them if they have // a damage level associated with them. if (damageLeft != 0.0) { float damageAmount = 0.0; WeaponShotInfo shot; if (damageLeft > ((CarnagePtr)collidee)->info.explosion.chunkSize) damageAmount = ((CarnagePtr)collidee)->info.explosion.chunkSize; else damageAmount = damageLeft; if (collider->isMover()) { shot.init(NULL, -1, damageAmount, 0, 0); while (damageLeft > 0.0) { shot.hitLocation = collider->calcHitLocation(collidee,-1,ATTACKSOURCE_ARTILLERY,0); collider->handleWeaponHit(&shot, (MPlayer != NULL)); damageLeft -= ((CarnagePtr)collidee)->info.explosion.chunkSize; } } else { switch (collider->getObjectClass()) { case GATE: { //--------------------------------------------------- // An explosion may NOT damage a gate or turret unless // is is within the "little" extent radius. Stuff::Vector3D distance = collider->getPosition(); distance -= collidee->getPosition(); float range = distance.GetLength(); float littleExtent = ((TurretPtr)collider)->getLittleExtent(); if (littleExtent < range) { float realRange = range - littleExtent; if (realRange > collidee->getExtentRadius()) return(false); } shot.init(NULL, -1, damageAmount, 0, 0); while (damageLeft > 0.0) { shot.hitLocation = 0; collider->handleWeaponHit(&shot, (MPlayer != NULL)); damageLeft -= chunkSize; } } break; case TURRET: { //--------------------------------------------------- // An explosion may NOT damage a gate or turret unless // is is within the "little" extent radius. Stuff::Vector3D distance = collider->getPosition(); distance -= collidee->getPosition(); float range = distance.GetLength(); float littleExtent = ((TurretPtr)collider)->getLittleExtent(); if (littleExtent < range) { float realRange = range - littleExtent; if (realRange > collidee->getExtentRadius()) return(FALSE); } float damageLeft = collidee->getExplDmg(); WeaponShotInfo shot; shot.init(NULL, -1, damageAmount, 0, 0); while (damageLeft > 0.0) { shot.hitLocation = 0; collider->handleWeaponHit(&shot, (MPlayer != NULL)); damageLeft -= chunkSize; } } break; default: { WeaponShotInfo shot; shot.init(NULL, -1, collidee->getExplDmg(), 0, 0); collider->handleWeaponHit(&shot, (MPlayer != NULL)); } } } } return(false); } //--------------------------------------------------------------------------- bool ExplosionType::handleDestruction (GameObjectPtr collidee, GameObjectPtr collider) { //------------------------------------------------------- // The laser ceases to exist when its effect is done. // always return FALSE or the collision will make it // go away! return(false); } //*************************************************************************** // CARNAGE class //*************************************************************************** void Carnage::init (bool create) { carnageType = CARNAGE_NONE; ownerWID = 0; info.fire.timeToBurn = 0.0; info.fire.radius = 0.0; info.fire.reallyVisible = 0; info.explosion.timer = EXPLOSION_TIME; info.explosion.chunkSize = 0.0; gosEffect = NULL; intensity = 0.0f; inRadius = 0.0f; outRadius = 0.0f; duration = 0.0f; pointLight = NULL; lightId = 0xffffffff; } //--------------------------------------------------------------------------- void Carnage::init (CarnageEnumType _carnageType) { //-------------------------------------------------------------------- // This function not only sets the carnage type, but also re-inits the // object so it may be used again in the game without re-allocating // it... carnageType = _carnageType; if (carnageType == CARNAGE_FIRE) { } else if (carnageType == CARNAGE_EXPLOSION) { intensity = 0.0f; inRadius = 0.0f; outRadius = 0.0f; duration = 0.0f; pointLight = NULL; lightId = 0xffffffff; } } //--------------------------------------------------------------------------- GameObjectPtr Carnage::getOwner (void) { return(ObjectManager->getByWatchID(ownerWID)); } //--------------------------------------------------------------------------- void Carnage::handleStaticCollision (void) { if (getTangible()) { //----------------------------------------------------- // What is our block and vertex number? long blockNumber = 0; long vertexNumber = 0; getBlockAndVertexNumber(blockNumber,vertexNumber); //------------------------------------------------------------------------- // We must now move out into other tiles for the artillery strike to work. // Remember, Its pretty big! // Just grab the nine vertices around this one. Problems arise when on Block border. Handle it. long topLeftBlockNumber = blockNumber - Terrain::blocksMapSide - 1; long currentBlockNumber = topLeftBlockNumber; long totalBlocks = Terrain::blocksMapSide * Terrain::blocksMapSide; for (long i = 0; i < 3; i++) { for (long j = 0; j < 3; j++) { if ((currentBlockNumber >= 0) && (currentBlockNumber < totalBlocks)) { long numObjectsInBlock = ObjectManager->getObjBlockNumObjects(currentBlockNumber); for (long objIndex = 0; objIndex < numObjectsInBlock; objIndex++) { GameObjectPtr obj = ObjectManager->getObjBlockObject(currentBlockNumber, objIndex); if (obj->getExists() && (obj->getObjectClass() != GATE) && (obj->getObjectClass() != TURRET)) ObjectManager->detectStaticCollision(this, obj); } } currentBlockNumber++; } currentBlockNumber = topLeftBlockNumber + (Terrain::blocksMapSide * (i + 1)); } //------------------------------------------------------- // MUST figure out radius to set off mines in CELLS now. // UNLESS WE ARE A MINE EXPLOSION!!! // -fs if ((effectId != MINE_EXPLOSION_ID) && (info.explosion.radius > 0.0f)) { long CellRow, CellCol; land->worldToCell(getPosition(), CellRow, CellCol); long startCellRow = CellRow - 4; long startCellCol = CellCol - 4; for (i = startCellRow; i < startCellRow + 9; i++) { for (long j = startCellCol; j < startCellCol + 9; j++) { if (GameMap->inBounds(i,j)) { long mineResult = 0; mineResult = GameMap->getMine(i,j); if (mineResult == 1) { Stuff::Vector3D minePosition; land->cellToWorld(i,j,minePosition); Stuff::Vector3D distance; distance.Subtract(getPosition(),minePosition); distance.z = 0.0f; float dist = distance.GetApproximateLength(); if (dist < info.explosion.radius) { GameMap->setMine(i,j,2); if (MPlayer) { MPlayer->addMineChunk(i, j, 1, 2, 2); } ObjectManager->createExplosion(MINE_EXPLOSION_ID, NULL, minePosition, MineSplashDamage, MineSplashRange * worldUnitsPerMeter); } } } } } } } } //--------------------------------------------------------------------------- bool Carnage::onScreen (void) { //---------------------------------------------------------------------- // Need a real check here against the OBB Radius of the effect return(true); } //--------------------------------------------------------------------------- void Carnage::finishNow (void) { //When the fire goes out, it may spread. if (carnageType == CARNAGE_FIRE) { setFlag(OBJECT_FLAG_SPREAD, true); } else if (carnageType == CARNAGE_EXPLOSION) { if (gosEffect) { gosEffect->Kill(); //Effect is over. delete gosEffect; gosEffect = NULL; } if (pointLight) { eye->removeWorldLight(lightId,pointLight); systemHeap->Free(pointLight); pointLight = NULL; } init(false); } } //--------------------------------------------------------------------------- void Carnage::addTimeLeft (float timeLeft) { if (carnageType == CARNAGE_FIRE) { } else if (carnageType == CARNAGE_EXPLOSION) { } } //--------------------------------------------------------------------------- long Carnage::update (void) { if (carnageType == CARNAGE_FIRE) { } else if (carnageType == CARNAGE_EXPLOSION) { if (getFlag(OBJECT_FLAG_JUSTCREATED)) { setFlag(OBJECT_FLAG_JUSTCREATED, false); Stuff::LinearMatrix4D shapeOrigin; Stuff::LinearMatrix4D localToWorld; Stuff::Point3D actualPosition; actualPosition.x = -position.x; actualPosition.y = position.z; actualPosition.z = position.y; shapeOrigin.BuildRotation(Stuff::EulerAngles(0.0f,0.0f,0.0f)); shapeOrigin.BuildTranslation(actualPosition); if (gosEffect && gosEffect->IsExecuted()) { gosFX::Effect::ExecuteInfo info((Stuff::Time)scenarioTime,&shapeOrigin,NULL); gosEffect->Start(&info); } setExists(true); //------------------------------------------ // If just created, play our sound effect... if (((ExplosionTypePtr)getObjectType())->soundEffectId != -1) soundSystem->playDigitalSample(((ExplosionTypePtr)getObjectType())->soundEffectId,position,true); if (((ExplosionTypePtr)getObjectType())->duration > 0.0f) { // Explosion Has Point Light Source // Make and Init it here. pointLight = (TG_LightPtr)systemHeap->Malloc(sizeof(TG_Light)); pointLight->init(TG_LIGHT_POINT); pointLight->SetaRGB(((ExplosionTypePtr)getObjectType())->lightRGB); intensity = ((ExplosionTypePtr)getObjectType())->maxIntensity; outRadius = ((ExplosionTypePtr)getObjectType())->lightOutMaxRadius; inRadius = ((ExplosionTypePtr)getObjectType())->lightMaxMaxRadius; pointLight->SetIntensity(intensity); pointLight->SetFalloffDistances(inRadius,outRadius); lightId = eye->addWorldLight(pointLight); duration = ((ExplosionTypePtr)getObjectType())->duration; if (lightId == -1) { //NO LIGHT ALLOWED! TOO Many in World! // LightId is now -1 which will cause this to NEVER make a light! systemHeap->Free(pointLight); pointLight = NULL; duration = 0.0f; } } } //------------------------------------------------ // Attach explosion to thing dying if we need to. if (ownerWID) { position = ObjectManager->getByWatchID(ownerWID)->getPosition(); } //------------------------------------------------ // Update pointLight if its not NULL if (pointLight) { if (duration > 0.0f) { float timeFactor = duration / ((ExplosionTypePtr)getObjectType())->duration; intensity = ((ExplosionTypePtr)getObjectType())->minIntensity + (((ExplosionTypePtr)getObjectType())->maxIntensity - ((ExplosionTypePtr)getObjectType())->minIntensity) * timeFactor; outRadius = ((ExplosionTypePtr)getObjectType())->lightOutMinRadius + (((ExplosionTypePtr)getObjectType())->lightOutMaxRadius - ((ExplosionTypePtr)getObjectType())->lightOutMinRadius) * timeFactor; inRadius = ((ExplosionTypePtr)getObjectType())->lightMinMaxRadius + (((ExplosionTypePtr)getObjectType())->lightMaxMaxRadius - ((ExplosionTypePtr)getObjectType())->lightMinMaxRadius) * timeFactor; pointLight->SetIntensity(intensity); pointLight->SetFalloffDistances(inRadius,outRadius); duration -= frameLength; Stuff::Point3D ourPosition; ourPosition.x = -position.x; ourPosition.y = position.z; ourPosition.z = position.y; pointLight->direction = ourPosition; Stuff::LinearMatrix4D lightToWorldMatrix; lightToWorldMatrix.BuildTranslation(ourPosition); lightToWorldMatrix.BuildRotation(Stuff::EulerAngles(0.0f,0.0f,0.0f)); pointLight->SetLightToWorld(&lightToWorldMatrix); pointLight->SetPosition(&position); } else { eye->removeWorldLight(lightId,pointLight); systemHeap->Free(pointLight); pointLight = NULL; } } setFlag(OBJECT_FLAG_TANGIBLE, false); if (info.explosion.timer > 0.0f) { info.explosion.timer -= frameLength; if (info.explosion.timer <= 0.0f) { setFlag(OBJECT_FLAG_TANGIBLE, true); } } //------------------------------------------------ // Update GOSFX if (gosEffect && gosEffect->IsExecuted()) { Stuff::Point3D actualPosition; actualPosition.x = -position.x; actualPosition.y = position.z; actualPosition.z = position.y; Stuff::LinearMatrix4D shapeOrigin; Stuff::LinearMatrix4D localToWorld; shapeOrigin.BuildRotation(Stuff::EulerAngles(0.0f,0.0f,0.0f)); shapeOrigin.BuildTranslation(actualPosition); localToWorld.Multiply(shapeOrigin,gosFX::Effect_Into_Motion); Stuff::OBB boundingBox; gosFX::Effect::ExecuteInfo info((Stuff::Time)scenarioTime,&shapeOrigin,&boundingBox); bool result = gosEffect->Execute(&info); if (!result) { gosEffect->Kill(); //Effect is over. delete gosEffect; gosEffect = NULL; if (pointLight) { eye->removeWorldLight(lightId,pointLight); systemHeap->Free(pointLight); pointLight = NULL; } return false; //Tell the object system to stop updating, too. } } else if (info.explosion.timer <= 0.0f) //If we had no GosEffect, make sure we've collided and then move on. { if (pointLight) { eye->removeWorldLight(lightId,pointLight); systemHeap->Free(pointLight); pointLight = NULL; } return false; } } return (true); } //--------------------------------------------------------------------------- void Carnage::render (void) { if (carnageType == CARNAGE_FIRE) { } else if (carnageType == CARNAGE_EXPLOSION) { if (gosEffect && gosEffect->IsExecuted() && !getFlag(OBJECT_FLAG_JUSTCREATED)) { gosFX::Effect::DrawInfo drawInfo; drawInfo.m_clipper = theClipper; MidLevelRenderer::MLRState mlrState; mlrState.SetDitherOn(); mlrState.SetTextureCorrectionOn(); mlrState.SetZBufferCompareOn(); mlrState.SetZBufferWriteOn(); drawInfo.m_state = mlrState; drawInfo.m_clippingFlags = 0x0; Stuff::Point3D actualPosition; actualPosition.x = -position.x; actualPosition.y = position.z; actualPosition.z = position.y; Stuff::LinearMatrix4D shapeOrigin; Stuff::LinearMatrix4D localToWorld; shapeOrigin.BuildRotation(Stuff::EulerAngles(0.0f,0.0f,0.0f)); shapeOrigin.BuildTranslation(actualPosition); drawInfo.m_parentToWorld = &shapeOrigin; if (!MLRVertexLimitReached) gosEffect->Draw(&drawInfo); } } } //--------------------------------------------------------------------------- void Carnage::destroy (void) { if (carnageType == CARNAGE_FIRE) { } else if (carnageType == CARNAGE_EXPLOSION) { if (gosEffect) { gosEffect->Kill(); //Effect is over. delete gosEffect; gosEffect = NULL; } if (pointLight) { if (eye) eye->removeWorldLight(lightId,pointLight); systemHeap->Free(pointLight); pointLight = NULL; } } } //--------------------------------------------------------------------------- void Carnage::init (bool create, ObjectTypePtr _type) { GameObject::init(true, _type); gosEffect = NULL; pointLight = NULL; setFlag(OBJECT_FLAG_JUSTCREATED, true); if (carnageType == CARNAGE_FIRE) { } else if (carnageType == CARNAGE_EXPLOSION) { info.explosion.chunkSize = ((ExplosionType *)_type)->chunkSize; info.explosion.timer = ((ExplosionType *)_type)->delayUntilCollidable; if (stricmp(weaponEffects->GetEffectName(effectId),"NONE") != 0) { //-------------------------------------------------------------- // We need to create the GOSFX we need for this explosion here. // The name of the GOSFX is stored as the appearanceName in the Type. // ONLY Execute. Explosions just run once! unsigned flags = gosFX::Effect::ExecuteFlag; Check_Object(gosFX::EffectLibrary::Instance); gosFX::Effect::Specification* gosEffectSpec = gosFX::EffectLibrary::Instance->Find(weaponEffects->GetEffectName(effectId)); gosEffect = gosFX::EffectLibrary::Instance->MakeEffect(gosEffectSpec->m_effectID, flags); gosASSERT(gosEffect != NULL); MidLevelRenderer::MLRTexturePool::Instance->LoadImages(); } setFlag(OBJECT_FLAG_TANGIBLE, false); } } //--------------------------------------------------------------------------- void Carnage::Save (PacketFilePtr file, long packetNum) { CarnageData data; CopyTo(&data); //PacketNum incremented in ObjectManager!! file->writePacket(packetNum,(MemoryPtr)&data,sizeof(CarnageData),STORAGE_TYPE_ZLIB); } //--------------------------------------------------------------------------- void Carnage::CopyTo (CarnageData *data) { data->carnageType = carnageType; data->ownerWID = ownerWID; data->info = info; data->effectId = effectId; GameObject::CopyTo(dynamic_cast(data)); } //--------------------------------------------------------------------------- void Carnage::Load (CarnageData *data) { GameObject::Load(dynamic_cast(data)); carnageType = data->carnageType; ownerWID = data->ownerWID; info = data->info; effectId = data->effectId; } //---------------------------------------------------------------------------