// * This file is part of the COLOBOT source code // * Copyright (C) 2001-2008, Daniel ROUX & EPSITEC SA, www.epsitec.ch // * // * This program is free software: you can redistribute it and/or modify // * it under the terms of the GNU General Public License as published by // * the Free Software Foundation, either version 3 of the License, or // * (at your option) any later version. // * // * 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, see http://www.gnu.org/licenses/. #include #include "object/task/taskrecover.h" #include "common/iman.h" #include "graphics/engine/particle.h" #include "math/geometry.h" #include "physics/physics.h" #include "object/robotmain.h" const float ENERGY_RECOVER = 0.25f; // energy consumed by recovery const float RECOVER_DIST = 11.8f; // Object's constructor. CTaskRecover::CTaskRecover(CObject* object) : CTask(object) { m_ruin = 0; m_soundChannel = -1; } // Object's constructor. CTaskRecover::~CTaskRecover() { } // Management of an event. bool CTaskRecover::EventProcess(const Event &event) { CObject* power; Math::Vector pos, speed; Math::Point dim; float a, g, cirSpeed, angle, energy, dist, linSpeed; if ( m_engine->GetPause() ) return true; if ( event.type != EVENT_FRAME ) return true; if ( m_bError ) return false; if ( m_phase == TRP_TURN ) // preliminary rotation? { a = m_object->GetAngleY(0); g = m_angle; cirSpeed = Math::Direction(a, g)*1.0f; if ( cirSpeed > 1.0f ) cirSpeed = 1.0f; if ( cirSpeed < -1.0f ) cirSpeed = -1.0f; m_physics->SetMotorSpeedZ(cirSpeed); // turns left / right return true; } m_progress += event.rTime*m_speed; // others advance m_time += event.rTime; if ( m_phase == TRP_DOWN ) { angle = Math::PropAngle(126, -10, m_progress); m_object->SetAngleZ(2, angle); m_object->SetAngleZ(4, angle); angle = Math::PropAngle(-144, 0, m_progress); m_object->SetAngleZ(3, angle); m_object->SetAngleZ(5, angle); } if ( m_phase == TRP_MOVE ) // preliminary forward/backward? { dist = Math::Distance(m_object->GetPosition(0), m_ruin->GetPosition(0)); linSpeed = 0.0f; if ( dist > RECOVER_DIST ) linSpeed = 1.0f; if ( dist < RECOVER_DIST ) linSpeed = -1.0f; m_physics->SetMotorSpeedX(linSpeed); // forward/backward return true; } if ( m_phase == TRP_OPER ) { power = m_object->GetPower(); if ( power != 0 ) { energy = power->GetEnergy(); power->SetEnergy(energy-ENERGY_RECOVER*event.rTime*m_speed); } speed.x = (Math::Rand()-0.5f)*0.1f*m_progress; speed.y = (Math::Rand()-0.5f)*0.1f*m_progress; speed.z = (Math::Rand()-0.5f)*0.1f*m_progress; m_ruin->SetCirVibration(speed); if ( m_progress >= 0.75f ) { m_ruin->SetZoom(0, 1.0f-(m_progress-0.75f)/0.25f); } if ( m_progress > 0.5f && m_progress < 0.8f ) { m_metal->SetZoom(0, (m_progress-0.5f)/0.3f); } if ( m_lastParticle+m_engine->ParticleAdapt(0.02f) <= m_time ) { m_lastParticle = m_time; pos = m_recoverPos; pos.x += (Math::Rand()-0.5f)*8.0f*(1.0f-m_progress); pos.z += (Math::Rand()-0.5f)*8.0f*(1.0f-m_progress); pos.y -= 4.0f; speed.x = (Math::Rand()-0.5f)*0.0f; speed.z = (Math::Rand()-0.5f)*0.0f; speed.y = Math::Rand()*15.0f; dim.x = Math::Rand()*2.0f+1.5f; dim.y = dim.x; m_particle->CreateParticle(pos, speed, dim, Gfx::PARTIRECOVER, 1.0f, 0.0f, 0.0f); } } if ( m_phase == TRP_UP ) { angle = Math::PropAngle(-10, 126, m_progress); m_object->SetAngleZ(2, angle); m_object->SetAngleZ(4, angle); angle = Math::PropAngle(0, -144, m_progress); m_object->SetAngleZ(3, angle); m_object->SetAngleZ(5, angle); if ( m_lastParticle+m_engine->ParticleAdapt(0.02f) <= m_time ) { m_lastParticle = m_time; pos = m_recoverPos; pos.y -= 4.0f; speed.x = (Math::Rand()-0.5f)*0.0f; speed.z = (Math::Rand()-0.5f)*0.0f; speed.y = Math::Rand()*15.0f; dim.x = Math::Rand()*2.0f+1.5f; dim.y = dim.x; m_particle->CreateParticle(pos, speed, dim, Gfx::PARTIRECOVER, 1.0f, 0.0f, 0.0f); } } return true; } // Assigns the goal was achieved. Error CTaskRecover::Start() { CObject* power; Math::Matrix* mat; Math::Vector pos, iPos, oPos; float energy; ObjectType type; m_bError = true; // operation impossible if ( !m_physics->GetLand() ) return ERR_RECOVER_VEH; type = m_object->GetType(); if ( type != OBJECT_MOBILErr ) return ERR_RECOVER_VEH; power = m_object->GetPower(); if ( power == 0 ) return ERR_RECOVER_ENERGY; energy = power->GetEnergy(); if ( energy < ENERGY_RECOVER/power->GetCapacity()+0.05f ) return ERR_RECOVER_ENERGY; mat = m_object->GetWorldMatrix(0); pos = Math::Vector(RECOVER_DIST, 3.3f, 0.0f); pos = Transform(*mat, pos); // position in front m_recoverPos = pos; m_ruin = SearchRuin(); if ( m_ruin == 0 ) return ERR_RECOVER_NULL; m_ruin->SetLock(true); // ruin no longer usable iPos = m_object->GetPosition(0); oPos = m_ruin->GetPosition(0); m_angle = Math::RotateAngle(oPos.x-iPos.x, iPos.z-oPos.z); // CW ! m_metal = 0; m_phase = TRP_TURN; m_progress = 0.0f; m_speed = 1.0f/1.0f; m_time = 0.0f; m_lastParticle = 0.0f; m_bError = false; // ok m_camera->StartCentering(m_object, Math::PI*0.85f, 99.9f, 10.0f, 3.0f); return ERR_OK; } // Indicates whether the action is finished. Error CTaskRecover::IsEnded() { Math::Matrix* mat; Math::Vector pos, speed, goal; Math::Point dim; float angle, dist, time; int i; if ( m_engine->GetPause() ) return ERR_CONTINUE; if ( m_bError ) return ERR_STOP; if ( m_phase == TRP_TURN ) // preliminary rotation? { angle = m_object->GetAngleY(0); angle = Math::NormAngle(angle); // 0..2*Math::PI if ( Math::TestAngle(angle, m_angle-Math::PI*0.01f, m_angle+Math::PI*0.01f) ) { m_physics->SetMotorSpeedZ(0.0f); dist = Math::Distance(m_object->GetPosition(0), m_ruin->GetPosition(0)); if ( dist > RECOVER_DIST ) { time = m_physics->GetLinTimeLength(dist-RECOVER_DIST, 1.0f); m_speed = 1.0f/time; } else { time = m_physics->GetLinTimeLength(RECOVER_DIST-dist, -1.0f); m_speed = 1.0f/time; } m_phase = TRP_MOVE; m_progress = 0.0f; } return ERR_CONTINUE; } if ( m_phase == TRP_MOVE ) // preliminary advance? { dist = Math::Distance(m_object->GetPosition(0), m_ruin->GetPosition(0)); if ( dist >= RECOVER_DIST-1.0f && dist <= RECOVER_DIST+1.0f ) { m_physics->SetMotorSpeedX(0.0f); mat = m_object->GetWorldMatrix(0); pos = Math::Vector(RECOVER_DIST, 3.3f, 0.0f); pos = Transform(*mat, pos); // position in front m_recoverPos = pos; i = m_sound->Play(SOUND_MANIP, m_object->GetPosition(0), 0.0f, 0.9f, true); m_sound->AddEnvelope(i, 1.0f, 1.5f, 0.3f, SOPER_CONTINUE); m_sound->AddEnvelope(i, 1.0f, 1.5f, 1.0f, SOPER_CONTINUE); m_sound->AddEnvelope(i, 0.0f, 0.9f, 0.3f, SOPER_STOP); m_phase = TRP_DOWN; m_progress = 0.0f; m_speed = 1.0f/1.5f; m_time = 0.0f; } else { if ( m_progress > 1.0f ) // timeout? { m_ruin->SetLock(false); // usable again m_camera->StopCentering(m_object, 2.0f); return ERR_RECOVER_NULL; } } return ERR_CONTINUE; } if ( m_progress < 1.0f ) return ERR_CONTINUE; m_progress = 0.0f; if ( m_phase == TRP_DOWN ) { m_metal = new CObject(); if ( !m_metal->CreateResource(m_recoverPos, 0.0f, OBJECT_METAL) ) { delete m_metal; m_metal = 0; Abort(); m_bError = true; m_main->DisplayError(ERR_TOOMANY, m_object); return ERR_STOP; } m_metal->SetLock(true); // metal not yet usable m_metal->SetZoom(0, 0.0f); mat = m_object->GetWorldMatrix(0); pos = Math::Vector(RECOVER_DIST, 3.1f, 3.9f); pos = Transform(*mat, pos); goal = Math::Vector(RECOVER_DIST, 3.1f, -3.9f); goal = Transform(*mat, goal); m_particle->CreateRay(pos, goal, Gfx::PARTIRAY2, Math::Point(2.0f, 2.0f), 8.0f); m_soundChannel = m_sound->Play(SOUND_RECOVER, m_ruin->GetPosition(0), 0.0f, 1.0f, true); m_sound->AddEnvelope(m_soundChannel, 0.6f, 1.0f, 2.0f, SOPER_CONTINUE); m_sound->AddEnvelope(m_soundChannel, 0.6f, 1.0f, 4.0f, SOPER_CONTINUE); m_sound->AddEnvelope(m_soundChannel, 0.0f, 0.7f, 2.0f, SOPER_STOP); m_phase = TRP_OPER; m_speed = 1.0f/8.0f; return ERR_CONTINUE; } if ( m_phase == TRP_OPER ) { m_metal->SetZoom(0, 1.0f); m_ruin->DeleteObject(); // destroys the ruin delete m_ruin; m_ruin = 0; m_soundChannel = -1; i = m_sound->Play(SOUND_MANIP, m_object->GetPosition(0), 0.0f, 0.9f, true); m_sound->AddEnvelope(i, 1.0f, 1.5f, 0.3f, SOPER_CONTINUE); m_sound->AddEnvelope(i, 1.0f, 1.5f, 1.0f, SOPER_CONTINUE); m_sound->AddEnvelope(i, 0.0f, 0.9f, 0.3f, SOPER_STOP); m_phase = TRP_UP; m_speed = 1.0f/1.5f; return ERR_CONTINUE; } m_metal->SetLock(false); // metal usable Abort(); return ERR_STOP; } // Suddenly ends the current action. bool CTaskRecover::Abort() { m_object->SetAngleZ(2, 126.0f*Math::PI/180.0f); m_object->SetAngleZ(4, 126.0f*Math::PI/180.0f); m_object->SetAngleZ(3, -144.0f*Math::PI/180.0f); m_object->SetAngleZ(5, -144.0f*Math::PI/180.0f); // rest if ( m_soundChannel != -1 ) { m_sound->FlushEnvelope(m_soundChannel); m_sound->AddEnvelope(m_soundChannel, 1.0f, 1.0f, 1.0f, SOPER_STOP); m_soundChannel = -1; } m_camera->StopCentering(m_object, 2.0f); return true; } // Seeks if a ruin is in front of the vehicle. CObject* CTaskRecover::SearchRuin() { CObject *pObj, *pBest; Math::Vector oPos; ObjectType type; float dist, min; int i; CInstanceManager* iMan = CInstanceManager::GetInstancePointer(); pBest = 0; min = 100000.0f; for ( i=0 ; i<1000000 ; i++ ) { pObj = static_cast(iMan->SearchInstance(CLASS_OBJECT, i)); if ( pObj == 0 ) break; type = pObj->GetType(); if ( type == OBJECT_RUINmobilew1 || type == OBJECT_RUINmobilew2 || type == OBJECT_RUINmobilet1 || type == OBJECT_RUINmobilet2 || type == OBJECT_RUINmobiler1 || type == OBJECT_RUINmobiler2 ) // vehicle in ruin? { oPos = pObj->GetPosition(0); dist = Math::Distance(oPos, m_recoverPos); if ( dist > 40.0f ) continue; if ( dist < min ) { min = dist; pBest = pObj; } } } return pBest; }