/* ** Copyright (C) 1999 Microsoft Corporation. All Rights Reserved. ** ** File: trainingmission.cpp ** ** Author: ** ** Description: ** Implementation of the training library "trainingmission" interface. ** ** History: */ #include "pch.h" #include "TrainingMission.h" #include "GoalList.h" #include "Predicate.h" #include "TrueCondition.h" #include "FalseCondition.h" #include "AndCondition.h" #include "OrCondition.h" #include "NotCondition.h" #include "PeriodicCondition.h" #include "ElapsedTimeCondition.h" #include "ObjectMovingTowardsCondition.h" #include "ObjectWithinRadiusCondition.h" #include "ObjectPointingAtCondition.h" #include "ConditionalAction.h" #include "ConditionList.h" #include "ActionList.h" #include "MessageAction.h" #include "PlaySoundAction.h" #include "SetCommandAction.h" #include "NoAction.h" #include "NTimesCondition.h" #include "EndMissionAction.h" #include "ResetAction.h" #include "ResetShipAction.h" #include "SoundFinishedCondition.h" #include "SetDisplayModeAction.h" #include "TurnShipAboutCondition.h" #include "GetRadarLODCondition.h" #include "SetRadarLODAction.h" #include "GetControlActiveCondition.h" #include "GetStateBitsCondition.h" #include "GetShipIsStoppedCondition.h" #include "SetControlConstraintsAction.h" #include "GetAutopilotCondition.h" #include "SetAutopilotAction.h" #include "GetTargetCondition.h" #include "SuspendedPlaySoundAction.h" #include "SuspendedSoundFinishedCondition.h" #include "CommandAcknowledgedCondition.h" #include "GetKeyCondition.h" #include "GetChatCondition.h" namespace Training { //------------------------------------------------------------------------------ // class methods //------------------------------------------------------------------------------ /* void */ TrainingMission::TrainingMission (void) : m_pCondition (0), m_pDeadCondition (0), m_commanderID (NA), m_pChatCondition (0), m_bSkipPostSlideShow (false) { // get the window pointer TrekWindow* pWindow = GetWindow (); // check to see if music was playing, then turn it off if ((m_bMusicWasOn = pWindow->GetMusicIsOn ()) == true) pWindow->SetMusicOn (false); // check to see what the HUD style is, and set it to hardware if ((m_fHUDStyle = pWindow->GetHUDStyle ()) != 0.0f) pWindow->SetHUDStyle (0.0f); // load the default key map pWindow->GetInput ()->LoadMap (DEFAULTINPUTMAP_FILE); } //------------------------------------------------------------------------------ /* void */ TrainingMission::~TrainingMission (void) { // clean up the old condition assert (m_pCondition == 0); delete m_pDeadCondition; // clean up the wait conditions std::list::iterator pWaitConditionIterator = m_WaitConditionList.begin (); std::list::iterator end = m_WaitConditionList.end (); while (pWaitConditionIterator != end) delete (*pWaitConditionIterator++); // get the window pointer TrekWindow* pWindow = GetWindow (); // check to see if we have quit or ar just running through the end of the mission if (m_bSkipPostSlideShow) // go back to the training screen pWindow->screen (ScreenIDTrainScreen); else // go to the post training mission slideshow pWindow->screen (ScreenIDPostTrainSlideshow); // quit the mission PlayerInfo* pPlayerInfo = trekClient.GetPlayerInfo (); trekClient.RemovePlayerFromSide (pPlayerInfo, QSR_Quit); trekClient.RemovePlayerFromMission (pPlayerInfo, QSR_Quit); // turn music back on if it was on when we started pWindow->SetMusicOn (m_bMusicWasOn); // set the HUD style back to what it was pWindow->SetHUDStyle (m_fHUDStyle); // load the customized key map if one exists pWindow->GetInput ()->LoadMap (INPUTMAP_FILE); // restart the game clock (if it was paused) if (Time::IsPaused ()) Time::Continue (); } //------------------------------------------------------------------------------ void TrainingMission::SetupShipAndCamera (void) { // set up the ship IshipIGC* pShip = trekClient.GetShip (); trekClient.ResetClusterScanners(trekClient.GetShip()->GetSide()); pShip->SetOrientation(Orientation (Vector (1.0f, 0.0f, 0.0f), Vector (0.0f, 0.0f, 1.0f))); pShip->SetPosition(Vector(0.0f, 0.0f, 0.0f)); pShip->SetCluster(trekClient.GetCore()->GetCluster(GetStartSectorID ())); // set up the camera TrekWindow* pWindow = GetWindow (); pWindow->SetViewMode (TrekWindow::vmCombat); pWindow->SetCameraMode (TrekWindow::cmExternalOrbit); pWindow->SetRadarMode (RadarImage::c_rlAll); //pWindow->EnableDisplacementCommandView (false); // set the unnecessary panes to off pWindow->TurnOffOverlayFlags (ofSectorPane | ofInventory | ofGameState); } //------------------------------------------------------------------------------ void TrainingMission::Start (void) { trekClient.ClearLoadout (); trekClient.GetClientEventSource ()->OnClearChat (); CreateUniverse (); trekClient.OnEnterGame(); m_pCondition = CreateMission (); ResetAction::Initialize (); CommandAcknowledgedCondition::SetCommandAcknowledged (c_cidDoNothing); m_pCondition->Start (); trekClient.m_serverOffset = 0; } //------------------------------------------------------------------------------ bool TrainingMission::Frame (void) { // Check that the play condition is valid. if (m_pCondition) { // Check to see if the mission is paused, and evaluate the mission // goals only if it is running. if (not Time::IsPaused ()) { // Check to see if there is a wait condition. std::list::iterator pWaitConditionIterator = m_WaitConditionList.begin (); std::list::iterator end = m_WaitConditionList.end (); if (pWaitConditionIterator != end) { while (pWaitConditionIterator != end) { // See if the wait condition is satisfied. if ((*pWaitConditionIterator)->Evaluate () == true) { // Delete the condition and remove it from the list so we don't // check it next time around. Note that we have to advance the // iterator at the same time in order to preserve traversal // order. delete (*pWaitConditionIterator); m_WaitConditionList.erase (pWaitConditionIterator++); } else { // The wait condition is false, so leave it alone and advance // the iterator. pWaitConditionIterator++; } } } else { // There is no wait condition, so evaluate the play condition. if (m_pCondition->Evaluate ()) Terminate (); } } // Always return 'false' when running. return false; } else { // There is no play condition, so return 'true' to indicate completion. // This will result in termination of the mission. This path will occur // after Terminate has been called on the mission. return true; } } //------------------------------------------------------------------------------ void TrainingMission::Terminate (void) { // pause the game clock, if it isn't already stopped if (not Time::IsPaused ()) Time::Pause (); // swap the condition into dead space, and set it to null m_pDeadCondition = m_pCondition; m_pCondition = 0; } //------------------------------------------------------------------------------ void TrainingMission::AddWaitCondition (Condition* pWaitCondition) { // XXX I don't remember if order is important, but look at ResetAction to see if it is m_WaitConditionList.push_front (pWaitCondition); } //------------------------------------------------------------------------------ void TrainingMission::RecordChat (ChatTarget recipient) { if (m_pChatCondition) static_cast (m_pChatCondition)->RecordChat (recipient); } //------------------------------------------------------------------------------ void TrainingMission::SetChatCondition (Condition* pChatCondition) { if (m_pChatCondition) delete m_pChatCondition; m_pChatCondition = pChatCondition; } //------------------------------------------------------------------------------ bool TrainingMission::RecordKeyPress (TrekKey key) { // check the key for tm conditions switch (key) { // XXX hack to disable some keys in training case TK_ViewCommand: case TK_ConModeCommand: case TK_ConModeInvest: case TK_TargetSelf: case TK_Suicide: case TK_ConModeGameState: case TK_ConModeTeleport: case TK_ToggleAutoPilot: return false; case TK_PauseTM: { // if the game is paused if (Time::IsPaused ()) // resume play Time::Continue (); else // otherwise pause it Time::Pause (); } break; #ifdef _DEBUG_TRAINING case TK_SkipTMGoal: { // XXX NYI } break; case TK_IncreaseTMClockSpeed: { // increase the clock speed by increasing the clock shift value Time::SetShift (Time::GetShift () + 1); } break; case TK_DecreaseTMClockSpeed: { // decrease the clock speed by decreasing the clock shift value Time::SetShift (Time::GetShift () - 1); } break; case TK_ResetTMClockSpeed: { // reset the clock speed by clearing the clock shift value Time::SetShift (0); } break; #endif default: { // iterate over the key conditions with the unknown key std::list::iterator iterator = m_KeyConditionList.begin (); while (iterator != m_KeyConditionList.end ()) (static_cast (*iterator++))->KeyPressed (key); } break; } return true; } //------------------------------------------------------------------------------ void TrainingMission::AddKeyCondition (Condition* pKeyCondition) { m_KeyConditionList.push_front (pKeyCondition); } //------------------------------------------------------------------------------ bool TrainingMission::ShipLanded (void) { return true; } //------------------------------------------------------------------------------ void TrainingMission::ShipDied (ImodelIGC* pLauncher) { // fetch a few needed variables IshipIGC* pShip = trekClient.GetShip (); TrekWindow* pWindow = GetWindow (); // save the current status of the ship and camera m_deadViewMode = pWindow->GetViewMode (); m_deadCameraMode = pWindow->GetCameraMode (); m_deadSectorID = static_cast (pShip->GetCluster ()->GetObjectID ()); // check if there was a second party involved in the player's death, and that party is valid if (pLauncher && pLauncher->GetMission ()) { // if the death occurred because the ship whacked something, we want to be sure that // it won't die immediately upon returning. To that end, we move the ship away from // whatever it collided with, and adjust its velocity accordingly Vector shipPosition = pShip->GetPosition (); Vector launcherPosition = pLauncher->GetPosition (); Vector delta = shipPosition - launcherPosition; float fDeltaLength = delta.Length (); Vector deltaUnit = delta / fDeltaLength; float fNewVelocity = (pShip->GetVelocity ().Length () * 0.5f); Vector newVelocity = deltaUnit * fNewVelocity; float fNewSeparation = 1.5f * (pShip->GetRadius () + pLauncher->GetRadius ()); if (fNewSeparation > fDeltaLength) pShip->SetPosition (launcherPosition + (deltaUnit * fNewSeparation)); pShip->SetVelocity (newVelocity); } // toss the player from the ship trekClient.EjectPlayer (pLauncher); // move the ship to empty space pShip->SetCluster (NULL); } //------------------------------------------------------------------------------ bool TrainingMission::RestoreShip (void) { // fetch a few needed variables IshipIGC* pShip = trekClient.GetShip (); TrekWindow* pWindow = GetWindow (); ImissionIGC* pMission = trekClient.GetCore (); // restore the ship's health pShip->SetFraction (1.0f); // set the view cluster to end the camera override trekClient.SetViewCluster (NULL); // Set the ship cluster back to what it was before dying pShip->SetCluster (pMission->GetCluster (m_deadSectorID)); // restore the view and camera modes to what they were before dying pWindow->SetViewMode (m_deadViewMode); pWindow->SetCameraMode (m_deadCameraMode); // set all of the clusters to be active so that movement works const ClusterListIGC* pClusterList = pMission->GetClusters (); ClusterLinkIGC* pClusterLink; for (pClusterLink = pClusterList->first (); pClusterLink; pClusterLink = pClusterLink->next ()) { IclusterIGC * pCluster = pClusterLink->data (); pCluster->SetActive (true); } // wait for the override camera to end (wintrek.cpp, in OverrideCamera - time is 5 seconds or 3 seconds) AddWaitCondition (new ElapsedTimeCondition (5.0f)); // always return true return true; } //------------------------------------------------------------------------------ IshipIGC* TrainingMission::GetCommanderShip (void) const { return trekClient.GetCore ()->GetShip (m_commanderID); } //------------------------------------------------------------------------------ void TrainingMission::AddPartToShip (IshipIGC* pShip, PartID part, Mount mount, short ammo) { PartData pd; pd.partID = part; pd.mountID = mount; pd.amount = ammo; pShip->CreateAndAddPart (&pd); } //------------------------------------------------------------------------------ void TrainingMission::AddPartToShip (PartID part, Mount mount, short ammo) { AddPartToShip (trekClient.GetShip (), part, mount, ammo); } //------------------------------------------------------------------------------ IshipIGC* TrainingMission::CreateDrone (const ZString& name, ShipID shipID, HullID hullID, SideID sideID, PilotType pilotType) { DataShipIGC dataShip; ImissionIGC* pMission = trekClient.GetCore (); // generate a ship ID if we need to if (shipID == NA) shipID = pMission->GenerateNewShipID (); // set the known parameters dataShip.shipID = (shipID != NA) ? shipID : pMission->GenerateNewShipID (); dataShip.hullID = hullID; dataShip.sideID = sideID; dataShip.pilotType = pilotType; strcpy (dataShip.name, name); // clear out the dynamic values //dataShip.wingID = 0; dataShip.nKills = 0; dataShip.nDeaths = 0; dataShip.nParts = 0; dataShip.baseObjectID = NA; // assign order abilities appropriate to the pilot type switch (pilotType) { case c_ptMiner: dataShip.abmOrders = c_aabmMineHe3; break; case c_ptBuilder: dataShip.abmOrders = c_aabmBuildable; break; default: dataShip.abmOrders = 0; break; } // create the ship IshipIGC* pShip = static_cast (pMission->CreateObject (pMission->GetLastUpdate(), OT_ship, &dataShip, sizeof(DataShipIGC))); // create the player info CreatePlayerInfo (pShip, sideID, shipID); // set up the default loadouts for the desired ship DefaultLoadout (pShip); // make sure the drone works with autopilot pShip->SetAutopilot(true); // free the pointer pShip->Release(); // return the ship pointer return pShip; } //------------------------------------------------------------------------------ void TrainingMission::SetSkipPostSlideshow (void) { m_bSkipPostSlideShow = true; } //------------------------------------------------------------------------------ void TrainingMission::LoadUniverse (const ZString& name, HullID hullID, StationID homeStationID) { ImissionIGC* pCore = trekClient.ResetStaticData (); Time now = pCore->GetLastUpdate(); char* szStaticCoreFilename = IGC_STATIC_CORE_FILENAME; int iStaticCoreVersion = LoadIGCStaticCore (szStaticCoreFilename, pCore, false); // stuff for creating the sides static const float fSideColors[c_cSidesMax][3] = { {200.0f/255.0f, 15.0f/255.0f, 200.0f/255.0f}, //purple { 8.0f/255.0f, 184.0f/255.0f, 184.0f/255.0f}, //teal { 0.0f/255.0f, 0.0f/255.0f, 233.0f/255.0f}, //blue {184.0f/255.0f, 184.0f/255.0f, 50.0f/255.0f}, //icky yellow {184.0f/255.0f, 92.0f/255.0f, 0.0f/255.0f}, //icky orange {123.0f/255.0f, 61.0f/255.0f, 61.0f/255.0f} //icky magenta }; static const char* szSideNames[c_cSidesMax] = { "Iron League", "Bios", "Gigacorp", "Protectorate", "Colossal Mining Corp", "Midnight Runners" }; static CivID civs[c_cSidesMax] = {25, 35, 18, 25, 35, 18}; assert (sizeofArray(szSideNames) == c_cSidesMax); assert (sizeofArray(civs) == c_cSidesMax); // set up the civs associated with the sides. There are more sides than civs, so we want to loop // over them once /* const CivilizationListIGC* pCivsList = pCore->GetCivilizations (); CivilizationLinkIGC* first = pCivsList->first (); int iCivCount = pCivsList->n (); for (int iSideIndex = 0; iSideIndex < iCivCount; iSideIndex++) { IcivilizationIGC* civ = first->data (); civs[iSideIndex] =static_cast (civ->GetObjectID ()); first = first->next (); } for (; iSideIndex < c_cSidesMax; iSideIndex++) civs[iSideIndex] = civs[iSideIndex % iCivCount]; */ // set up the sides for (SideID sid = 0; sid < c_cSidesMax; sid++) { IcivilizationIGC* pCiv = pCore->GetCivilization (civs[sid]); DataSideIGC ds; ds.nFlags = 0; ds.sideID = sid; ds.gasAttributes.Initialize(); assert (civs[sid] != NA); ds.civilizationID = civs[sid]; ds.color.SetRGBA(fSideColors[sid][0], fSideColors[sid][1], fSideColors[sid][2]); strcpy(ds.name, szSideNames[sid]); ds.ttbmDevelopmentTechs = pCiv->GetBaseTechs(); ds.ttbmInitialTechs = pCiv->GetBaseTechs(); ds.conquest = 100 / c_cSidesMax; ds.nKills = ds.nDeaths = ds.nBaseKills = ds.nBaseCaptures = ds.nFlags = ds.nArtifacts = 0; ds.squadID = NA; IsideIGC* pSide = static_cast (pCore->CreateObject(now, OT_side, &ds, sizeof(ds))); // make sure the player's side has all the tech bits if (sid == 0) { TechTreeBitMask allTechs; allTechs.SetAll (); pSide->SetDevelopmentTechs (allTechs); pSide->SetBuildingTechs (allTechs); } pSide->Release (); } // create the mission def FMD_S_MISSIONDEF fmMissionDef; fmMissionDef.szDescription[0] = 0; strcpy (fmMissionDef.misparms.szIGCStaticFile, szStaticCoreFilename); fmMissionDef.misparms.verIGCcore = iStaticCoreVersion; fmMissionDef.dwCookie = static_cast (pCore->GetMissionID ()); fmMissionDef.iSideMissionOwner = 0; fmMissionDef.fAutoAcceptLeaders = false; fmMissionDef.fInProgress = true; fmMissionDef.stage = STAGE_STARTED; for (int q = 0; q < c_cSidesMax; q++) { strcpy (fmMissionDef.rgszName[q], szSideNames[q]); //fmMissionDef.rgCivID[q] = civs[q]; fmMissionDef.rgStationID[q] = NA; fmMissionDef.rgShipIDLeaders[q] = NA; fmMissionDef.rgcPlayers[q] = 0; fmMissionDef.rgfAutoAccept[q] = true; fmMissionDef.rgfReady[q] = true; fmMissionDef.rgfForceReady[q] = true; fmMissionDef.rgfActive[q] = true; } MissionInfo* pMissionInfo = trekClient.MyMission(); ZAssert (pMissionInfo); pMissionInfo->Update (&fmMissionDef); trekClient.GetClientEventSource ()->OnAddMission (pMissionInfo); // get the player ship IshipIGC* pShip = trekClient.GetShip (); // set the player name pShip->SetName ("Cadet"); // set the player info for the player CreatePlayerInfo (trekClient.GetShip (), 0, pCore->GenerateNewShipID()); // now load the actual map world LoadIGCFile (name, pCore); // set up the default loadouts for the desired ship IhullTypeIGC* pHullType = pCore->GetHullType (hullID); pShip->SetBaseHullType (pHullType); DefaultLoadout (pShip); trekClient.SaveLoadout (pShip); // set the trail color ThingSite* pThingSite = trekClient.GetShip ()->GetThingSite(); assert (pThingSite); pThingSite->SetTrailColor (pCore->GetSide (0)->GetColor()); // create the commander ship m_commanderID = pCore->GenerateNewShipID(); IshipIGC* commanderShip = CreateDrone ("Commander", m_commanderID, 412, 0, c_ptWingman); // set all of the clusters to be active so that movement works const ClusterListIGC* pClusterList = pCore->GetClusters (); ClusterLinkIGC* pClusterLink; for (pClusterLink = pClusterList->first (); pClusterLink; pClusterLink = pClusterLink->next ()) { IclusterIGC * pCluster = pClusterLink->data (); pCluster->SetActive (true); } } //------------------------------------------------------------------------------ void TrainingMission::DefaultLoadout (IshipIGC* pShip) { // install all the preferred part types const IhullTypeIGC* pHullType = pShip->GetHullType (); for (PartTypeLinkIGC* pPartTypeLink = pHullType->GetPreferredPartTypes()->first (); pPartTypeLink != NULL; pPartTypeLink = pPartTypeLink->next ()) { IpartTypeIGC* pPartType = pPartTypeLink->data (); EquipmentType equipmentType = pPartType->GetEquipmentType(); Mount mountMax = (equipmentType == ET_Weapon) ? pHullType->GetMaxWeapons () : 1; for (Mount i = 0; i < mountMax; i++) if ((pShip->GetMountedPart (equipmentType, i) == NULL) && pHullType->CanMount (pPartType, i)) pShip->CreateAndAddPart (pPartType, i, 0x7fff); } // fill the empty cargo slots AddPartToShip (pShip, 24, -1, 0x7fff); //ammo AddPartToShip (pShip, 24, -2, 0x7fff); // ammo AddPartToShip (pShip, 25, -3, 0x7fff); // fuel AddPartToShip (pShip, 150, -4, 0x7fff); // missiles AddPartToShip (pShip, 150, -5, 0x7fff); // missiles // if there's a shield, make sure its charged IshieldIGC* pShield = static_cast (pShip->GetMountedPart (ET_Shield, 0)); if (pShield) pShield->SetFraction (1.0f); // weapons, energy, and ammo pShip->SetAmmo (pShip->GetHullType ()->GetMaxAmmo ()); pShip->SetEnergy (pShip->GetHullType ()->GetMaxEnergy ()); pShip->SetFuel (pShip->GetHullType ()->GetMaxFuel ()); } //------------------------------------------------------------------------------ void TrainingMission::CreatePlayerInfo (IshipIGC* pShip, SideID side, ShipID shipID) { ImissionIGC* pMission = trekClient.GetCore (); // check that the side is set correctly IsideIGC* pSide = pShip->GetSide (); if (pSide) { assert (static_cast (pSide->GetObjectID ()) == side); pShip->SetSide (NULL); } // create the player info FMD_S_PLAYERINFO fmPlayerInfo; PlayerInfo* pPlayerInfo = trekClient.FindAndCreatePlayerLink (shipID); memset(&fmPlayerInfo, 0, sizeof(fmPlayerInfo)); strcpy(fmPlayerInfo.CharacterName, pShip->GetName ()); fmPlayerInfo.iSide = NA; fmPlayerInfo.dtidDrone = NA; fmPlayerInfo.shipID = shipID; fmPlayerInfo.cbrgPersistPlayerScores = 0; pPlayerInfo->Set (&fmPlayerInfo); pPlayerInfo->SetMission (pMission); if (pShip == trekClient.GetShip ()) trekClient.SetPlayerInfo (pPlayerInfo); // update the playerinfo data pPlayerInfo->SetShip (pShip); pShip->SetPrivateData (reinterpret_cast (pPlayerInfo)); // join the mission and side trekClient.AddPlayerToMission (pPlayerInfo); trekClient.AddPlayerToSide (pPlayerInfo, side); } //------------------------------------------------------------------------------ Goal* TrainingMission::CreatePlaySoundGoal (SoundID sound) { PlaySoundAction* pPlaySoundAction = new PlaySoundAction (sound); Goal* pGoal = new Goal (new SoundFinishedCondition (pPlaySoundAction)); pGoal->AddStartAction (pPlaySoundAction); return pGoal; } //------------------------------------------------------------------------------ Condition* TrainingMission::CreateTooCloseCondition (TRef place) { TRef ship = static_cast (trekClient.GetShip()); ObjectWithinRadiusCondition* pWithinRadiusCondition = new ObjectWithinRadiusCondition (ship, place, ship->GetRadius () * 10.0f); Condition* pMovingTowardsCondition = new ObjectMovingTowardsCondition (ship, place); Condition* pAndCondition = new AndCondition (pWithinRadiusCondition, pMovingTowardsCondition); MessageAction* pTooCloseMessage = new MessageAction (ZString ("Don't get too close to ") + GetModelName (place) + "!"); float fDisplayRadius = pWithinRadiusCondition->GetRadarRadius (); char cFloatStringBuffer[256]; fDisplayRadius = floorf (fDisplayRadius * 0.02) * 50.0; sprintf (cFloatStringBuffer, "Stay at least %g meters from ", floorf (fDisplayRadius)); pTooCloseMessage->AddMessage (ZString (cFloatStringBuffer) + GetModelName (place) + "!"); pTooCloseMessage->AddMessage ("A little close, don't ya think?"); pTooCloseMessage->AddMessage ("Watch your range!"); pTooCloseMessage->AddMessage ("Whoa! where are the parachutes?"); pTooCloseMessage->AddMessage ("You scratch my ship, I scratch your hide!"); Condition* pPeriodicCondition = new PeriodicCondition (pAndCondition, 2.0f); ConditionalAction* pTooCloseCondition = new ConditionalAction (pPeriodicCondition, pTooCloseMessage, ConditionalAction::DELAYED_EXECUTION); return pTooCloseCondition; } //------------------------------------------------------------------------------ Condition* TrainingMission::CreateTooLongCondition (float fDelay, SoundID sound) { Condition* pPeriodicCondition = new PeriodicCondition (new NTimesCondition (new TrueCondition, 2, true), fDelay); ConditionalAction* pTooLongCondition = new ConditionalAction (pPeriodicCondition, new PlaySoundAction (sound)); return pTooLongCondition; } //------------------------------------------------------------------------------ }