//////////////////////////////////////////////////////////////////////////////// // // Copyright 2016 RWS Inc, All Rights Reserved // // This program is free software; you can redistribute it and/or modify // it under the terms of version 2 of the GNU General Public License as published by // the Free Software Foundation // // 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, write to the Free Software Foundation, Inc., // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA // // ostrich.cpp // Project: Postal // // This module implements the ostrich. // // History: // // 05/09/97 BRH Started the ostrich from the band member logic. // // 05/22/97 BRH Render skips the Doofus render and goes right to the // Character render since its not using the m_dAnimRot. // // 05/26/97 BRH Changed the call to OnDead to the Doofus version // rather than the Character version. // // 05/29/97 JMI Changed instance of REALM_ATTR_FLOOR_MASK to // REALM_ATTR_NOT_WALKABLE. // // 06/05/97 JMI Changed m_sHitPoints to m_stockpile.m_sHitPoints to // accommodate new m_stockpile in base class, CThing3d (which // used to contain the m_sHitPoints). // // 06/18/97 BRH Changed over to using GetRandom() // // 06/25/97 JMI Now calls PrepareShadow() in Init() which loads and sets up // a shadow sprite. // // 06/30/97 MJR Replaced SAFE_GUI_REF with new GuiItem.h-defined macro. // // 06/30/97 BRH Cached the sound samples in static portion of Load // function so that the sound effects will be ready // whenever this item is used on a level. // // 07/01/97 JMI Replaced GetFloorMapValue() with GetHeightAndNoWalk() call. // // 07/09/97 JMI Now uses m_pRealm->Make2dResPath() to get the fullpath // for 2D image components. // // 07/12/97 BRH Changed panic motion to the same as the base class, but // it still didn't fix the problem of them going through the // wall of the train car on the farm level. Also increased // their hit points, and made them not die in one shot. // // 07/18/97 JMI Got rid of bogus immitation PlaySample functions. // Now there is one PlaySample() function. Also, you now // MUST specify a category and you don't have to specify a // SoundInstance ptr to specify a volume. // // 07/21/97 BRH Changed the burning state so that the ostrich will burn // longer. // // 08/06/97 BRH Changed smash bits to zero when dying starts so that things // won't hit him as he falls slowly, and so you can shoot // past him. Also allows the shot animation restart when shot // to get a little more reaction since his shot animation // is so long. // // 08/10/97 JMI Commented out the code that deleted all the sound things in // a realm when the ostriches paniced. // // 08/12/97 BRH Set the flag for victim rather than hostile so that // the score will display the correct numbers of each // type. // // 09/03/97 JMI Replaced Good Smash bit with Civilian. // // 09/03/97 BRH Put in the real ostrich sounds. // // 09/04/97 BRH Added ostrich dying sound. // //////////////////////////////////////////////////////////////////////////////// #define OSTRICH_CPP #include "RSPiX.h" #include #include "ostrich.h" #include "SampleMaster.h" #include "item3d.h" //////////////////////////////////////////////////////////////////////////////// // Macros/types/etc. //////////////////////////////////////////////////////////////////////////////// #define BRIGHTNESS_PER_LIGHT_ATTRIBUTE 15 #define NUM_ELEMENTS(a) (sizeof(a) / sizeof(a[0]) ) // Notification message lParm1's. #define BLOOD_POOL_DONE_NOTIFICATION 1 // Blood pool is done animating. // Random amount the blood splat can adjust. #define BLOOD_SPLAT_SWAY 10 // Gets a random between -range / 2 and range / 2. #define RAND_SWAY(sway) ((GetRandom() % sway) - sway / 2) #define GUI_ID_OK 1 //////////////////////////////////////////////////////////////////////////////// // Variables/data //////////////////////////////////////////////////////////////////////////////// double COstrich::ms_dExplosionVelocity = 180.0; double COstrich::ms_dMaxMarchVel = 30.0; double COstrich::ms_dMaxRunVel = 80.0; long COstrich::ms_lStateChangeTime = 10000; short COstrich::ms_sStartingHitPoints = 100; // Let this auto-init to 0 short COstrich::ms_sFileCount; /// Standing Animation Files // An array of pointers to resource names (one for each channel of the animation) static char* ms_apszStandResNames[] = { "3d/ostrich_stand.sop", "3d/ostrich_stand.mesh", "3d/ostrich_stand.tex", "3d/ostrich_stand.hot", "3d/ostrich_stand.bounds", "3d/ostrich_stand.floor", NULL, NULL }; /// Running Animation Files // An array of pointers to resource names (one for each channel of the animation) static char* ms_apszRunResNames[] = { "3d/ostrich_run.sop", "3d/ostrich_run.mesh", "3d/ostrich_run.tex", "3d/ostrich_run.hot", "3d/ostrich_run.bounds", "3d/ostrich_run.floor", NULL, NULL }; /// Throwing Animation Files // An array of pointers to resource names (one for each channel of the animation) static char* ms_apszWalkResNames[] = { "3d/ostrich_walk.sop", "3d/ostrich_walk.mesh", "3d/ostrich_walk.tex", "3d/ostrich_walk.hot", "3d/ostrich_walk.bounds", "3d/ostrich_walk.floor", NULL, NULL }; // Shot Animation Files // An array of pointers to resource names (one for each channel of the animation) static char* ms_apszShotResNames[] = { "3d/ostrich_shot.sop", "3d/ostrich_shot.mesh", "3d/ostrich_shot.tex", "3d/ostrich_shot.hot", "3d/ostrich_shot.bounds", "3d/ostrich_shot.floor", NULL, NULL }; /// Blown up Animation Files // An array of pointers to resource names (one for each channel of the animation) static char* ms_apszBlownupResNames[] = { "3d/ostrich_blownup.sop", "3d/ostrich_blownup.mesh", "3d/ostrich_blownup.tex", "3d/ostrich_blownup.hot", "3d/ostrich_blownup.bounds", "3d/ostrich_blownup.floor", NULL, NULL }; /// Hide Animation Files // An array of pointers to resource names (one for each channel of the animation) static char* ms_apszHideResNames[] = { "3d/ostrich_hide.sop", "3d/ostrich_hide.mesh", "3d/ostrich_hide.tex", "3d/ostrich_hide.hot", "3d/ostrich_hide.bounds", "3d/ostrich_hide.floor", NULL, NULL }; /// Die Animation files // An array of pointers to resource names (one for each channel of the animation) static char* ms_apszDieResNames[] = { "3d/ostrich_die.sop", "3d/ostrich_die.mesh", "3d/ostrich_die.tex", "3d/ostrich_die.hot", "3d/ostrich_die.bounds", "3d/ostrich_die.floor", NULL, NULL }; // These are the points that are checked on the attribute map relative to his origin static RP3d ms_apt3dAttribCheck[] = { {-6, 0, -6}, { 0, 0, -6}, { 6, 0, -6}, {-6, 0, 6}, { 0, 0, 6}, { 6, 0, 6}, }; //////////////////////////////////////////////////////////////////////////////// // Load object (should call base class version!) //////////////////////////////////////////////////////////////////////////////// short COstrich::Load( // Returns 0 if successfull, non-zero otherwise RFile* pFile, // In: File to load from bool bEditMode, // In: True for edit mode, false otherwise short sFileCount, // In: File count (unique per file, never 0) ULONG ulFileVersion) // In: Version of file format to load. { short sResult = 0; // Call the base class load to get the instance ID, position, motion etc. sResult = CDoofus::Load(pFile, bEditMode, sFileCount, ulFileVersion); if (sResult == 0) { // Load common data just once per file (not with each object) if (ms_sFileCount != sFileCount) { ms_sFileCount = sFileCount; // Load static data switch (ulFileVersion) { default: case 1: CacheSample(g_smidOstrichShot); CacheSample(g_smidOstrichBurning); CacheSample(g_smidOstrichBlownup); CacheSample(g_smidOstrichDie); break; } } // Load Rocket Man specific data switch (ulFileVersion) { default: case 4: case 3: case 2: case 1: break; } // Make sure there were no file errors or format errors . . . if (!pFile->Error() && sResult == 0) { // Get resources sResult = GetResources(); } else { sResult = -1; TRACE("COstrich::Load(): Error reading from file!\n"); } } else { TRACE("CGrenader::Load(): CDoofus::Load() failed.\n"); } return sResult; } //////////////////////////////////////////////////////////////////////////////// // Save object (should call base class version!) //////////////////////////////////////////////////////////////////////////////// short COstrich::Save( // Returns 0 if successfull, non-zero otherwise RFile* pFile, // In: File to save to short sFileCount) // In: File count (unique per file, never 0) { short sResult = SUCCESS; // Call the base class save to save the instance ID, position etc. CDoofus::Save(pFile, sFileCount); // Save common data just once per file (not with each object) if (ms_sFileCount != sFileCount) { ms_sFileCount = sFileCount; // Save static data } if (!pFile->Error()) { sResult = SUCCESS; } else { TRACE("COstrich::Save() - Error writing to file\n"); sResult = -1; } return sResult; } //////////////////////////////////////////////////////////////////////////////// // Init - Call this after the resources are in place //////////////////////////////////////////////////////////////////////////////// short COstrich::Init(void) { short sResult = 0; // Prepare shadow (get resources and setup sprite). sResult = PrepareShadow(); // Init position, rotation and velocity m_dVel = 0.0; m_dRot = rspMod360(GetRandom()); m_lPrevTime = m_pRealm->m_time.GetGameTime(); m_state = CCharacter::State_Idle; m_lTimer = m_pRealm->m_time.GetGameTime() + 500; m_sBrightness = 0; // Default brightness m_smash.m_bits = CSmash::Civilian | CSmash::Character; m_smash.m_pThing = this; m_lAnimTime = 0; m_panimCur = &m_animStand; m_lTimer = m_pRealm->m_time.GetGameTime(); m_stockpile.m_sHitPoints = ms_sStartingHitPoints; m_state = CCharacter::State_Stand; m_dAcc = 150; return sResult; } //////////////////////////////////////////////////////////////////////////////// // Startup object //////////////////////////////////////////////////////////////////////////////// short COstrich::Startup(void) // Returns 0 if successfull, non-zero otherwise { // Register this as a victim rather than a hostile. m_bCivilian = true; // Set the current height, previous time, and Nav Net by calling the // base class startup CDoofus::Startup(); // Init other stuff return Init(); } //////////////////////////////////////////////////////////////////////////////// // Update object //////////////////////////////////////////////////////////////////////////////// void COstrich::Update(void) { short sHeight = m_sPrevHeight; double dNewX; double dNewY; double dNewZ; double dX; double dZ; long lThisTime; long lTimeDifference; if (!m_sSuspend) { // Get new time lThisTime = m_pRealm->m_time.GetGameTime(); lTimeDifference = lThisTime - m_lPrevTime; // Calculate elapsed time in seconds double dSeconds = (double)(lThisTime - m_lPrevTime) / 1000.0; // Check for new messages that may change the state ProcessMessages(); // Increment animation time m_lAnimTime += lTimeDifference; // Check the current state switch (m_state) { //----------------------------------------------------------------------- // Stand or Hide - Either one waits in the current position for the // random timer to expire. //----------------------------------------------------------------------- case State_Stand: case State_Hide: if (lThisTime > m_lTimer) ChangeRandomState(); break; //----------------------------------------------------------------------- // Walk - Walk around a bit //----------------------------------------------------------------------- case State_Walk: if (lThisTime > m_lTimer) { ChangeRandomState(); } else { dX = m_dX; dZ = m_dZ; m_dAcc = ms_dAccUser; UpdateVelocities(dSeconds, ms_dMaxMarchVel, ms_dMaxMarchVel); GetNewPosition(&dNewX, &dNewY, &dNewZ, dSeconds); if (MakeValidPosition(&dNewX, &dNewY, &dNewZ, 10) == true) { // Update Values ///////////////////////////////////////////////////////// m_dX = dNewX; m_dY = dNewY; m_dZ = dNewZ; UpdateFirePosition(); } else { // Restore Values //////////////////////////////////////////////////////// m_dVel -= m_dDeltaVel; } // If he didn't move at all, then turn him so he will // avoid the wall if (dX == m_dX && dZ == m_dZ) { // Turn to avoid wall next time if (m_sRotDirection == 0) m_dRot = rspMod360(m_dRot - 20); else m_dRot = rspMod360(m_dRot + 20); } } break; //----------------------------------------------------------------------- // Panic - Pick a random bouy and run to it. When you are there, pick // a different random bouy and run to it. //----------------------------------------------------------------------- case State_Panic: dX = m_dX; dZ = m_dZ; DeluxeUpdatePosVel(dSeconds); /* m_dAcc = ms_dAccUser; UpdateVelocities(dSeconds, ms_dMaxRunVel, ms_dMaxRunVel); GetNewPosition(&dNewX, &dNewY, &dNewZ, dSeconds); // Get height and 'no walk' status at new position. bool bNoWalk; sHeight = m_pRealm->GetHeightAndNoWalk(dNewX, dNewY, &bNoWalk); // If too big a height difference or completely not walkable . . . if (bNoWalk == true || (sHeight - dNewY > 10) )// && m_bAboveTerrain == false && m_dExtHorzVel == 0.0)) { TRACE("****** - loose ostrish\n"); // Move like smoke } if (MakeValidPosition(&dNewX, &dNewY, &dNewZ, 10) == true) { // Update Values ///////////////////////////////////////////////////////// m_dX = dNewX; m_dY = dNewY; m_dZ = dNewZ; UpdateFirePosition(); } else { // Restore Values //////////////////////////////////////////////////////// m_dVel -= m_dDeltaVel; } */ // If he didn't move at all, then turn him so he will // avoid the wall if (dX == m_dX && dZ == m_dZ) { // Turn to avoid wall next time if (m_sRotDirection == 0) m_dRot = rspMod360(m_dRot - 20); else m_dRot = rspMod360(m_dRot + 20); } break; //----------------------------------------------------------------------- // Burning - Run around on fire until dead //----------------------------------------------------------------------- case State_Burning: if (!CCharacter::WhileBurning()) { if (m_stockpile.m_sHitPoints <= 0) { m_state = State_Die; m_lAnimTime = 0; m_panimCur = &m_animDie; PlaySample( g_smidOstrichDie, SampleMaster::Voices); } else { m_state = State_Panic; } } break; //----------------------------------------------------------------------- // Blown Up - Do motion into the air until you hit the ground again //----------------------------------------------------------------------- case State_BlownUp: if (!CCharacter::WhileBlownUp()) m_state = State_Dead; else UpdateFirePosition(); break; //----------------------------------------------------------------------- // Shot - Dies in one shot //----------------------------------------------------------------------- case State_Shot: if (!CCharacter::WhileShot()) { if (m_stockpile.m_sHitPoints <= 0) { m_state = State_Die; m_panimCur = &m_animDie; m_lAnimTime = 0; PlaySample( g_smidOstrichDie, SampleMaster::Voices); } else { m_state = State_Panic; m_panimCur = &m_animRun; m_lAnimTime = 0; } } break; //----------------------------------------------------------------------- // Die - run die animation until done, the you are dead //----------------------------------------------------------------------- case State_Die: m_smash.m_bits = 0; if (!CCharacter::WhileDying()) m_state = State_Dead; else UpdateFirePosition(); break; //----------------------------------------------------------------------- // Dead - paste yourself in the background and delete yourself //----------------------------------------------------------------------- case State_Dead: OnDead(); delete this; return; break; } m_smash.m_sphere.sphere.X = m_dX; // Fudge center of sphere as half way up the dude. // Doesn't work if dude's feet leave the origin. m_smash.m_sphere.sphere.Y = m_dY + m_sprite.m_sRadius; m_smash.m_sphere.sphere.Z = m_dZ; m_smash.m_sphere.sphere.lRadius = m_sprite.m_sRadius; // Update the smash. m_pRealm->m_smashatorium.Update(&m_smash); // Save height for next time m_sPrevHeight = sHeight; // Save time for next time m_lPrevTime = lThisTime; } } //////////////////////////////////////////////////////////////////////////////// // Render object //////////////////////////////////////////////////////////////////////////////// void COstrich::Render(void) { // Call base class. CCharacter::Render(); } //////////////////////////////////////////////////////////////////////////////// // Called by editor to init new object at specified position //////////////////////////////////////////////////////////////////////////////// short COstrich::EditNew( // Returns 0 if successfull, non-zero otherwise short sX, // In: New x coord short sY, // In: New y coord short sZ) // In: New z coord { short sResult = 0; // Call the base class to place the item. sResult = CDoofus::EditNew(sX, sY, sZ); if (sResult == SUCCESS) { // Load resources sResult = GetResources(); if (sResult == SUCCESS) { sResult = Init(); } } return sResult; } //////////////////////////////////////////////////////////////////////////////// // EditModify - Show dialog box for selecting starting bouy //////////////////////////////////////////////////////////////////////////////// short COstrich::EditModify(void) { return 0; } //////////////////////////////////////////////////////////////////////////////// // Get all required resources //////////////////////////////////////////////////////////////////////////////// short COstrich::GetResources(void) // Returns 0 if successfull, non-zero otherwise { short sResult = 0; sResult = m_animRun.Get(ms_apszRunResNames, RChannel_LoopAtStart | RChannel_LoopAtEnd); if (sResult == 0) { sResult = m_animStand.Get(ms_apszStandResNames, RChannel_LoopAtStart | RChannel_LoopAtEnd); if (sResult == 0) { sResult = m_animWalk.Get(ms_apszWalkResNames, RChannel_LoopAtStart | RChannel_LoopAtEnd); if (sResult == 0) { sResult = m_animShot.Get(ms_apszShotResNames); if (sResult == 0) { sResult = m_animBlownup.Get(ms_apszBlownupResNames); if (sResult == 0) { sResult = m_animHide.Get(ms_apszHideResNames); if (sResult == 0) { sResult = m_animDie.Get(ms_apszDieResNames); if (sResult == 0) { // Add more anim gets here if necessary } else { TRACE("COstrich::GetResources - Failed to open 3D die animation\n"); } } else { TRACE("COstrich::GetResources - Failed to open 3D hide animation\n"); } } else { TRACE("COstrich::GetResources - Failed to open 3D blownup animation\n"); } } else { TRACE("COstrich::GetResources - Failed to open 3D shot animation\n"); } } else { TRACE("COstrich::GetResources - Failed to open 3D walk animation\n"); } } else { TRACE("COstrich::GetResources - Failed to open 3D stand animation\n"); } } else { TRACE("COstrich::GetResources - Failed to open 3D run animation\n"); } return sResult; } //////////////////////////////////////////////////////////////////////////////// // Free all resources //////////////////////////////////////////////////////////////////////////////// short COstrich::FreeResources(void) // Returns 0 if successfull, non-zero otherwise { m_animRun.Release(); m_animStand.Release(); m_animWalk.Release(); m_animShot.Release(); m_animBlownup.Release(); m_animHide.Release(); m_animDie.Release(); return 0; } //////////////////////////////////////////////////////////////////////////////// // ProcessMessages - Similar to the base class version but handles a few more //////////////////////////////////////////////////////////////////////////////// void COstrich::ProcessMessages(void) { // Check queue of messages. GameMessage msg; while (m_MessageQueue.DeQ(&msg) == true) { ProcessMessage(&msg); switch(msg.msg_Generic.eType) { case typePanic: OnPanicMsg(&(msg.msg_Panic)); break; } } } //////////////////////////////////////////////////////////////////////////////// // Message handlers //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // Shot Message //////////////////////////////////////////////////////////////////////////////// void COstrich::OnShotMsg(Shot_Message* pMessage) { UnlockAchievement(ACHIEVEMENT_FIGHT_AN_OSTRICH); m_stockpile.m_sHitPoints -= pMessage->sDamage; if (m_state != State_BlownUp && // m_state != State_Shot && m_state != State_Die && m_state != State_Dead) { CCharacter::OnShotMsg(pMessage); // Restart the animation even if he is shot, m_lAnimTime = 0; m_panimCur = &m_animShot; if (m_state != State_Shot) { PlaySample( g_smidOstrichShot, SampleMaster::Voices); m_state = State_Shot; AlertFlock(); } } } //////////////////////////////////////////////////////////////////////////////// // Explosion message //////////////////////////////////////////////////////////////////////////////// void COstrich::OnExplosionMsg(Explosion_Message* pMessage) { if (m_state != State_BlownUp) { UnlockAchievement(ACHIEVEMENT_FIGHT_AN_OSTRICH); CCharacter::OnExplosionMsg(pMessage); // Explosion kills the guy m_stockpile.m_sHitPoints = 0; // m_dExtVertVel = ms_dExplosionVelocity; m_state = State_BlownUp; m_lAnimTime = 0; m_panimCur = &m_animBlownup; PlaySample( g_smidOstrichBlownup, SampleMaster::Voices); AlertFlock(); } } //////////////////////////////////////////////////////////////////////////////// // Burning message //////////////////////////////////////////////////////////////////////////////// void COstrich::OnBurnMsg(Burn_Message* pMessage) { UnlockAchievement(ACHIEVEMENT_FIGHT_AN_OSTRICH); CCharacter::OnBurnMsg(pMessage); m_stockpile.m_sHitPoints -= MAX(pMessage->sDamage / 4, 1); if (m_state != State_Burning && m_state != State_BlownUp && m_state != State_Die && m_state != State_Dead) { m_state = State_Burning; m_panimCur = &m_animRun; m_lAnimTime = 0; PlaySample( g_smidOstrichBurning, SampleMaster::Voices); AlertFlock(); } } //////////////////////////////////////////////////////////////////////////////// // Panic message //////////////////////////////////////////////////////////////////////////////// void COstrich::OnPanicMsg(Panic_Message* pMessage) { if (m_state != State_Die && m_state != State_Dead && m_state != State_BlownUp && m_state != State_Shot && m_state != State_Burning && m_state != State_Panic && m_state != State_Hide) { m_state = State_Panic; m_panimCur = &m_animRun; m_lAnimTime = GetRandom() % m_panimCur->m_psops->TotalTime(); } } //////////////////////////////////////////////////////////////////////////////// // AlertFlock //////////////////////////////////////////////////////////////////////////////// void COstrich::AlertFlock(void) { // CThing::Things::iterator iNext; CThing* pThing; GameMessage msg; // GameMessage msgStopSound; // msgStopSound.msg_ObjectDelete.eType = typeObjectDelete; // msgStopSound.msg_ObjectDelete.sPriority = 0; msg.msg_Panic.eType = typePanic; msg.msg_Panic.sPriority = 0; msg.msg_Panic.sX = (short) m_dX; msg.msg_Panic.sY = (short) m_dY; msg.msg_Panic.sZ = (short) m_dZ; CListNode* pNext = m_pRealm->m_everythingHead.m_pnNext; while (pNext->m_powner != NULL) { pThing = pNext->m_powner; if (pThing->GetClassID() == COstrichID && pThing != this) SendThingMessage(&msg, pThing); // else if (pThing->GetClassID() == CSoundThingID) // SendThingMessage(&msgStopSound, pThing); pNext = pNext->m_pnNext; } } //////////////////////////////////////////////////////////////////////////////// // Implements basic one-time functionality for each time State_Dead is // entered. //////////////////////////////////////////////////////////////////////////////// void COstrich::OnDead(void) { // Call base class. CDoofus::OnDead(); } //////////////////////////////////////////////////////////////////////////////// // ChangeRandomState - Choose among the 3 normal states, stand, hide or walk //////////////////////////////////////////////////////////////////////////////// void COstrich::ChangeRandomState(void) { short sMod; m_lTimer = m_pRealm->m_time.GetGameTime() + ms_lStateChangeTime + GetRandom() % 5000; sMod = m_lTimer % 3; m_dRot = rspMod360(m_dRot - 10 + (GetRandom() % 20)); m_sRotDirection = m_lTimer % 2; switch (sMod) { case 0: m_state = State_Hide; if (m_panimCur != &m_animHide) { m_panimCur = &m_animHide; m_lAnimTime = 0; } break; case 1: m_state = State_Stand; if (m_panimCur != &m_animStand) { m_panimCur = &m_animStand; m_lAnimTime = 0; } break; case 2: m_state = State_Walk; if (m_panimCur != &m_animWalk) { m_panimCur = &m_animWalk; m_lAnimTime = 0; } break; default: break; } } //////////////////////////////////////////////////////////////////////////////// // EOF ////////////////////////////////////////////////////////////////////////////////